linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 00/12] HID: ft260: fixes and performance improvements
@ 2022-10-30 20:33 Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 01/12] HID: ft260: ft260_xfer_status routine cleanup Michael Zaidman
                   ` (11 more replies)
  0 siblings, 12 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

This patch series is an updated version of this one:
https://lore.kernel.org/all/20220928144854.5580-1-michael.zaidman@gmail.com/

Changes since v2:

  - Remove SMBus Quick command support
  - Missed NACK from big i2c read
  - Wake up device from power saving mode
  - Fix a NULL pointer dereference in ft260_i2c_write
  - Missed NACK from busy device

Changes since v1:

  - Do not populate hidraw device
  - Avoid stale read buffer pointer

Michael Zaidman (12):
  HID: ft260: ft260_xfer_status routine cleanup
  HID: ft260: improve i2c write performance
  HID: ft260: support i2c writes larger than HID report size
  HID: ft260: support i2c reads greater than HID report size
  HID: ft260: improve i2c large reads performance
  HID: ft260: do not populate /dev/hidraw device
  HID: ft260: skip unexpected HID input reports
  HID: ft260: remove SMBus Quick command support
  HID: ft260: missed NACK from big i2c read
  HID: ft260: wake up device from power saving mode
  HID: ft260: fix a NULL pointer dereference in ft260_i2c_write
  HID: ft260: missed NACK from busy device

 drivers/hid/hid-ft260.c | 313 ++++++++++++++++++++++++----------------
 1 file changed, 185 insertions(+), 128 deletions(-)

-- 
2.34.1

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 01/12] HID: ft260: ft260_xfer_status routine cleanup
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 02/12] HID: ft260: improve i2c write performance Michael Zaidman
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman, Guillaume Champagne

After clarifying with FTDI's support, it turned out that the error
condition (bit 1) in byte 1 of the i2c status HID report is a status
bit reflecting all error conditions. When bits 2, 3, or 4 are raised
to 1, bit 1 is set to 1 also. Since the ft260_xfer_status routine tests
the error condition bit and exits in the case of an error, the program
flow never reaches the conditional expressions for 2, 3, and 4 bits when
any of them indicates an error state. Though these expressions are never
evaluated to true, they are checked several times per IO, increasing the
ft260_xfer_status polling cycle duration.

The patch removes the conditional expressions for 2, 3, and 4 bits in
byte 1 of the i2c status HID report.

Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
Tested-by: Guillaume Champagne <champagne.guillaume.c@gmail.com>
---
 drivers/hid/hid-ft260.c | 30 ++++++++++--------------------
 1 file changed, 10 insertions(+), 20 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 79505c64dbfe..a35201d68b15 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -313,27 +313,17 @@ static int ft260_xfer_status(struct ft260_device *dev)
 	if (report.bus_status & FT260_I2C_STATUS_CTRL_BUSY)
 		return -EAGAIN;
 
-	if (report.bus_status & FT260_I2C_STATUS_BUS_BUSY)
-		return -EBUSY;
-
-	if (report.bus_status & FT260_I2C_STATUS_ERROR)
+	/*
+	 * The error condition (bit 1) is a status bit reflecting any
+	 * error conditions. When any of the bits 2, 3, or 4 are raised
+	 * to 1, bit 1 is also set to 1.
+	 */
+	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
+		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
 		return -EIO;
+	}
 
-	ret = -EIO;
-
-	if (report.bus_status & FT260_I2C_STATUS_ADDR_NO_ACK)
-		ft260_dbg("unacknowledged address\n");
-
-	if (report.bus_status & FT260_I2C_STATUS_DATA_NO_ACK)
-		ft260_dbg("unacknowledged data\n");
-
-	if (report.bus_status & FT260_I2C_STATUS_ARBITR_LOST)
-		ft260_dbg("arbitration loss\n");
-
-	if (report.bus_status & FT260_I2C_STATUS_CTRL_IDLE)
-		ret = 0;
-
-	return ret;
+	return 0;
 }
 
 static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
@@ -376,7 +366,7 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
 			break;
 	} while (--try);
 
-	if (ret == 0 || ret == -EBUSY)
+	if (ret == 0)
 		return 0;
 
 	ft260_i2c_reset(hdev);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 02/12] HID: ft260: improve i2c write performance
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 01/12] HID: ft260: ft260_xfer_status routine cleanup Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 03/12] HID: ft260: support i2c writes larger than HID report size Michael Zaidman
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman, Guillaume Champagne

The patch improves the I2C write performance by 20 - 30 percent by
revising the sleep time in the ft260_hid_output_report_check_status()
in the following ways:

1. Reduce the wait time and start to poll earlier.

Sending a large amount of data at a low I2C clock rate saturates the
internal FT260 buffer and causes hiccups in status readiness, as shown
below in the log fragment. Aligning the status check wait time to the
worst case significantly reduces the write performance.

[Oct22 10:28] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.005296] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.013460] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.003244] ft260_hid_output_report_check_status: wait 1920 usec, len 38
[  +0.000190] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.015324] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.003491] ft260_hid_output_report_check_status: wait 1920 usec, len 38
[  +0.000202] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.016047] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.002768] ft260_hid_output_report_check_status: wait 1920 usec, len 38
[  +0.000150] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.011389] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.003467] ft260_hid_output_report_check_status: wait 1920 usec, len 38
[  +0.000191] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000172] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000131] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000241] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000233] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000190] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000196] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.011314] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0
[  +0.003334] ft260_hid_output_report_check_status: wait 1920 usec, len 38
[  +0.000227] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000204] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000198] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000147] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.011060] ft260_i2c_write: rep 0xd8 addr 0x51 off 0 len 34 d[0] 0x0

  Before:
    $ sudo ./i2cperf -f 2 -o 2 -s 32 -r 0-0xff 13 0x51 -S

      Fill block with increment via i2ctransfer by chunks
      -------------------------------------------------------------------
      data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
      -------------------------------------------------------------------
      40510           80             256           8           32

  After:
    $ sudo ./i2cperf -f 2 -o 2 -s 32 -r 0-0xff 13 0x51 -S

      Fill block with increment via i2ctransfer by chunks
      -------------------------------------------------------------------
      data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
      -------------------------------------------------------------------
      52584           80             256           8           32

2. Do not sleep if the estimated I2C transfer time is below 2 ms since
   the first xfer status query frequently takes around 1.5 ms, and the
   following status queries take about 200us on average. So we usually
   return from the routine after the first 1 - 3 status checks.

[Oct22 11:14] ft260_i2c_write: rep 0xd4 addr 0x51 off 0 len 18 d[0] 0x0
[  +0.004270] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.013889] ft260_i2c_write: rep 0xd4 addr 0x51 off 0 len 18 d[0] 0x0
[  +0.000856] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000138] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.013352] ft260_i2c_write: rep 0xd4 addr 0x51 off 0 len 18 d[0] 0x0
[  +0.001501] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000177] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.014477] ft260_i2c_write: rep 0xd4 addr 0x51 off 0 len 18 d[0] 0x0
[  +0.001377] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000233] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000191] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.013197] ft260_i2c_write: rep 0xd4 addr 0x51 off 0 len 18 d[0] 0x0

  Before:
    $ sudo ./i2cperf -f 2 -o 2 -s 16 -r 0-0xff 13 0x51 -S

      Fill block with increment via i2ctransfer by chunks
      -------------------------------------------------------------------
      data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
      -------------------------------------------------------------------
      28826           73             256           16          16

  After:
    $ sudo ./i2cperf -f 2 -o 2 -s 16 -r 0-0xff 13 0x51 -S

      Fill block with increment via i2ctransfer by chunks
      -------------------------------------------------------------------
      data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
      -------------------------------------------------------------------
      45138           73             256           16          16

Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
Tested-by: Guillaume Champagne <champagne.guillaume.c@gmail.com>
---
 drivers/hid/hid-ft260.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index a35201d68b15..44106cadd746 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -345,7 +345,7 @@ 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)
 {
-	int ret, usec, try = 3;
+	int ret, usec, try = 100;
 	struct hid_device *hdev = dev->hdev;
 
 	ret = ft260_hid_output_report(hdev, data, len);
@@ -356,10 +356,14 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
 		return ret;
 	}
 
-	/* transfer time = 1 / clock(KHz) * 10 bits * bytes */
-	usec = 10000 / dev->clock * len;
-	usleep_range(usec, usec + 100);
-	ft260_dbg("wait %d usec, len %d\n", usec, len);
+	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
+	usec = len * 9000 / dev->clock;
+	if (usec > 2000) {
+		usec -= 1500;
+		usleep_range(usec, usec + 100);
+		ft260_dbg("wait %d usec, len %d\n", usec, len);
+	}
+
 	do {
 		ret = ft260_xfer_status(dev);
 		if (ret != -EAGAIN)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 03/12] HID: ft260: support i2c writes larger than HID report size
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 01/12] HID: ft260: ft260_xfer_status routine cleanup Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 02/12] HID: ft260: improve i2c write performance Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 04/12] HID: ft260: support i2c reads greater " Michael Zaidman
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman, Guillaume Champagne

To support longer than one HID report size write, the driver splits a
single i2c message data payload into multiple i2c messages of HID report
size. However, it does not replicate the offset bytes within the EEPROM
chip in every consequent HID report because it is not and should not be
aware of the EEPROM type. It breaks the i2c write message integrity and
causes the EEPROM device not to acknowledge the second HID report keeping
the i2c bus busy until the ft260 controller reports failure.

This patch preserves the i2c write message integrity by manipulating the
i2c flag bits across multiple HID reports to be seen by the EEPROM device
as a single i2c write transfer.

Before:

$ sudo ./i2cperf -f 2 -o 2 -s 64 -r 0-0xff 13 0x51 -S
Error: Sending messages failed: Input/output error

[  +3.667741] ft260_i2c_write: rep 0xde addr 0x51 off 0 len 60 d[0] 0x0
[  +0.007330] ft260_hid_output_report_check_status: wait 6400 usec, len 64
[  +0.000203] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000001] ft260_i2c_write: rep 0xd1 addr 0x51 off 60 len 6 d[0] 0x0
[  +0.002337] ft260_hid_output_report_check_status: wait 1000 usec, len 10
[  +0.000157] ft260_xfer_status: bus_status 0x2e, clock 100
[  +0.000241] ft260_i2c_reset: done
[  +0.000003] ft260_i2c_write: failed to start transfer, ret -5

After:

$ sudo ./i2cperf -f 2 -o 2 -s 128 -r 0-0xff 13 0x51 -S

  Fill block with increment via i2ctransfer by chunks
  -------------------------------------------------------------------
  data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
  -------------------------------------------------------------------
  71260           86             256           2           128

Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
Tested-by: Guillaume Champagne <champagne.guillaume.c@gmail.com>
---
 drivers/hid/hid-ft260.c | 41 +++++++++++++++++++++++------------------
 1 file changed, 23 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 44106cadd746..cec83f69ebdc 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -378,41 +378,46 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
 }
 
 static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
-			   int data_len, u8 flag)
+			   int len, u8 flag)
 {
-	int len, ret, idx = 0;
+	int ret, wr_len, idx = 0;
 	struct hid_device *hdev = dev->hdev;
 	struct ft260_i2c_write_request_report *rep =
 		(struct ft260_i2c_write_request_report *)dev->write_buf;
 
+	rep->flag = FT260_FLAG_START;
+
 	do {
-		if (data_len <= FT260_WR_DATA_MAX)
-			len = data_len;
-		else
-			len = FT260_WR_DATA_MAX;
+		if (len <= FT260_WR_DATA_MAX) {
+			wr_len = len;
+			if (flag == FT260_FLAG_START_STOP)
+				rep->flag |= FT260_FLAG_STOP;
+		} else {
+			wr_len = FT260_WR_DATA_MAX;
+		}
 
-		rep->report = FT260_I2C_DATA_REPORT_ID(len);
+		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
 		rep->address = addr;
-		rep->length = len;
-		rep->flag = flag;
+		rep->length = wr_len;
 
-		memcpy(rep->data, &data[idx], len);
+		memcpy(rep->data, &data[idx], wr_len);
 
-		ft260_dbg("rep %#02x addr %#02x off %d len %d d[0] %#02x\n",
-			  rep->report, addr, idx, len, data[0]);
+		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
+			  rep->report, addr, idx, len, wr_len,
+			  rep->flag, data[0]);
 
 		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
-							   len + 4);
+							   wr_len + 4);
 		if (ret < 0) {
-			hid_err(hdev, "%s: failed to start transfer, ret %d\n",
-				__func__, ret);
+			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
 			return ret;
 		}
 
-		data_len -= len;
-		idx += len;
+		len -= wr_len;
+		idx += wr_len;
+		rep->flag = 0;
 
-	} while (data_len > 0);
+	} while (len > 0);
 
 	return 0;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 04/12] HID: ft260: support i2c reads greater than HID report size
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (2 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 03/12] HID: ft260: support i2c writes larger than HID report size Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 05/12] HID: ft260: improve i2c large reads performance Michael Zaidman
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman, Guillaume Champagne

A random i2c read operation in EEPROM devices is implemented as a dummy
write operation, followed by a current address read operation. The dummy
write operation is used to load the target byte or word address (a.k.a
offset) into the offset counter, from which the subsequent read operation
then reads.

To support longer than one HID report size random read, the ft260 driver
issues multiple pairs of i2c write offset + read data transactions of HID
report size so that the EEPROM device sees many i2c random read requests
from different offsets.

Two issues with the current implementation:
- This approach suffers from extra overhead caused by writing offset
  requests.
- Necessity to handle offset per HID report in big-endian representation
  as EEPROM devices expect. The current implementation does not do it and
  correctly handles the reads up to 60 bytes only.

This patch addresses both issues by implementing more efficient approach.
It issues a single i2c read request of up to the EEPROM page size and then
waits for the data to arrive in multiple HID reports. For example, to read
the 256 bytes from a 24LC512 chip, which has 128 bytes page size, the old
method performs six ft260_i2c_write_read transactions while the new - two
only.

Before:

$ sudo ./i2cperf -d 2 -o 2 -s 128 -r 0-0xff 13 0x51 -S

  Read block via i2ctransfer by chunks
  -------------------------------------------------------------------
  data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
  -------------------------------------------------------------------
  40803           85             256           2           128

Kernel log of a single 128 bytes read request:

[  +2.376308] ft260_i2c_write_read: read_off 0x0 left_len 128 len 60
[  +0.000002] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.000707] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000173] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000001] ft260_i2c_read: rep 0xc2 addr 0x51 len 60
[  +0.008660] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000156] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.000001] ft260_i2c_write_read: read_off 0x3c left_len 68 len 60
[  +0.000001] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x3c
[  +0.001034] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000191] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000001] ft260_i2c_read: rep 0xc2 addr 0x51 len 60
[  +0.008614] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000203] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.000001] ft260_i2c_write_read: read_off 0x78 left_len 8 len 8
[  +0.000001] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x78
[  +0.000987] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000192] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000001] ft260_i2c_read: rep 0xc2 addr 0x51 len 8
[  +0.002614] ft260_raw_event: i2c resp: rep 0xd1 len 8
[  +0.000200] ft260_xfer_status: bus_status 0x20, clock 100

After:

$ sudo ./i2cperf -d 2 -o 2 -s 128 -r 0-0xff 13 0x51 -S

  Read block via i2ctransfer by chunks
  -------------------------------------------------------------------
  data rate(bps)  efficiency(%)  data size(B)  total IOs   IO size(B)
  -------------------------------------------------------------------
  43990           85             256           2           128

Kernel log of a single 128 bytes read request:

[  +1.464346] ft260_i2c_write_read: off 0x0 rlen 128 wlen 2
[  +0.000002] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.001653] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000188] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000002] ft260_i2c_read: rep 0xc2 addr 0x51 len 128 rlen 60 flag 0x3
[  +0.008609] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000157] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000002] ft260_i2c_read: rep 0xc2 addr 0x51 len 68 rlen 60 flag 0x0
[  +0.008840] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000203] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000002] ft260_i2c_read: rep 0xc2 addr 0x51 len 8 rlen 8 flag 0x4
[  +0.002794] ft260_raw_event: i2c resp: rep 0xd1 len 8
[  +0.000201] ft260_xfer_status: bus_status 0x20, clock 100

Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
Tested-by: Guillaume Champagne <champagne.guillaume.c@gmail.com>
---
 drivers/hid/hid-ft260.c | 127 +++++++++++++++++++++-------------------
 1 file changed, 66 insertions(+), 61 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index cec83f69ebdc..a354089bb747 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -456,49 +456,62 @@ static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
 static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 			  u16 len, u8 flag)
 {
+	u16 rd_len;
+	int timeout, ret;
 	struct ft260_i2c_read_request_report rep;
 	struct hid_device *hdev = dev->hdev;
-	int timeout;
-	int ret;
 
-	if (len > FT260_RD_DATA_MAX) {
-		hid_err(hdev, "%s: unsupported rd len: %d\n", __func__, len);
-		return -EINVAL;
-	}
+	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
+		flag = FT260_FLAG_START_REPEATED;
+	else
+		flag = FT260_FLAG_START;
+	do {
+		if (len <= FT260_RD_DATA_MAX) {
+			rd_len = len;
+			flag |= FT260_FLAG_STOP;
+		} else {
+			rd_len = FT260_RD_DATA_MAX;
+		}
 
-	dev->read_idx = 0;
-	dev->read_buf = data;
-	dev->read_len = len;
+		dev->read_idx = 0;
+		dev->read_buf = data;
+		dev->read_len = rd_len;
 
-	rep.report = FT260_I2C_READ_REQ;
-	rep.length = cpu_to_le16(len);
-	rep.address = addr;
-	rep.flag = flag;
+		rep.report = FT260_I2C_READ_REQ;
+		rep.length = cpu_to_le16(rd_len);
+		rep.address = addr;
+		rep.flag = flag;
 
-	ft260_dbg("rep %#02x addr %#02x len %d\n", rep.report, rep.address,
-		  rep.length);
+		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
+			  rep.report, rep.address, len, rd_len, flag);
 
-	reinit_completion(&dev->wait);
+		reinit_completion(&dev->wait);
 
-	ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
-	if (ret < 0) {
-		hid_err(hdev, "%s: failed to start transaction, ret %d\n",
-			__func__, ret);
-		return ret;
-	}
+		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
+		if (ret < 0) {
+			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
+			return ret;
+		}
 
-	timeout = msecs_to_jiffies(5000);
-	if (!wait_for_completion_timeout(&dev->wait, timeout)) {
-		ft260_i2c_reset(hdev);
-		return -ETIMEDOUT;
-	}
+		timeout = msecs_to_jiffies(5000);
+		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
+			ft260_i2c_reset(hdev);
+			return -ETIMEDOUT;
+		}
 
-	ret = ft260_xfer_status(dev);
-	if (ret == 0)
-		return 0;
+		ret = ft260_xfer_status(dev);
+		if (ret < 0) {
+			ft260_i2c_reset(hdev);
+			return -EIO;
+		}
 
-	ft260_i2c_reset(hdev);
-	return -EIO;
+		len -= rd_len;
+		data += rd_len;
+		flag = 0;
+
+	} while (len > 0);
+
+	return 0;
 }
 
 /*
@@ -509,45 +522,37 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
  */
 static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
 {
-	int len, ret;
-	u16 left_len = msgs[1].len;
-	u8 *read_buf = msgs[1].buf;
+	int ret;
+	int wr_len = msgs[0].len;
+	int rd_len = msgs[1].len;
+	struct hid_device *hdev = dev->hdev;
 	u8 addr = msgs[0].addr;
 	u16 read_off = 0;
-	struct hid_device *hdev = dev->hdev;
 
-	if (msgs[0].len > 2) {
-		hid_err(hdev, "%s: unsupported wr len: %d\n", __func__,
-			msgs[0].len);
+	if (wr_len > 2) {
+		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
 		return -EOPNOTSUPP;
 	}
 
-	memcpy(&read_off, msgs[0].buf, msgs[0].len);
-
-	do {
-		if (left_len <= FT260_RD_DATA_MAX)
-			len = left_len;
+	if (ft260_debug) {
+		if (wr_len == 2)
+			read_off = be16_to_cpu(*(u16 *)msgs[0].buf);
 		else
-			len = FT260_RD_DATA_MAX;
+			read_off = *msgs[0].buf;
 
-		ft260_dbg("read_off %#x left_len %d len %d\n", read_off,
-			  left_len, len);
-
-		ret = ft260_i2c_write(dev, addr, (u8 *)&read_off, msgs[0].len,
-				      FT260_FLAG_START);
-		if (ret < 0)
-			return ret;
-
-		ret = ft260_i2c_read(dev, addr, read_buf, len,
-				     FT260_FLAG_START_STOP);
-		if (ret < 0)
-			return ret;
+		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
+			read_off, rd_len, wr_len);
+	}
 
-		left_len -= len;
-		read_buf += len;
-		read_off += len;
+	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
+			      FT260_FLAG_START);
+	if (ret < 0)
+		return ret;
 
-	} while (left_len > 0);
+	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
+			     FT260_FLAG_START_STOP_REPEATED);
+	if (ret < 0)
+		return ret;
 
 	return 0;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 05/12] HID: ft260: improve i2c large reads performance
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (3 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 04/12] HID: ft260: support i2c reads greater " Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 06/12] HID: ft260: do not populate /dev/hidraw device Michael Zaidman
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

The patch increases the read buffer size to 180 bytes. It reduces
the number of ft260_i2c_read() calls by three, improving the big
reads performance.

$ sudo i2ctransfer -y -f 13 w2@0x51 0x0 0x0 r180

Before:

[  +4.071878] ft260_i2c_write_read: off 0x0 rlen 180 wlen 2
[  +0.000005] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.001097] ft260_xfer_status: bus_status 0x41, clock 100
[  +0.000175] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000004] ft260_i2c_read: rep 0xc2 addr 0x51 len 180 rlen 60 flag 0x3
[  +0.008579] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000208] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000001] ft260_i2c_read: rep 0xc2 addr 0x51 len 120 rlen 60 flag 0x0
[  +0.008794] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000181] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000002] ft260_i2c_read: rep 0xc2 addr 0x51 len 60 rlen 60 flag 0x4
[  +0.008817] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000223] ft260_xfer_status: bus_status 0x20, clock 100

After:

[ +11.611642] ft260_i2c_write_read: off 0x0 rlen 180 wlen 2
[  +0.000005] ft260_i2c_write: rep 0xd0 addr 0x51 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.008001] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.000001] ft260_i2c_read: rep 0xc2 addr 0x51 len 180 rlen 180 flag 0x7
[  +0.008994] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.007987] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.007992] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.000206] ft260_xfer_status: bus_status 0x20, clock 100

Suggested-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index a354089bb747..91f9087e49dc 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -30,12 +30,19 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");
 
 #define FT260_REPORT_MAX_LENGTH (64)
 #define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)
+
 /*
- * The input report format assigns 62 bytes for the data payload, but ft260
- * returns 60 and 2 in two separate transactions. To minimize transfer time
- * in reading chunks mode, set the maximum read payload length to 60 bytes.
- */
-#define FT260_RD_DATA_MAX (60)
+ * The ft260 input report format defines 62 bytes for the data payload, but
+ * when requested 62 bytes, the controller returns 60 and 2 in separate input
+ * reports. To achieve better performance with the multi-report read data
+ * transfers, we set the maximum read payload length to a multiple of 60.
+ * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
+ * which is excessive; On the other hand, some higher layer drivers like at24
+ * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
+ * of I2C for potentially troublesome amounts of time, we select the maximum
+ * read payload length to be 180 bytes.
+*/
+#define FT260_RD_DATA_MAX (180)
 #define FT260_WR_DATA_MAX (60)
 
 /*
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 06/12] HID: ft260: do not populate /dev/hidraw device
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (4 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 05/12] HID: ft260: improve i2c large reads performance Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 07/12] HID: ft260: skip unexpected HID input reports Michael Zaidman
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

Do not populate the /dev/hidraw on ft260 interfaces when the hid-ft260
driver is loaded.

$ sudo insmod hid-ft260.ko
$ ls /dev/hidraw*
/dev/hidraw0

$ sudo rmmod hid-ft260.ko
$ ls /dev/hidraw*
/dev/hidraw0  /dev/hidraw1  /dev/hidraw2

Reported-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 91f9087e49dc..8d6d2a19b9ed 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -939,7 +939,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		return ret;
 	}
 
-	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	ret = hid_hw_start(hdev, 0);
 	if (ret) {
 		hid_err(hdev, "failed to start HID HW\n");
 		return ret;
@@ -966,6 +966,10 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	if (ret <= 0)
 		goto err_hid_close;
 
+	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
+		hdev->version >> 8, hdev->version & 0xff, hdev->name,
+		hdev->phys);
+
 	hid_set_drvdata(hdev, dev);
 	dev->hdev = hdev;
 	dev->adap.owner = THIS_MODULE;
@@ -974,8 +978,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	dev->adap.quirks = &ft260_i2c_quirks;
 	dev->adap.dev.parent = &hdev->dev;
 	snprintf(dev->adap.name, sizeof(dev->adap.name),
-		 "FT260 usb-i2c bridge on hidraw%d",
-		 ((struct hidraw *)hdev->hidraw)->minor);
+		 "FT260 usb-i2c bridge");
 
 	mutex_init(&dev->lock);
 	init_completion(&dev->wait);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 07/12] HID: ft260: skip unexpected HID input reports
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (5 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 06/12] HID: ft260: do not populate /dev/hidraw device Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:33 ` [PATCH v3 08/12] HID: ft260: remove SMBus Quick command support Michael Zaidman
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

The FT260 is not supposed to generate unexpected HID reports. However,
in theory, the unsolicited HID Input reports can be issued by a specially
crafted malicious USB device masquerading as FT260 when the attacker has
physical access to the USB port. In this case, the read_buf pointer points
to the final data portion of the previous I2C Read transfer, and the memcpy
invoked in the ft260_raw_event() will try copying the content of the
unexpected report into the wrong location.

This commit sets the Read buffer pointer to NULL on the I2C Read
transaction completion and checks it in the ft260_raw_event() to detect
and skip the unsolicited Input report.

Reported-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 36 ++++++++++++++++++++++++------------
 1 file changed, 24 insertions(+), 12 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 8d6d2a19b9ed..8b6ebc5228eb 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -464,7 +464,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 			  u16 len, u8 flag)
 {
 	u16 rd_len;
-	int timeout, ret;
+	int timeout, ret = 0;
 	struct ft260_i2c_read_request_report rep;
 	struct hid_device *hdev = dev->hdev;
 
@@ -480,10 +480,6 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 			rd_len = FT260_RD_DATA_MAX;
 		}
 
-		dev->read_idx = 0;
-		dev->read_buf = data;
-		dev->read_len = rd_len;
-
 		rep.report = FT260_I2C_READ_REQ;
 		rep.length = cpu_to_le16(rd_len);
 		rep.address = addr;
@@ -494,22 +490,30 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 
 		reinit_completion(&dev->wait);
 
+		dev->read_idx = 0;
+		dev->read_buf = data;
+		dev->read_len = rd_len;
+
 		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
 		if (ret < 0) {
 			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
-			return ret;
+			goto ft260_i2c_read_exit;
 		}
 
 		timeout = msecs_to_jiffies(5000);
 		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
+			ret = -ETIMEDOUT;
 			ft260_i2c_reset(hdev);
-			return -ETIMEDOUT;
+			goto ft260_i2c_read_exit;
 		}
 
+		dev->read_buf = NULL;
+
 		ret = ft260_xfer_status(dev);
 		if (ret < 0) {
+			ret = -EIO;
 			ft260_i2c_reset(hdev);
-			return -EIO;
+			goto ft260_i2c_read_exit;
 		}
 
 		len -= rd_len;
@@ -518,7 +522,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 
 	} while (len > 0);
 
-	return 0;
+ft260_i2c_read_exit:
+	dev->read_buf = NULL;
+	return ret;
 }
 
 /*
@@ -1036,6 +1042,13 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
 		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
 			  xfer->length);
 
+		if ((dev->read_buf == NULL) ||
+		    (xfer->length > dev->read_len - dev->read_idx)) {
+			hid_err(hdev, "unexpected report %#02x, length %d\n",
+				xfer->report, xfer->length);
+			return -1;
+		}
+
 		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
 		       xfer->length);
 		dev->read_idx += xfer->length;
@@ -1044,10 +1057,9 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
 			complete(&dev->wait);
 
 	} else {
-		hid_err(hdev, "unknown report: %#02x\n", xfer->report);
-		return 0;
+		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
 	}
-	return 1;
+	return 0;
 }
 
 static struct hid_driver ft260_driver = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 08/12] HID: ft260: remove SMBus Quick command support
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (6 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 07/12] HID: ft260: skip unexpected HID input reports Michael Zaidman
@ 2022-10-30 20:33 ` Michael Zaidman
  2022-10-30 20:34 ` [PATCH v3 09/12] HID: ft260: missed NACK from big i2c read Michael Zaidman
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:33 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman, Vince Asbridge, Stephen Shirron

The i2cdetect uses the SMBus Quick command by default to scan devices
on the I2C bus. The FT260 implements an I2C bus controller. The SMBus
is derived from I2C, but there are several differences between the
specifications of the two buses in the areas of timing, protocols,
operation modes, and electrical characteristics.

One of the differences is that the I2C devices allow the slave not
to ACK its slave address, but SMBus requires it to always ACK it as
a mechanism to detect a detachable device’s presence on the bus.
Since FT260 is the I2C bus controller, it does not acknowledge the
SMBus Quick write command, which sends a single bit to the device at
the place of the RD/WR bit.

The ft260 driver attempted to mimic the SMBus Quick Write functionality
by writing a single byte as the SMBus Byte Write command does.

Usually, one byte in the SMBus Quick Write will be fine. However, it may
cause problems with devices with a control register at offset 0, like
i2c muxes, for example, when scanned with the i2cdetect utility.

The i2cdetect with the "-r" option uses the SMBus Read Byte command,
which is a reasonable workaround. To prevent the I2C bus from locking
at write-only devices (most notably clock chips at address 0x69), use
the "-r" option in conjunction with scanning range parameters.

This patch removes the SMBus Quick command support.

$ sudo i2cdetect -y 13
Warning: Can't use SMBus Quick Write command, will skip some addresses
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:
10:
20:
30: -- -- -- -- -- -- -- --
40:
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60:
70:

$ sudo i2cdetect -y -r 13
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Reported-by: Vince Asbridge <VAsbridge@sanblaze.com>
Reported-by: Stephen Shirron <SShirron@sanblaze.com>
Reported-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 8b6ebc5228eb..d186aa5a8e73 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -630,14 +630,6 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 	}
 
 	switch (size) {
-	case I2C_SMBUS_QUICK:
-		if (read_write == I2C_SMBUS_READ)
-			ret = ft260_i2c_read(dev, addr, &data->byte, 0,
-					     FT260_FLAG_START_STOP);
-		else
-			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
-						FT260_FLAG_START_STOP);
-		break;
 	case I2C_SMBUS_BYTE:
 		if (read_write == I2C_SMBUS_READ)
 			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
@@ -720,7 +712,7 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 
 static u32 ft260_functionality(struct i2c_adapter *adap)
 {
-	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_QUICK |
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
 	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
 	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 09/12] HID: ft260: missed NACK from big i2c read
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (7 preceding siblings ...)
  2022-10-30 20:33 ` [PATCH v3 08/12] HID: ft260: remove SMBus Quick command support Michael Zaidman
@ 2022-10-30 20:34 ` Michael Zaidman
  2022-10-30 20:34 ` [PATCH v3 10/12] HID: ft260: wake up device from power saving mode Michael Zaidman
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:34 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

The FT260 controller does not return NACK when performing a big
read (of multiple hid reports size) from a non-existing device
or from the device responding with NACK when it is not ready
to serve the request. However, it responds correctly with NACK
to a read of up to a single hid report size.

To overcome this issue, we split the muli-report read request
into a read of a single HID report of 60 bytes size and a
multi-report read.

Big read of 256 bytes with first read of 60 bytes:

$ sudo ./i2cperf -d 2 -o 2 -s 256 -r 0-0xff 1 0x50 -S

[  +5.633280] ft260_i2c_write_read: off 0x0 rlen 255 wlen 2
[  +0.000006] ft260_i2c_write: rep 0xd0 addr 0x50 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.013205] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.000007] ft260_i2c_read: rep 0xc2 addr 0x50 len 255 rlen 60 flag 0x3
[  +0.010932] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.004733] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000006] ft260_i2c_read: rep 0xc2 addr 0x50 len 195 rlen 128 flag 0x0
[  +0.012572] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.005789] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.003189] ft260_raw_event: i2c resp: rep 0xd1 len 8
[  +0.004092] ft260_xfer_status: bus_status 0x40, clock 100
[  +0.000010] ft260_i2c_read: rep 0xc2 addr 0x50 len 67 rlen 67 flag 0x4
[  +0.011688] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.004700] ft260_raw_event: i2c resp: rep 0xd1 len 7
[  +0.004858] ft260_xfer_status: bus_status 0x20, clock 100

Read from non-existing device at address 8. The first 60 read responded
with NACK.

$ sudo ./i2cperf -d 2 -o 2 -s 256 -r 0-0xff 1 0x8 -S
[Oct19 15:37] ft260_i2c_write_read: off 0x0 rlen 255 wlen 2
[  +0.000007] ft260_i2c_write: rep 0xd0 addr 0x8 off 0 len 2 wlen 2 flag 0x2 d[0] 0x0
[  +0.022820] ft260_xfer_status: bus_status 0x20, clock 100
[  +0.000007] ft260_i2c_read: rep 0xc2 addr 0x8 len 255 rlen 60 flag 0x3
[  +0.010658] ft260_raw_event: i2c resp: rep 0xde len 60
[  +0.005965] ft260_xfer_status: bus_status 0x46, clock 100  <-- NACK
[  +0.000009] ft260 0003:0403:6030.0004: i2c bus error: 0x46
[  +0.007784] ft260_i2c_reset: done

Co-developed-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index d186aa5a8e73..40fae81386e3 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -464,6 +464,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 			  u16 len, u8 flag)
 {
 	u16 rd_len;
+	u16 rd_data_max = 60;
 	int timeout, ret = 0;
 	struct ft260_i2c_read_request_report rep;
 	struct hid_device *hdev = dev->hdev;
@@ -473,12 +474,13 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 	else
 		flag = FT260_FLAG_START;
 	do {
-		if (len <= FT260_RD_DATA_MAX) {
+		if (len <= rd_data_max) {
 			rd_len = len;
 			flag |= FT260_FLAG_STOP;
 		} else {
-			rd_len = FT260_RD_DATA_MAX;
+			rd_len = rd_data_max;
 		}
+		rd_data_max = FT260_RD_DATA_MAX;
 
 		rep.report = FT260_I2C_READ_REQ;
 		rep.length = cpu_to_le16(rd_len);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 10/12] HID: ft260: wake up device from power saving mode
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (8 preceding siblings ...)
  2022-10-30 20:34 ` [PATCH v3 09/12] HID: ft260: missed NACK from big i2c read Michael Zaidman
@ 2022-10-30 20:34 ` Michael Zaidman
  2022-10-31 17:12   ` Enrik Berkhan
  2022-10-30 20:34 ` [PATCH v3 11/12] HID: ft260: fix a NULL pointer dereference in ft260_i2c_write Michael Zaidman
  2022-10-30 20:34 ` [PATCH v3 12/12] HID: ft260: missed NACK from busy device Michael Zaidman
  11 siblings, 1 reply; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:34 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

The FT260 can enter a power saving mode after being idle for longer than
5 seconds.

When being woken up from power saving mode by an I2C write request, it
looks like a possible NACK will not be correctly reported back. As a
workaround, the driver will request an I2C status report before starting
the next I2C transfer if the FT260 has been idle for longer than 4800ms.

Signed-off-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 40fae81386e3..00cbe7693ba0 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -31,6 +31,8 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");
 #define FT260_REPORT_MAX_LENGTH (64)
 #define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)
 
+#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */
+
 /*
  * The ft260 input report format defines 62 bytes for the data payload, but
  * when requested 62 bytes, the controller returns 60 and 2 in separate input
@@ -237,6 +239,7 @@ struct ft260_device {
 	struct completion wait;
 	struct mutex lock;
 	u8 write_buf[FT260_REPORT_MAX_LENGTH];
+	unsigned long need_wakeup_at;
 	u8 *read_buf;
 	u16 read_idx;
 	u16 read_len;
@@ -392,6 +395,12 @@ static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
 	struct ft260_i2c_write_request_report *rep =
 		(struct ft260_i2c_write_request_report *)dev->write_buf;
 
+
+	if (time_is_before_jiffies(dev->need_wakeup_at)) {
+		(void)ft260_xfer_status(dev);
+		ft260_dbg("device wakeup");
+	}
+
 	rep->flag = FT260_FLAG_START;
 
 	do {
@@ -441,6 +450,11 @@ static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
 	if (data_len >= sizeof(rep->data))
 		return -EINVAL;
 
+	if (time_is_before_jiffies(dev->need_wakeup_at)) {
+		(void)ft260_xfer_status(dev);
+		ft260_dbg("device wakeup");
+	}
+
 	rep->address = addr;
 	rep->data[0] = cmd;
 	rep->length = data_len + 1;
@@ -607,6 +621,8 @@ static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
 
 	ret = num;
 i2c_exit:
+	dev->need_wakeup_at =
+		jiffies + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
 	hid_hw_power(hdev, PM_HINT_NORMAL);
 	mutex_unlock(&dev->lock);
 	return ret;
@@ -707,6 +723,8 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 	}
 
 smbus_exit:
+	dev->need_wakeup_at =
+		jiffies + msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
 	hid_hw_power(hdev, PM_HINT_NORMAL);
 	mutex_unlock(&dev->lock);
 	return ret;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 11/12] HID: ft260: fix a NULL pointer dereference in ft260_i2c_write
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (9 preceding siblings ...)
  2022-10-30 20:34 ` [PATCH v3 10/12] HID: ft260: wake up device from power saving mode Michael Zaidman
@ 2022-10-30 20:34 ` Michael Zaidman
  2022-10-30 20:34 ` [PATCH v3 12/12] HID: ft260: missed NACK from busy device Michael Zaidman
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:34 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

The len=0 passed into the ft260_i2c_write() triggered the NULL
pointer dereference in the debug message on access to data[0].
Since a Write without data makes little sense in this context,
do not allow it.

Before:

$ sudo i2ctransfer -y 13 w0@0x51
Killed

After:

$ sudo i2ctransfer -y 13 w0@0x51
Error: Sending messages failed: Invalid argument

Reported-by: Enrik Berkhan <Enrik.Berkhan@inka.de>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
 drivers/hid/hid-ft260.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 00cbe7693ba0..b3f715f6ea86 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -395,6 +395,8 @@ static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
 	struct ft260_i2c_write_request_report *rep =
 		(struct ft260_i2c_write_request_report *)dev->write_buf;
 
+	if (len < 1)
+		return -EINVAL;
 
 	if (time_is_before_jiffies(dev->need_wakeup_at)) {
 		(void)ft260_xfer_status(dev);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v3 12/12] HID: ft260: missed NACK from busy device
  2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
                   ` (10 preceding siblings ...)
  2022-10-30 20:34 ` [PATCH v3 11/12] HID: ft260: fix a NULL pointer dereference in ft260_i2c_write Michael Zaidman
@ 2022-10-30 20:34 ` Michael Zaidman
  11 siblings, 0 replies; 14+ messages in thread
From: Michael Zaidman @ 2022-10-30 20:34 UTC (permalink / raw)
  To: jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert,
	Enrik.Berkhan, Michael Zaidman

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>
---
 drivers/hid/hid-ft260.c | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index b3f715f6ea86..da8ea0d16059 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;
@@ -320,7 +320,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;
 
 	/*
@@ -355,8 +355,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) {
@@ -374,8 +377,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);
@@ -399,7 +412,7 @@ static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
 		return -EINVAL;
 
 	if (time_is_before_jiffies(dev->need_wakeup_at)) {
-		(void)ft260_xfer_status(dev);
+		(void)ft260_xfer_status(dev, 0);
 		ft260_dbg("device wakeup");
 	}
 
@@ -453,7 +466,7 @@ static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
 		return -EINVAL;
 
 	if (time_is_before_jiffies(dev->need_wakeup_at)) {
-		(void)ft260_xfer_status(dev);
+		(void)ft260_xfer_status(dev, 0);
 		ft260_dbg("device wakeup");
 	}
 
@@ -484,6 +497,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
 	int timeout, ret = 0;
 	struct ft260_i2c_read_request_report rep;
 	struct hid_device *hdev = dev->hdev;
+	u8 bus_busy = 0;
 
 	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
 		flag = FT260_FLAG_START_REPEATED;
@@ -527,7 +541,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);
@@ -1003,7 +1020,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);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 10/12] HID: ft260: wake up device from power saving mode
  2022-10-30 20:34 ` [PATCH v3 10/12] HID: ft260: wake up device from power saving mode Michael Zaidman
@ 2022-10-31 17:12   ` Enrik Berkhan
  0 siblings, 0 replies; 14+ messages in thread
From: Enrik Berkhan @ 2022-10-31 17:12 UTC (permalink / raw)
  To: Michael Zaidman, jikos
  Cc: linux-kernel, linux-input, linux-i2c, germain.hebert

Hi Michael,

On Sun, 2022-10-30 at 22:34 +0200, Michael Zaidman wrote:
> The FT260 can enter a power saving mode after being idle for longer than
> 5 seconds.
> 
> When being woken up from power saving mode by an I2C write request, it
> looks like a possible NACK will not be correctly reported back. As a
> workaround, the driver will request an I2C status report before starting
> the next I2C transfer if the FT260 has been idle for longer than 4800ms.

Unfortunately, I have found issues with this patch as it is. When I
tested from an installation running in KVM, I saw missed NACKs again.

A possible fix seems to be to send the extra status request after the
output report triggering the write. See
https://github.com/MichaelZaidman/hid-ft260/pull/7.

Cheers,
Enrik

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2022-10-31 17:12 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-10-30 20:33 [PATCH v3 00/12] HID: ft260: fixes and performance improvements Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 01/12] HID: ft260: ft260_xfer_status routine cleanup Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 02/12] HID: ft260: improve i2c write performance Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 03/12] HID: ft260: support i2c writes larger than HID report size Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 04/12] HID: ft260: support i2c reads greater " Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 05/12] HID: ft260: improve i2c large reads performance Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 06/12] HID: ft260: do not populate /dev/hidraw device Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 07/12] HID: ft260: skip unexpected HID input reports Michael Zaidman
2022-10-30 20:33 ` [PATCH v3 08/12] HID: ft260: remove SMBus Quick command support Michael Zaidman
2022-10-30 20:34 ` [PATCH v3 09/12] HID: ft260: missed NACK from big i2c read Michael Zaidman
2022-10-30 20:34 ` [PATCH v3 10/12] HID: ft260: wake up device from power saving mode Michael Zaidman
2022-10-31 17:12   ` Enrik Berkhan
2022-10-30 20:34 ` [PATCH v3 11/12] HID: ft260: fix a NULL pointer dereference in ft260_i2c_write Michael Zaidman
2022-10-30 20:34 ` [PATCH v3 12/12] HID: ft260: missed NACK from busy device Michael Zaidman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).