diff options
| author | Paulo Alcantara <pc@manguebit.org> | 2026-09-26 19:20:30 -0300 |
|---|---|---|
| committer | Paulo Alcantara <pc@manguebit.org> | 2026-09-30 14:24:12 -0300 |
| commit | dd05add7b2b6004c5fa3a1f276f56d8668f86b65 (patch) | |
| tree | 23efdcaa0b8031f6ac6f57b6734d18d6c1adc6f2 /drivers/usb/phy/phy-ulpi.c | |
| download | linux-stable-dd05add7b2b6004c5fa3a1f276f56d8668f86b65.tar.gz linux-stable-dd05add7b2b6004c5fa3a1f276f56d8668f86b65.zip | |
netfs: zero the tail of a short DIO/unbuffered readgrafted
The buffered read collector zero-fills the tail of a short read that
stops below the inode's i_size (netfs_clear_unread()), so a read that
races an extending write still returns the expected number of bytes.
The non-buffered collector path does no such thing: it just records
how much was transferred.
Add the same zero-fill for the non-buffered case, gated on
NETFS_SREQ_CLEAR_TAIL: a subreq's source sets that flag when a short
result from it is known to be safe to treat as a hole, as opposed to
NETFS_SREQ_HIT_EOF, which means the read genuinely ran off the end of
the file and should be reported short as-is. Only CLEAR_TAIL should
zero-fill here; a real EOF must stay a real short read.
No source currently sets CLEAR_TAIL on an unbuffered/DIO subrequest,
so this is inert on its own -- a following change teaches cifs to set
it in the one case that needs it.
Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item")
Reviewed-by: David Howells <dhowells@redhat.com>
Reviewed-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Paulo Alcantara <pc@manguebit.org>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Cc: Shyam Prasad N <sprasad@microsoft.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Bharath SM <bharathsm@microsoft.com>
Cc: stable@vger.kernel.org
Diffstat (limited to 'drivers/usb/phy/phy-ulpi.c')
| -rw-r--r-- | drivers/usb/phy/phy-ulpi.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c new file mode 100644 index 000000000..4df63e67b --- /dev/null +++ b/drivers/usb/phy/phy-ulpi.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Generic ULPI USB transceiver support + * + * Copyright (C) 2009 Daniel Mack <daniel@caiaq.de> + * + * Based on sources from + * + * Sascha Hauer <s.hauer@pengutronix.de> + * Freescale Semiconductors + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/usb/ulpi.h> + + +struct ulpi_info { + unsigned int id; + char *name; +}; + +#define ULPI_ID(vendor, product) (((vendor) << 16) | (product)) +#define ULPI_INFO(_id, _name) \ + { \ + .id = (_id), \ + .name = (_name), \ + } + +/* ULPI hardcoded IDs, used for probing */ +static struct ulpi_info ulpi_ids[] = { + ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), + ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), + ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"), + ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"), + ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"), +}; + +static int ulpi_set_otg_flags(struct usb_phy *phy) +{ + unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN | + ULPI_OTG_CTRL_DM_PULLDOWN; + + if (phy->flags & ULPI_OTG_ID_PULLUP) + flags |= ULPI_OTG_CTRL_ID_PULLUP; + + /* + * ULPI Specification rev.1.1 default + * for Dp/DmPulldown is enabled. + */ + if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS) + flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN; + + if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS) + flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN; + + if (phy->flags & ULPI_OTG_EXTVBUSIND) + flags |= ULPI_OTG_CTRL_EXTVBUSIND; + + return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +static int ulpi_set_fc_flags(struct usb_phy *phy) +{ + unsigned int flags = 0; + + /* + * ULPI Specification rev.1.1 default + * for XcvrSelect is Full Speed. + */ + if (phy->flags & ULPI_FC_HS) + flags |= ULPI_FUNC_CTRL_HIGH_SPEED; + else if (phy->flags & ULPI_FC_LS) + flags |= ULPI_FUNC_CTRL_LOW_SPEED; + else if (phy->flags & ULPI_FC_FS4LS) + flags |= ULPI_FUNC_CTRL_FS4LS; + else + flags |= ULPI_FUNC_CTRL_FULL_SPEED; + + if (phy->flags & ULPI_FC_TERMSEL) + flags |= ULPI_FUNC_CTRL_TERMSELECT; + + /* + * ULPI Specification rev.1.1 default + * for OpMode is Normal Operation. + */ + if (phy->flags & ULPI_FC_OP_NODRV) + flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; + else if (phy->flags & ULPI_FC_OP_DIS_NRZI) + flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI; + else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP) + flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP; + else + flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + + /* + * ULPI Specification rev.1.1 default + * for SuspendM is Powered. + */ + flags |= ULPI_FUNC_CTRL_SUSPENDM; + + return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL); +} + +static int ulpi_set_ic_flags(struct usb_phy *phy) +{ + unsigned int flags = 0; + + if (phy->flags & ULPI_IC_AUTORESUME) + flags |= ULPI_IFC_CTRL_AUTORESUME; + + if (phy->flags & ULPI_IC_EXTVBUS_INDINV) + flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS; + + if (phy->flags & ULPI_IC_IND_PASSTHRU) + flags |= ULPI_IFC_CTRL_PASSTHRU; + + if (phy->flags & ULPI_IC_PROTECT_DIS) + flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE; + + return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_flags(struct usb_phy *phy) +{ + int ret; + + ret = ulpi_set_otg_flags(phy); + if (ret) + return ret; + + ret = ulpi_set_ic_flags(phy); + if (ret) + return ret; + + return ulpi_set_fc_flags(phy); +} + +static int ulpi_check_integrity(struct usb_phy *phy) +{ + int ret, i; + unsigned int val = 0x55; + + for (i = 0; i < 2; i++) { + ret = usb_phy_io_write(phy, val, ULPI_SCRATCH); + if (ret < 0) + return ret; + + ret = usb_phy_io_read(phy, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != val) { + pr_err("ULPI integrity check: failed!"); + return -ENODEV; + } + val = val << 1; + } + + pr_info("ULPI integrity check: passed.\n"); + + return 0; +} + +static int ulpi_init(struct usb_phy *phy) +{ + int i, vid, pid, ret; + u32 ulpi_id = 0; + + for (i = 0; i < 4; i++) { + ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i); + if (ret < 0) + return ret; + ulpi_id = (ulpi_id << 8) | ret; + } + vid = ulpi_id & 0xffff; + pid = ulpi_id >> 16; + + pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid); + + for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) { + if (ulpi_ids[i].id == ULPI_ID(vid, pid)) { + pr_info("Found %s ULPI transceiver.\n", + ulpi_ids[i].name); + break; + } + } + + ret = ulpi_check_integrity(phy); + if (ret) + return ret; + + return ulpi_set_flags(phy); +} + +static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct usb_phy *phy = otg->usb_phy; + unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL); + + if (!host) { + otg->host = NULL; + return 0; + } + + otg->host = host; + + flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE | + ULPI_IFC_CTRL_3_PIN_SERIAL_MODE | + ULPI_IFC_CTRL_CARKITMODE); + + if (phy->flags & ULPI_IC_6PIN_SERIAL) + flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE; + else if (phy->flags & ULPI_IC_3PIN_SERIAL) + flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE; + else if (phy->flags & ULPI_IC_CARKIT) + flags |= ULPI_IFC_CTRL_CARKITMODE; + + return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); +} + +static int ulpi_set_vbus(struct usb_otg *otg, bool on) +{ + struct usb_phy *phy = otg->usb_phy; + unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL); + + flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT); + + if (on) { + if (phy->flags & ULPI_OTG_DRVVBUS) + flags |= ULPI_OTG_CTRL_DRVVBUS; + + if (phy->flags & ULPI_OTG_DRVVBUS_EXT) + flags |= ULPI_OTG_CTRL_DRVVBUS_EXT; + } + + return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); +} + +static void otg_ulpi_init(struct usb_phy *phy, struct usb_otg *otg, + struct usb_phy_io_ops *ops, + unsigned int flags) +{ + phy->label = "ULPI"; + phy->flags = flags; + phy->io_ops = ops; + phy->otg = otg; + phy->init = ulpi_init; + + otg->usb_phy = phy; + otg->set_host = ulpi_set_host; + otg->set_vbus = ulpi_set_vbus; +} + +struct usb_phy * +devm_otg_ulpi_create(struct device *dev, + struct usb_phy_io_ops *ops, + unsigned int flags) +{ + struct usb_phy *phy; + struct usb_otg *otg; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return NULL; + + otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + devm_kfree(dev, phy); + return NULL; + } + + otg_ulpi_init(phy, otg, ops, flags); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_otg_ulpi_create); |
