[PATCH v3 1/3] i2c: xiic: preserve PEC byte length in SMBus block read setup
Abdurrahman Hussain
abdurrahman at nexthop.ai
Wed May 13 03:09:22 PDT 2026
xiic_smbus_block_read_setup() recalculates i2c->rx_msg->len based on the
length byte returned by the device, but historically clobbered the PEC
byte expectation the SMBus core had baked into msg->len. That dropped
the PEC byte from the caller's buffer on the normal and chunked
receive-fifo branches.
Compute pec_len up-front as (i2c->rx_msg->len - 1) and add it to the
new length in every branch:
- chunked (rxmsg_len + pec_len > IIC_RX_FIFO_DEPTH): set the drain
target to rxmsg_len + 1 + pec_len.
- deferred (small enough to drain in one fill but >= MIN_LEN total):
same.
- padded (1 + rxmsg_len + pec_len < SMBUS_BLOCK_READ_MIN_LEN): the
hardware needs at least 3 bytes on the bus to exit the read
cleanly (the second byte is already being clocked in by the time
the ISR reads the length byte and is too late to NACK), so we
still pad rx_msg->len up to SMBUS_BLOCK_READ_MIN_LEN. The dummy
trailing byte that gets drained must then be trimmed off before
handing the message back to the SMBus core; otherwise
i2c_smbus_check_pec() reads buf[len-1] (= dummy) instead of the
real PEC byte at buf[1] and rejects every clean zero-length block
read with -EBADMSG.
Record the true valid byte count in a new field
i2c->smbus_actual_len and have xiic_process()'s RX_FULL completion
site trim rx_msg->len down to it before clearing rx_msg.
Widen the branch condition from the old "(rxmsg_len == 1) ||
(rxmsg_len == 0)" to "(1 + rxmsg_len + pec_len) < MIN_LEN" so that
user requests with multi-byte trailing bytes (e.g. pec_len == 2 on
a zero-length block) flow through the deferred branch instead of
getting truncated to MIN_LEN here.
Signed-off-by: Abdurrahman Hussain <abdurrahman at nexthop.ai>
---
drivers/i2c/busses/i2c-xiic.c | 40 +++++++++++++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 7 deletions(-)
diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c
index 3e7735e1dae0..d4308716d461 100644
--- a/drivers/i2c/busses/i2c-xiic.c
+++ b/drivers/i2c/busses/i2c-xiic.c
@@ -73,6 +73,11 @@ enum i2c_scl_freq {
* @prev_msg_tx: Previous message is Tx
* @quirks: To hold platform specific bug info
* @smbus_block_read: Flag to handle block read
+ * @smbus_actual_len: For SMBus block reads padded to SMBUS_BLOCK_READ_MIN_LEN,
+ * the number of bytes that are actually valid (length byte + payload +
+ * optional PEC). msg->len gets trimmed to this on transfer completion so
+ * the SMBus core sees the real PEC byte and not the trailing dummy.
+ * Zero when no trimming is needed.
* @input_clk: Input clock to I2C controller
* @i2c_clk: I2C SCL frequency
* @atomic: Mode of transfer
@@ -98,6 +103,7 @@ struct xiic_i2c {
bool prev_msg_tx;
u32 quirks;
bool smbus_block_read;
+ unsigned int smbus_actual_len;
unsigned long input_clk;
unsigned int i2c_clk;
bool atomic;
@@ -539,6 +545,8 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c)
/* Check if received length is valid */
if (rxmsg_len <= I2C_SMBUS_BLOCK_MAX) {
+ unsigned int pec_len = i2c->rx_msg->len - 1;
+
/* Set Receive fifo depth */
if (rxmsg_len > IIC_RX_FIFO_DEPTH) {
/*
@@ -546,23 +554,30 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c)
* Receive fifo depth should set to Rx fifo capacity minus 1
*/
rfd_set = IIC_RX_FIFO_DEPTH - 1;
- i2c->rx_msg->len = rxmsg_len + 1;
- } else if ((rxmsg_len == 1) ||
- (rxmsg_len == 0)) {
+ i2c->rx_msg->len = rxmsg_len + 1 + pec_len;
+ } else if (1 + rxmsg_len + pec_len < SMBUS_BLOCK_READ_MIN_LEN) {
/*
- * Minimum of 3 bytes required to exit cleanly. 1 byte
- * already received, Second byte is being received. Have
- * to set NACK in read_rx before receiving the last byte
+ * Hardware requires at least SMBUS_BLOCK_READ_MIN_LEN
+ * bytes on the bus to exit the read cleanly: by the
+ * time the ISR pulls the length byte from the FIFO,
+ * the second byte is already being clocked in and
+ * cannot be NACKed in time. Pad the drain target so
+ * the HW reaches the minimum, but remember the true
+ * valid byte count and trim msg->len back on transfer
+ * completion -- otherwise the SMBus core's PEC check
+ * reads the trailing dummy byte instead of the real
+ * PEC byte and rejects clean transfers with -EBADMSG.
*/
rfd_set = 0;
i2c->rx_msg->len = SMBUS_BLOCK_READ_MIN_LEN;
+ i2c->smbus_actual_len = 1 + rxmsg_len + pec_len;
} else {
/*
* When Rx msg len less than Rx fifo capacity
* Receive fifo depth should set to Rx msg len minus 2
*/
rfd_set = rxmsg_len - 2;
- i2c->rx_msg->len = rxmsg_len + 1;
+ i2c->rx_msg->len = rxmsg_len + 1 + pec_len;
}
xiic_setreg8(i2c, XIIC_RFD_REG_OFFSET, rfd_set);
@@ -797,6 +812,17 @@ static irqreturn_t xiic_process(int irq, void *dev_id)
xiic_read_rx(i2c);
if (xiic_rx_space(i2c) == 0) {
+ /*
+ * If the setup path padded a short SMBus block read up
+ * to SMBUS_BLOCK_READ_MIN_LEN for the HW exit
+ * workaround, trim rx_msg->len back to the number of
+ * bytes that are actually valid so the SMBus core's
+ * PEC check reads the right index. Must happen before
+ * the rx_msg = NULL below.
+ */
+ if (i2c->rx_msg && i2c->smbus_actual_len)
+ i2c->rx_msg->len = i2c->smbus_actual_len;
+
/* this is the last part of the message */
i2c->rx_msg = NULL;
--
2.53.0
More information about the linux-arm-kernel
mailing list