All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Ryan Chen" <ryan_chen@aspeedtech.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org
Subject: Re: [PATCH RESEND v29 3/4] i2c: ast2600: Add controller driver for AST2600 new register set
Date: Thu, 14 May 2026 02:47:35 +0000	[thread overview]
Message-ID: <20260514024735.E9196C19425@smtp.kernel.org> (raw)
In-Reply-To: <20260513-upstream_i2c-v29-3-fe9926964d55@aspeedtech.com>

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] Zero-byte transfers cause out-of-bounds memory accesses and protocol violations.
- [Critical] Interrupt handler fails to clear status bits in a write-1-to-clear register, causing IRQ storms.
- [High] Hardware is disabled in `remove()` before the I2C adapter is unregistered.
- [High] Missing synchronization between the timeout path and the interrupt handler causes a Use-After-Free.
- [High] Dynamic buffer size calculation conflicts with hardcoded RX offset, causing memory corruption.
- [High] Switch statement on bitmask register drops interrupts and causes timeouts.
- [High] Arithmetic underflow in AC timing configuration corrupts the timing register.
- [Medium] Hardware error interrupts are checked in ISR but never unmasked in the Interrupt Enable Register.
- [Medium] Zero-length SMBus block reads corrupt the length byte and trigger redundant reads.
- [Medium] The I2C timeout device tree property is silently truncated due to missing clamping.
--

commit 806e76a70ecd3287089b3f21eee416bd2f1e583e
Author: Ryan Chen <ryan_chen@aspeedtech.com>
Subject: i2c: ast2600: Add controller driver for AST2600 new register set

This commit adds a new I2C controller driver for the AST2600 SoC to support
its updated register layout and packet-based transfers. The driver is selected
at probe time based on the presence of the aspeed,global-regs property.

> diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2600.c
> new file mode 100644
> index 0000000000000..787ef6bd62442
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-ast2600.c
> @@ -0,0 +1,808 @@

[ ... ]

> +static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus)
> +{
[ ... ]
> +	baseclk_idx = min(baseclk_idx, 15);
> +	divisor = min(divisor, 32);
> +	scl_low = min(divisor * 9 / 16 - 1, 15);
> +	scl_high = (divisor - scl_low - 2) & GENMASK(3, 0);

If divisor evaluates to 1 or 0, divisor * 9 / 16 - 1 becomes -1.

Since -1 is compared against 15 using min(), it returns -1. When this is
assigned to the u32 scl_low variable, it underflows to 0xFFFFFFFF.

Could this completely overwrite bits 12 through 31 of the
AST2600_I2CC_AC_TIMING register when left-shifted and combined into data?

> +	data = (scl_high - 1) << 20 | scl_high << 16 | scl_low << 12 | baseclk_idx;
> +	if (i2c_bus->timeout) {
> +		data |= AST2600_I2CC_TOUTBASECLK(AST_I2C_TIMEOUT_CLK);
> +		data |= AST2600_I2CC_TTIMEOUT(i2c_bus->timeout);
> +	}

The AST2600_I2CC_TTIMEOUT macro applies a bitwise mask of GENMASK(4, 0).

If a large timeout is configured in the device tree, will this silently
truncate the value instead of clamping it?

[ ... ]

> +static int ast2600_i2c_setup_buff_tx(u32 cmd, struct ast2600_i2c_bus *i2c_bus)
> +{
[ ... ]
> +	if (xfer_len) {
> +		cmd |= AST2600_I2CM_TX_BUFF_EN | AST2600_I2CM_TX_CMD;
[ ... ]
> +		writel(AST2600_I2CC_SET_TX_BUF_LEN(xfer_len),
> +		       i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL);
> +	}
> +
> +	writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS);
> +
> +	return 0;
> +}

For a 0-byte write, this skips setting the TX_BUF_LEN register, leaving a
stale length in the hardware.

When the TX_ACK interrupt occurs, it will read this stale length:

ast2600_i2c_controller_packet_irq() {
    ...
    case AST2600_I2CM_TX_ACK:
        xfer_len = AST2600_I2CC_GET_TX_BUF_LEN(...);
        i2c_bus->controller_xfer_cnt += xfer_len;
    ...
}

This causes i2c_bus->controller_xfer_cnt to exceed msg->len. On the
next setup call, xfer_len becomes negative, and the unsigned comparison
promotes it to a large value, forcing it to i2c_bus->buf_size.

Could this transmit arbitrary kernel memory out-of-bounds to the I2C bus?

> +static int ast2600_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus *i2c_bus)
> +{
> +	struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index];
> +	int xfer_len = msg->len - i2c_bus->controller_xfer_cnt;
> +
> +	cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_BUFF_EN | AST2600_I2CM_RX_CMD;
> +
> +	if (cmd & AST2600_I2CM_START_CMD)
> +		cmd |= AST2600_I2CM_PKT_ADDR(msg->addr);
> +
> +	if (msg->flags & I2C_M_RECV_LEN) {
> +		dev_dbg(i2c_bus->dev, "smbus read\n");
> +		xfer_len = 1;
> +	} else if (xfer_len > i2c_bus->buf_size) {
> +		xfer_len = i2c_bus->buf_size;
> +	} else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) {
> +		cmd |= CONTROLLER_TRIGGER_LAST_STOP;
> +	}
> +	writel(AST2600_I2CC_SET_RX_BUF_LEN(xfer_len), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL);

If xfer_len is 0, the AST2600_I2CC_SET_RX_BUF_LEN(0) macro calculates
((0 - 1) & 0x1F) << 16.

Does this command the hardware to read 32 bytes instead of 0, which would then
cause the interrupt handler to loop 32 times writing to msg->buf and overflow
the buffer?

[ ... ]

> +static void ast2600_i2c_controller_packet_irq(struct ast2600_i2c_bus *i2c_bus, u32 sts)
> +{
> +	struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index];
> +	int xfer_len;
> +	int i;
> +
> +	sts &= ~AST2600_I2CM_PKT_DONE;
> +	writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR);
> +	switch (sts) {

Is it safe to use a switch statement with exact-match case labels for a
register that can assert multiple independent bit flags simultaneously?

If the hardware reports a bit combination not explicitly listed here, it falls
through to the default case without calling complete(), which will cause the
active transfer to hang until it times out.

[ ... ]

> +	case AST2600_I2CM_RX_DONE:
> +	case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP:
[ ... ]
> +		if (msg->flags & I2C_M_RECV_LEN) {
> +			u8 recv_len = AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base
> +						       + AST2600_I2CC_STS_AND_BUFF));
> +			msg->len = min_t(unsigned int, recv_len, I2C_SMBUS_BLOCK_MAX);
> +			msg->len += ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1);
> +			msg->flags &= ~I2C_M_RECV_LEN;
> +			if (!recv_len)
> +				i2c_bus->controller_xfer_cnt = 0;
> +			else
> +				i2c_bus->controller_xfer_cnt = 1;

If recv_len is 0, i2c_bus->controller_xfer_cnt is set to 0 but msg->len
becomes 1.

Will this mismatch cause the end-of-transfer logic to incorrectly invoke
ast2600_i2c_setup_buff_rx() to read an additional byte, which then overwrites
msg->buf[0] and corrupts the legitimate 0-length indicator?

[ ... ]

> +static int ast2600_i2c_controller_irq(struct ast2600_i2c_bus *i2c_bus)
> +{
> +	u32 sts = readl(i2c_bus->reg_base + AST2600_I2CM_ISR);

This function selectively clears PKT_DONE, BUS_RECOVER_FAIL, and BUS_RECOVER.

Since the AST2600 I2CM_ISR is a write-1-to-clear register, shouldn't it also
write back and clear the other active status bits like TX_ACK, RX_DONE,
NORMAL_STOP, PKT_ERROR, and ARBIT_LOSS?

Leaving these bits asserted in the hardware register could cause the interrupt
handler to execute continuously in an IRQ storm.

[ ... ]

> +static int ast2600_i2c_controller_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
[ ... ]
> +	timeout = wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->adap.timeout);
> +	if (timeout == 0) {
[ ... ]
> +		ret = -ETIMEDOUT;
> +	} else {
> +		ret = i2c_bus->cmd_err;
> +	}
> +
> +	dev_dbg(i2c_bus->dev, "bus%d-m: %d end\n", i2c_bus->adap.nr, i2c_bus->cmd_err);
> +
> +controller_out:
> +	return ret;
> +}

When wait_for_completion_timeout expires, this function returns -ETIMEDOUT
without synchronizing with the interrupt handler.

If a delayed or concurrent interrupt fires and the caller has already freed
or reused the msgs array, could ast2600_i2c_controller_packet_irq
dereference the invalid array or write to msg->buf, leading to a
use-after-free?

[ ... ]

> +static int ast2600_i2c_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	buf_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);
> +	if (IS_ERR(buf_base))
> +		return dev_err_probe(dev, PTR_ERR(buf_base), "Missing buffer resource\n");
> +	i2c_bus->buf_base = buf_base;
> +	i2c_bus->buf_size = resource_size(res) / 2;

This calculates buf_size dynamically. For transmission, the driver writes
up to buf_size starting at buf_base + 0.

However, for reception, the read offset is hardcoded to 0x10:

ast2600_i2c_controller_packet_irq() {
    ...
    case AST2600_I2CM_RX_DONE:
        for (i = 0; i < xfer_len; i++)
            msg->buf[...] = readb(i2c_bus->buf_base + 0x10 + i);
    ...
}

If the Device Tree provides a resource larger than 32 bytes, a transmission
might write past offset 0x0F. Does this completely overwrite the RX buffer
expected at 0x10?

[ ... ]

> +	ret = devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0,
> +			       dev_name(dev), i2c_bus);
> +	if (ret < 0) {
> +		ret = dev_err_probe(dev, ret, "Unable to request irq %d\n",
> +				    i2c_bus->irq);
> +		goto err;
> +	}
> +
> +	writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER,
> +	       i2c_bus->reg_base + AST2600_I2CM_IER);

This writes only PKT_DONE and BUS_RECOVER to the Interrupt Enable Register.

Since error interrupts like BUS_RECOVER_FAIL, SCL_LOW_TO, ABNORMAL, and
ARBIT_LOSS are not enabled, will the controller silently hang until the
1-second software timeout expires when these hardware errors occur?

> +
> +	ret = devm_i2c_add_adapter(dev, &i2c_bus->adap);
> +	if (ret)
> +		goto err;
> +
> +	return 0;

[ ... ]

> +static void ast2600_i2c_remove(struct platform_device *pdev)
> +{
> +	struct ast2600_i2c_bus *i2c_bus = platform_get_drvdata(pdev);
> +
> +	/* Disable everything. */
> +	writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> +	writel(0, i2c_bus->reg_base + AST2600_I2CM_IER);
> +}

By using devm_i2c_add_adapter(), the adapter unregistration is deferred
until the devres cleanup phase, which runs after ast2600_i2c_remove()
returns.

Because remove() completely disables the hardware by writing 0 to the
FUN_CTRL and IER registers, if any I2C client device attempts to communicate
during its own remove() callback (triggered during i2c_del_adapter()),
will the transaction fail or hang because the underlying controller is
already turned off?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260513-upstream_i2c-v29-0-fe9926964d55@aspeedtech.com?part=3

  reply	other threads:[~2026-05-14  2:47 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-13  5:31 [PATCH RESEND v29 0/4] Add ASPEED AST2600 I2C controller driver Ryan Chen
2026-05-13  5:32 ` [PATCH RESEND v29 1/4] dt-bindings: i2c: Split AST2600 binding into a new YAML Ryan Chen
2026-05-13 17:05   ` Rob Herring (Arm)
2026-05-14  1:52   ` sashiko-bot
2026-05-13  5:32 ` [PATCH RESEND v29 2/4] dt-bindings: i2c: ast2600-i2c.yaml: Add global-regs properties Ryan Chen
2026-05-13 17:05   ` Rob Herring (Arm)
2026-05-13  5:32 ` [PATCH RESEND v29 3/4] i2c: ast2600: Add controller driver for AST2600 new register set Ryan Chen
2026-05-14  2:47   ` sashiko-bot [this message]
2026-05-13  5:32 ` [PATCH RESEND v29 4/4] i2c: ast2600: Add target mode support Ryan Chen
2026-05-14  3:22   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260514024735.E9196C19425@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=robh@kernel.org \
    --cc=ryan_chen@aspeedtech.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.