summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Zaidman <michael.zaidman@gmail.com>2026-08-26 13:48:06 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-09-02 14:28:34 +0200
commit65d6c17a1e393b626c8872fae85c6d3dc2229949 (patch)
tree9556cf384d65d2dfa2eb257282d020a683d5d529
parent12363736643b09925575707c7d96e3dbab00f6ba (diff)
downloadlinux-stable-65d6c17a1e393b626c8872fae85c6d3dc2229949.tar.gz
linux-stable-65d6c17a1e393b626c8872fae85c6d3dc2229949.zip
HID: ft260: missed NACK from busy device
[ Upstream commit 5afac727defa0b2a3dffb2abd5fb5f594b98d217 ] When writing into a slow device like an EEPROM chip, the controller may exit the busy state before the device releases the bus. In this case, the ft260_xfer_status returns success before the data transfer completion. The patch fixes it by returning from the ft260_xfer_status() with the "-EAGAIN" on both controller and bus busy status when appropriate. It does not apply to the i2c combined transactions when after the write IO, the controller keeps the bus busy until the read IO and then between reading IOs to ensure an atomic operation. Co-developed-by: Germain Hebert <germain.hebert@ca.abb.com> Signed-off-by: Germain Hebert <germain.hebert@ca.abb.com> Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz> Stable-dep-of: bf3e39df3a39 ("HID: ft260: fix stack-use-after-return write in I2C read race") Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/hid/hid-ft260.c27
1 files changed, 22 insertions, 5 deletions
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 3c4ca0016659..30d7f014df05 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -303,7 +303,7 @@ static int ft260_i2c_reset(struct hid_device *hdev)
return ret;
}
-static int ft260_xfer_status(struct ft260_device *dev)
+static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
struct hid_device *hdev = dev->hdev;
struct ft260_get_i2c_status_report report;
@@ -334,7 +334,7 @@ static int ft260_xfer_status(struct ft260_device *dev)
ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
dev->clock);
- if (report.bus_status & FT260_I2C_STATUS_CTRL_BUSY)
+ if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
return -EAGAIN;
if (report.bus_status & FT260_I2C_STATUS_BUS_BUSY)
@@ -379,8 +379,11 @@ static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
static int ft260_hid_output_report_check_status(struct ft260_device *dev,
u8 *data, int len)
{
+ u8 bus_busy;
int ret, usec, try = 100;
struct hid_device *hdev = dev->hdev;
+ struct ft260_i2c_write_request_report *rep =
+ (struct ft260_i2c_write_request_report *)data;
ret = ft260_hid_output_report(hdev, data, len);
if (ret < 0) {
@@ -398,8 +401,18 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
ft260_dbg("wait %d usec, len %d\n", usec, len);
}
+ /*
+ * Do not check the busy bit for combined transactions
+ * since the controller keeps the bus busy between writing
+ * and reading IOs to ensure an atomic operation.
+ */
+ if (rep->flag == FT260_FLAG_START)
+ bus_busy = 0;
+ else
+ bus_busy = FT260_I2C_STATUS_BUS_BUSY;
+
do {
- ret = ft260_xfer_status(dev);
+ ret = ft260_xfer_status(dev, bus_busy);
if (ret != -EAGAIN)
break;
} while (--try);
@@ -487,6 +500,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
{
struct ft260_i2c_read_request_report rep;
struct hid_device *hdev = dev->hdev;
+ u8 bus_busy = 0;
int timeout;
int ret = 0;
@@ -525,7 +539,10 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
dev->read_buf = NULL;
- ret = ft260_xfer_status(dev);
+ if (flag & FT260_FLAG_STOP)
+ bus_busy = FT260_I2C_STATUS_BUS_BUSY;
+
+ ret = ft260_xfer_status(dev, bus_busy);
if (ret < 0) {
ret = -EIO;
ft260_i2c_reset(hdev);
@@ -1004,7 +1021,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
mutex_init(&dev->lock);
init_completion(&dev->wait);
- ret = ft260_xfer_status(dev);
+ ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
if (ret)
ft260_i2c_reset(hdev);