From: sashiko-bot@kernel.org
To: "Mukesh Kumar Savaliya" <mukesh.savaliya@oss.qualcomm.com>,
linux-i3c@lists.infradead.org, devicetree@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org, Frank.Li@kernel.org,
"Alexandre Belloni" <alexandre.belloni@bootlin.com>
Subject: Re: [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver
Date: Wed, 04 Nov 2026 11:43:51 +0000 [thread overview]
Message-ID: <sashiko-outbox-197205@kernel.org> (raw)
In-Reply-To: <20261104111610.3739095-2-mukesh.savaliya@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] i3c: qcom-geni: use_7e updated after m_param in geni_i3c_master_priv_xfers()
- [High] i3c: qcom-geni: broken I3C_ADDR_MASK and DAA handling in geni_i3c_perform_daa()
- [High] i3c: qcom-geni: invalid clock lookup and 1 MHz cycle count in geni_i3c_clk_map
- [High] i3c: qcom-geni: unsafe I2C DMA mapping and broken DMA reset handling
- [High] i3c: qcom-geni: broken short RX FIFO reads and early termination in geni_i3c_irq()
- [High] i3c: qcom-geni: NACK error overwrite and status bitmask mismatch in geni_i3c_handle_err()
- [High] i3c: qcom-geni: missing reinit_completion() and TX_RESET_DONE in _i3c_geni_execute_command()
- [High] i3c: qcom-geni: TX watermark race and unhandled IRQ storm in geni_i3c_irq()
- [Medium] i3c: qcom-geni: double geni_se_resources_off() call in geni_i3c_probe()
- [Low] i3c: qcom-geni: unused variable m_stat_mask in geni_i3c_irq()
--
commit 2ad15e60246cd7898205f431d98abb7e053d2d97
Author: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
i3c: master: Add Qualcomm I3C controller driver
This commit adds a driver for the Qualcomm QUPv3 GENI-based I3C master
controller supporting I3C SDR and legacy I2C transfers.
> diff --git a/drivers/i3c/master/i3c-qcom-geni.c b/drivers/i3c/master/i3c-qcom-geni.c
> new file mode 100644
> index 0000000000000..2fee77e681e26
> --- /dev/null
> +++ b/drivers/i3c/master/i3c-qcom-geni.c
[ ... ]
> +#define I3C_AUTO_SUSPEND_DELAY 250
> +#define PACKING_BYTES_PER_WORD 4
> +#define XFER_TIMEOUT 250
> +
> +#define I3C_ADDR_MASK (I2C_MAX_ADDR >> 1)
[ ... ]
> +struct geni_i3c_dev {
[ ... ]
> + u8 *cur_buf;
> + u8 last_addr;
> + bool cur_is_write;
> + int cur_len;
> + int cur_idx;
> + DECLARE_BITMAP(newaddrslots, 64);
> +
> + const struct geni_i3c_clk_settings *clk_cfg;
> + const struct geni_i3c_clk_settings *clk_od_cfg;
> +};
[Severity: High]
Since I2C_MAX_ADDR is defined as GENMASK(6, 0) (0x7f) in
include/linux/i3c/master.h, shifting it right by 1 makes I3C_ADDR_MASK 0x3f
(6 bits instead of 7 bits), and newaddrslots is only 64 bits wide instead of
I3C_MAX_ADDR + 1 (128).
Later in geni_i3c_perform_daa():
addr = i3c_master_get_free_addr(&gi3c->ctrlr,
gi3c->last_addr + 1);
if (addr > I3C_ADDR_MASK)
return -EINVAL;
gi3c->last_addr = addr;
set_bit(addr, gi3c->newaddrslots);
...
ret = i3c_geni_execute_write_command(gi3c, &xfer, tx_buf, 1);
if (ret)
break;
Doesn't this reject valid 7-bit dynamic addresses from 0x40 to 0x7d (as well
as truncating negative error codes from i3c_master_get_free_addr() into u8)?
Also, in geni_i3c_perform_daa() and geni_i3c_master_send_ccc_cmd():
- gi3c->last_addr is never reset between DAA runs, so repeated DAA
invocations will only search above the previous address until the range is
exhausted.
- set_bit(addr, gi3c->newaddrslots) is called before
i3c_geni_execute_write_command() sends the dynamic address to the target.
If that write fails, geni_i3c_perform_daa() breaks out and returns 0 with
addr still set in newaddrslots, causing geni_i3c_master_do_daa() to call
i3c_master_add_i3c_dev_locked() for an unassigned address.
- geni_i3c_master_send_ccc_cmd() ignores the return value of
geni_i3c_perform_daa(gi3c).
Should I3C_ADDR_MASK be I3C_MAX_ADDR (0x7f) with newaddrslots sized to
I3C_MAX_ADDR + 1, and should set_bit(addr, gi3c->newaddrslots) happen only
after the address write succeeds?
[ ... ]
> +static const struct geni_i3c_clk_settings geni_i3c_clk_map[] = {
> + {
> + .clk_freq_out = 100 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 76,
> + .i2c_t_low_cnt = 90,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 8,
> + .i2c_t_cycle_cnt = 192,
> + },
> + {
> + .clk_freq_out = 400 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 12,
> + .i2c_t_low_cnt = 24,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 8,
> + .i2c_t_cycle_cnt = 48
> + },
> + {
> + .clk_freq_out = 1000 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 4,
> + .i2c_t_low_cnt = 9,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 0,
> + .i2c_t_cycle_cnt = 19
> + },
> + {
> + .clk_freq_out = 12500 * HZ_PER_KHZ,
> + .clk_src_freq = 100000 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 45,
> + .i2c_t_low_cnt = 63,
> + .i3c_t_high_cnt = 6,
> + .i3c_t_cycle_cnt = 7,
> + .i2c_t_cycle_cnt = 110
> + }
> +};
> +
> +static int geni_i3c_clk_map_idx(struct geni_i3c_dev *gi3c)
> +{
> + const struct geni_i3c_clk_settings *clk_table_idx = geni_i3c_clk_map;
> + struct i3c_master_controller *m = &gi3c->ctrlr;
> + struct i3c_bus *bus = i3c_master_get_bus(m);
> +
> + for (int i = 0; i < ARRAY_SIZE(geni_i3c_clk_map); i++, clk_table_idx++) {
> + if (clk_table_idx->clk_freq_out == bus->scl_rate.i3c &&
> + clk_table_idx->clk_src_freq == gi3c->clk_src_freq)
> + gi3c->clk_cfg = clk_table_idx;
> +
> + if (clk_table_idx->clk_freq_out == bus->scl_rate.i2c)
> + gi3c->clk_od_cfg = clk_table_idx;
> + }
> +
> + if (!gi3c->clk_cfg || !gi3c->clk_od_cfg)
> + return -EINVAL;
> +
> + return 0;
> +}
[Severity: High]
How does geni_i3c_clk_map_idx() behave on a pure I3C bus or a mixed-slow
bus?
In i3c_bus_set_mode(), when bus->mode is I3C_BUS_MODE_PURE and no i2c-scl-hz
property is set in devicetree, bus->scl_rate.i2c remains 0. Because no entry
in geni_i3c_clk_map[] has clk_freq_out == 0, gi3c->clk_od_cfg stays NULL and
geni_i3c_master_bus_init() fails with -EINVAL.
Conversely, in I3C_BUS_MODE_MIXED_SLOW (where i3c_bus_set_mode() clamps
bus->scl_rate.i3c to bus->scl_rate.i2c), i3c_geni_resources_init() hardcodes
gi3c->clk_src_freq to 100 MHz while the 100 kHz, 400 kHz, and 1000 kHz
entries in geni_i3c_clk_map[] all have clk_src_freq set to 19.2 MHz, so
gi3c->clk_cfg stays NULL and geni_i3c_clk_map_idx() also returns -EINVAL.
Also, in the 1000 kHz entry of geni_i3c_clk_map[], i3c_t_cycle_cnt is 0 while
i3c_t_high_cnt is 7 (whereas the 100 kHz and 400 kHz entries use
i3c_t_cycle_cnt = 8). Is programming SE_I3C_SCL_CYCLE to 0 when
qcom_geni_i3c_conf(gi3c, OPEN_DRAIN_MODE) selects the 1000 kHz entry
intentional?
[ ... ]
> +static void geni_i3c_handle_err(struct geni_i3c_dev *gi3c, u32 status)
> +{
> + dev_dbg(gi3c->se.dev, "len:%d, %s\n",
> + gi3c->cur_len, gi3c->cur_is_write ? "write" : "read");
> +
> + if (status & M_GP_IRQ_0_EN) {
> + gi3c->err = -EINVAL;
> + dev_dbg(gi3c->se.dev, "I3C slave early read termination\n");
> + }
[ ... ]
> + if (status & M_CMD_ABORT_EN) {
> + gi3c->err = -ETIMEDOUT;
> + dev_dbg(gi3c->se.dev, "Abort after timeout successful\n");
> + }
> +
> + if (status == GENI_TIMEOUT) {
> + gi3c->err = -ETIMEDOUT;
> + dev_dbg(gi3c->se.dev, "I3C transaction timed out\n");
> + }
> +}
[Severity: High]
Won't geni_i3c_handle_err() clobber an earlier NACK error (-ENOTCONN or
-EBUSY) with -ETIMEDOUT when a transfer times out and aborts?
When a NACK interrupt fires without M_CMD_DONE_EN set, geni_i3c_irq() records
-ENOTCONN or -EBUSY in gi3c->err and waits for the timeout path in
_i3c_geni_execute_command() to abort the command. Both the timeout path
(passing GENI_TIMEOUT) and the subsequent M_CMD_ABORT_EN interrupt overwrite
gi3c->err with -ETIMEDOUT, so geni_i3c_master_send_ccc_cmd() no longer sees
-ENOTCONN or -EBUSY and cannot set cmd->err = I3C_ERROR_M2.
In addition, status in geni_i3c_handle_err() is checked both as an
SE_GENI_M_IRQ_STATUS bitmask and for equality with the enum constant
GENI_TIMEOUT (which has value 9, or M_CMD_FAILURE_EN | M_CMD_DONE_EN). And
if geni_i3c_irq() enters the error branch due to dm_rx_st & DM_I3C_CB_ERR or
M_CMD_FAILURE_EN alone, geni_i3c_handle_err(gi3c, m_stat) leaves gi3c->err as
0. Should gi3c->err only be updated when it is not already set, with a
fallback error code when none of the specific M_GP_IRQ_* bits match?
> +
> +static irqreturn_t geni_i3c_irq(int irq, void *dev)
> +{
> + struct geni_i3c_dev *gi3c = dev;
> + u32 m_stat_mask, dm_tx_st, dm_rx_st;
> + u32 m_stat, rx_st, dma_mode;
> +
> + m_stat = readl_relaxed(gi3c->se.base + SE_GENI_M_IRQ_STATUS);
> + m_stat_mask = readl_relaxed(gi3c->se.base + SE_GENI_M_IRQ_EN);
[Severity: Low]
In geni_i3c_irq(), m_stat_mask is assigned here but never used, which will
trigger an unused-but-set-variable compiler warning. Should m_stat be masked
with m_stat_mask, or should m_stat_mask be removed?
> + rx_st = readl_relaxed(gi3c->se.base + SE_GENI_RX_FIFO_STATUS);
> + dm_tx_st = readl_relaxed(gi3c->se.base + SE_DMA_TX_IRQ_STAT);
> + dm_rx_st = readl_relaxed(gi3c->se.base + SE_DMA_RX_IRQ_STAT);
> + dma_mode = readl_relaxed(gi3c->se.base + SE_GENI_DMA_MODE_EN);
> +
> + scoped_guard(spinlock, &gi3c->irq_lock) {
> + if (m_stat & SE_I3C_ERR || dm_rx_st & DM_I3C_CB_ERR) {
> + geni_i3c_handle_err(gi3c, m_stat);
> + /* Disable the TX Watermark interrupt to stop TX */
> + if (!dma_mode)
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + } else if (dma_mode) {
> + dev_dbg(gi3c->se.dev, "i3c dma tx:0x%x, dma rx:0x%x\n", dm_tx_st, dm_rx_st);
> + } else if ((m_stat & (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN)) &&
> + !gi3c->cur_is_write && gi3c->cur_buf) {
> + /* FIFO RX handling */
> + u32 rxcnt = rx_st & RX_FIFO_WC_MSK;
> + int avail = rxcnt * 4;
> + int need = gi3c->cur_len - gi3c->cur_idx;
> + int n = min(need, avail);
> +
> + if (n > 0) {
> + i3c_readl_fifo(gi3c->se.base + SE_GENI_RX_FIFOn,
> + gi3c->cur_buf + gi3c->cur_idx, n);
> + gi3c->cur_idx += n;
> + }
[Severity: High]
What happens in geni_i3c_irq() when an I3C target returns a valid short read
response (for example, a 2-byte response to a 5-byte GETMXDS or 3-byte GETMRL
CCC read) or terminates a read early via the T-bit?
First, RX_FIFO_WC_MSK in SE_GENI_RX_FIFO_STATUS counts 32-bit FIFO words, and
valid bytes in the final partial word are indicated by RX_LAST and
RX_LAST_BYTE_VALID_MSK. Computing avail = rxcnt * 4 rounds a 2-byte response
up to 4 bytes, so geni_i3c_master_send_ccc_cmd() sets
cmd->dests[i].payload.actual_len = gi3c->cur_idx to 4 (for GETMXDS, causing
i3c_master_getmxds_locked() to fail with -EIO) or 3 (for GETMRL, reading a
padding byte as max_ibi_len).
Second, M_GP_IRQ_0_EN ("I3C slave early read termination") is included in
SE_I3C_ERR and sets gi3c->err = -EINVAL in geni_i3c_handle_err(). Because
the FIFO RX handler is in an else-if branch after if (m_stat & SE_I3C_ERR ...),
an early-terminated read fails with -EINVAL and leaves the received words
unread in the RX FIFO.
> + } else if ((m_stat & M_TX_FIFO_WATERMARK_EN) &&
> + gi3c->cur_is_write && gi3c->cur_buf) {
> + /* FIFO TX handling */
> + int avail = gi3c->tx_wm * 4;
> + int need = gi3c->cur_len - gi3c->cur_idx;
> + int n = min(need, avail);
> +
> + if (n > 0) {
> + i3c_writel_fifo(gi3c->se.base + SE_GENI_TX_FIFOn,
> + (void *)(gi3c->cur_buf + gi3c->cur_idx), n);
> + gi3c->cur_idx += n;
> + }
> + }
> +
> + if (!dma_mode && gi3c->cur_idx == gi3c->cur_len)
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + }
[Severity: High]
Can M_TX_FIFO_WATERMARK_EN cause an unhandled interrupt storm if gi3c->cur_buf
becomes NULL while gi3c->cur_idx < gi3c->cur_len?
In _i3c_geni_execute_command():
geni_se_setup_m_cmd(&gi3c->se, xfer->m_cmd, xfer->m_param);
...
if (xfer->mode == GENI_SE_FIFO && len > 0) /* Get FIFO IRQ */
writel_relaxed(1, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
Because geni_se_setup_m_cmd() is called before enabling
SE_GENI_TX_WATERMARK_REG without holding gi3c->irq_lock, an immediate error
or abort interrupt can run first and clear SE_GENI_TX_WATERMARK_REG, after
which _i3c_geni_execute_command() writes 1 to SE_GENI_TX_WATERMARK_REG and
sets gi3c->cur_buf = NULL. Likewise, on a transfer timeout,
_i3c_geni_execute_command() sets gi3c->cur_buf = NULL without clearing
SE_GENI_TX_WATERMARK_REG.
When M_TX_FIFO_WATERMARK_EN then fires in geni_i3c_irq() with
gi3c->cur_buf == NULL and gi3c->cur_idx < gi3c->cur_len, neither the TX
branch nor the cur_idx == cur_len check disables SE_GENI_TX_WATERMARK_REG,
leaving the level-triggered watermark interrupt asserted continuously.
> +
> + if (m_stat)
> + writel_relaxed(m_stat, gi3c->se.base + SE_GENI_M_IRQ_CLEAR);
> +
> + if (dma_mode) {
> + if (dm_tx_st)
> + writel_relaxed(dm_tx_st, gi3c->se.base + SE_DMA_TX_IRQ_CLR);
> + if (dm_rx_st)
> + writel_relaxed(dm_rx_st, gi3c->se.base + SE_DMA_RX_IRQ_CLR);
> + }
> +
> + /* if this is err with done-bit not set, handle that through timeout. */
> + if (m_stat & (M_CMD_DONE_EN | M_CMD_ABORT_EN)) {
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + complete(&gi3c->done);
> + } else if ((dm_tx_st & TX_DMA_DONE) || (dm_rx_st & (RX_DMA_DONE | RX_RESET_DONE))) {
> + complete(&gi3c->done);
> + }
> +
> + return IRQ_HANDLED;
> +}
[ ... ]
> +static int _i3c_geni_execute_command(struct geni_i3c_dev *gi3c, struct geni_i3c_xfer_params *xfer)
> +{
[ ... ]
> + if (xfer->mode == GENI_SE_DMA) {
> + if (gi3c->err) {
> + if (is_write)
> + writel_relaxed(1, gi3c->se.base + SE_DMA_TX_FSM_RST);
> + else
> + writel_relaxed(1, gi3c->se.base + SE_DMA_RX_FSM_RST);
> +
> + time_remaining = wait_for_completion_timeout(&gi3c->done, XFER_TIMEOUT);
> + dev_dbg(gi3c->se.dev, "Timeout completing DMA transfer\n");
> + }
> +
> + if (is_write)
> + geni_se_tx_dma_unprep(&gi3c->se, tx_dma, len);
> + else
> + geni_se_rx_dma_unprep(&gi3c->se, rx_dma, len);
> + }
[Severity: High]
In _i3c_geni_execute_command() and geni_i3c_irq(), how is gi3c->done
synchronized across multi-command loops and DMA FSM resets?
1. Callers only call reinit_completion(&gi3c->done) once before their
transfer loops rather than in _i3c_geni_execute_command() before each
command and before waiting on SE_DMA_TX_FSM_RST / SE_DMA_RX_FSM_RST.
2. In geni_i3c_irq(), the DMA completion check handles RX_RESET_DONE in
dm_rx_st, but omits TX_RESET_DONE in dm_tx_st, so waiting for
SE_DMA_TX_FSM_RST in _i3c_geni_execute_command() will always time out
before geni_se_tx_dma_unprep() unmaps the buffer.
3. Because m_stat is not masked with SE_GENI_M_IRQ_EN, if M_CMD_DONE_EN is
set in m_stat while dma_mode is active, geni_i3c_irq() can complete
gi3c->done before TX_DMA_DONE or RX_DMA_DONE fires, causing
_i3c_geni_execute_command() to unmap the DMA buffer while DMA is still in
flight and leaving an extra completion in gi3c->done for the next command.
[ ... ]
> +static int geni_i3c_master_priv_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
> + int nxfers, enum i3c_xfer_mode mode)
> +{
> + struct i3c_master_controller *m = i3c_dev_get_master(dev);
> + struct geni_i3c_dev *gi3c = to_geni_i3c_master(m);
> + bool use_7e = false;
> + int ret;
> +
> + scoped_guard(mutex, &gi3c->lock) {
[ ... ]
> + qcom_geni_i3c_conf(gi3c, PUSH_PULL_MODE);
> +
> + for (int i = 0; i < nxfers; i++) {
> + bool stall = (i < (nxfers - 1));
> + struct geni_i3c_xfer_params xfer = { GENI_SE_FIFO };
> +
> + xfer.m_param = (stall ? STOP_STRETCH : 0);
> + xfer.m_param |= FIELD_PREP(SLAVE_ADDR_MASK, dev->info.dyn_addr);
> + xfer.m_param |= (use_7e) ? USE_7E : 0;
> +
> + /* use_7e = true only for last transfer */
> + use_7e = (i == nxfers - 1);
[Severity: High]
In geni_i3c_master_priv_xfers(), use_7e is initialized to false and only set
to (i == nxfers - 1) after xfer.m_param |= (use_7e) ? USE_7E : 0 has already
been evaluated.
On iterations i < nxfers - 1, use_7e is false; on the final iteration
i == nxfers - 1, use_7e is still false when xfer.m_param is built and only
becomes true right before the loop exits. As a result, USE_7E is never set on
any transfer (and shouldn't USE_7E be set on the first transfer i == 0, as in
geni_i3c_master_send_ccc_cmd() and geni_i3c_perform_daa()?).
> +
> + if (xfers[i].rnw) {
[ ... ]
> +static int geni_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, struct i2c_msg *msgs, int num)
> +{
[ ... ]
> + for (int i = 0; i < num; i++) {
> + struct geni_i3c_xfer_params xfer;
> +
> + xfer.m_cmd = (msgs[i].flags & I2C_M_RD) ? I2C_READ : I2C_WRITE;
> + xfer.m_param = (i < (num - 1)) ? STOP_STRETCH : 0;
> + xfer.m_param |= FIELD_PREP(SLAVE_ADDR_MASK, msgs[i].addr);
> + xfer.mode = msgs[i].len > (gi3c->tx_depth / 2) ?
> + GENI_SE_DMA : GENI_SE_FIFO;
> + if (msgs[i].flags & I2C_M_RD)
> + ret = i3c_geni_execute_read_command(gi3c, &xfer, msgs[i].buf,
> + msgs[i].len);
> + else
> + ret = i3c_geni_execute_write_command(gi3c, &xfer, msgs[i].buf,
> + msgs[i].len);
> + if (ret)
> + break;
> + }
[Severity: High]
In geni_i3c_master_i2c_xfers(), when msgs[i].len > (gi3c->tx_depth / 2),
msgs[i].buf is passed directly to GENI_SE_DMA and mapped with
dma_map_single() in geni_se_tx_dma_prep() / geni_se_rx_dma_prep().
Since i2c_msg buffers are not guaranteed to be DMA-safe (as noted in the
struct i3c_master_controller_ops documentation in include/linux/i3c/master.h),
should this use i2c_get_dma_safe_msg_buf() and i2c_put_dma_safe_msg_buf()?
Also, for GENI_SE_DMA transfers:
- In _i3c_geni_execute_command(), geni_se_setup_m_cmd() starts the hardware
command before geni_se_tx_dma_prep() or geni_se_rx_dma_prep() sets up the
DMA transfer (or falls back to GENI_SE_FIFO).
- In i3c_geni_resources_init(), avg_bw is set on GENI_TO_CORE and
CPU_TO_GENI, but gi3c->se.icc_paths[GENI_TO_DDR].avg_bw is left at 0.
[ ... ]
> +static int geni_i3c_hw_init(struct geni_i3c_dev *gi3c, struct platform_device *pdev)
> +{
> + u32 proto, fifo_disable;
> +
> + proto = geni_se_read_proto(&gi3c->se);
> + if (proto != GENI_SE_I3C) {
> + geni_se_resources_off(&gi3c->se);
> + return dev_err_probe(gi3c->se.dev, -ENXIO, "Invalid proto %d\n", proto);
> + }
> +
> + fifo_disable = readl_relaxed(gi3c->se.base + GENI_IF_DISABLE_RO);
> + if (fifo_disable) {
> + geni_se_resources_off(&gi3c->se);
> + return dev_err_probe(gi3c->se.dev, -ENXIO, "GPI DMA mode not supported\n");
> + }
[ ... ]
> +static int geni_i3c_probe(struct platform_device *pdev)
> +{
[ ... ]
> + ret = geni_se_resources_on(&gi3c->se);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret, "Error turning resources ON\n");
> +
> + ret = geni_i3c_hw_init(gi3c, pdev);
> + geni_se_resources_off(&gi3c->se);
> + if (ret)
> + return ret;
[ ... ]
> +static int geni_i3c_runtime_suspend(struct device *dev)
> +{
> + struct geni_i3c_dev *gi3c = dev_get_drvdata(dev);
> +
> + disable_irq(gi3c->irq);
> + geni_se_resources_off(&gi3c->se);
> +
> + return 0;
> +}
[Severity: Medium]
If geni_i3c_hw_init() fails, it calls geni_se_resources_off(&gi3c->se) before
returning, and then geni_i3c_probe() calls geni_se_resources_off(&gi3c->se) a
second time. Won't this underflow the enable/prepare counts on se->clk and
the shared QUP wrapper clocks?
Also, in geni_i3c_runtime_suspend(), if geni_se_resources_off(&gi3c->se)
fails (for instance if pinctrl_pm_select_sleep_state() returns an error
before clocks are disabled), should the error be propagated after re-enabling
gi3c->irq?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104111610.3739095-1-mukesh.savaliya@oss.qualcomm.com?part=2
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
WARNING: multiple messages have this Message-ID (diff)
From: sashiko-bot@kernel.org
To: "Mukesh Kumar Savaliya" <mukesh.savaliya@oss.qualcomm.com>,
linux-i3c@lists.infradead.org, devicetree@vger.kernel.org
Cc: robh@kernel.org, conor+dt@kernel.org, Frank.Li@kernel.org,
"Alexandre Belloni" <alexandre.belloni@bootlin.com>
Subject: Re: [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver
Date: Wed, 04 Nov 2026 11:43:51 +0000 [thread overview]
Message-ID: <sashiko-outbox-197205@kernel.org> (raw)
In-Reply-To: <20261104111610.3739095-2-mukesh.savaliya@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] i3c: qcom-geni: use_7e updated after m_param in geni_i3c_master_priv_xfers()
- [High] i3c: qcom-geni: broken I3C_ADDR_MASK and DAA handling in geni_i3c_perform_daa()
- [High] i3c: qcom-geni: invalid clock lookup and 1 MHz cycle count in geni_i3c_clk_map
- [High] i3c: qcom-geni: unsafe I2C DMA mapping and broken DMA reset handling
- [High] i3c: qcom-geni: broken short RX FIFO reads and early termination in geni_i3c_irq()
- [High] i3c: qcom-geni: NACK error overwrite and status bitmask mismatch in geni_i3c_handle_err()
- [High] i3c: qcom-geni: missing reinit_completion() and TX_RESET_DONE in _i3c_geni_execute_command()
- [High] i3c: qcom-geni: TX watermark race and unhandled IRQ storm in geni_i3c_irq()
- [Medium] i3c: qcom-geni: double geni_se_resources_off() call in geni_i3c_probe()
- [Low] i3c: qcom-geni: unused variable m_stat_mask in geni_i3c_irq()
--
commit 2ad15e60246cd7898205f431d98abb7e053d2d97
Author: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
i3c: master: Add Qualcomm I3C controller driver
This commit adds a driver for the Qualcomm QUPv3 GENI-based I3C master
controller supporting I3C SDR and legacy I2C transfers.
> diff --git a/drivers/i3c/master/i3c-qcom-geni.c b/drivers/i3c/master/i3c-qcom-geni.c
> new file mode 100644
> index 0000000000000..2fee77e681e26
> --- /dev/null
> +++ b/drivers/i3c/master/i3c-qcom-geni.c
[ ... ]
> +#define I3C_AUTO_SUSPEND_DELAY 250
> +#define PACKING_BYTES_PER_WORD 4
> +#define XFER_TIMEOUT 250
> +
> +#define I3C_ADDR_MASK (I2C_MAX_ADDR >> 1)
[ ... ]
> +struct geni_i3c_dev {
[ ... ]
> + u8 *cur_buf;
> + u8 last_addr;
> + bool cur_is_write;
> + int cur_len;
> + int cur_idx;
> + DECLARE_BITMAP(newaddrslots, 64);
> +
> + const struct geni_i3c_clk_settings *clk_cfg;
> + const struct geni_i3c_clk_settings *clk_od_cfg;
> +};
[Severity: High]
Since I2C_MAX_ADDR is defined as GENMASK(6, 0) (0x7f) in
include/linux/i3c/master.h, shifting it right by 1 makes I3C_ADDR_MASK 0x3f
(6 bits instead of 7 bits), and newaddrslots is only 64 bits wide instead of
I3C_MAX_ADDR + 1 (128).
Later in geni_i3c_perform_daa():
addr = i3c_master_get_free_addr(&gi3c->ctrlr,
gi3c->last_addr + 1);
if (addr > I3C_ADDR_MASK)
return -EINVAL;
gi3c->last_addr = addr;
set_bit(addr, gi3c->newaddrslots);
...
ret = i3c_geni_execute_write_command(gi3c, &xfer, tx_buf, 1);
if (ret)
break;
Doesn't this reject valid 7-bit dynamic addresses from 0x40 to 0x7d (as well
as truncating negative error codes from i3c_master_get_free_addr() into u8)?
Also, in geni_i3c_perform_daa() and geni_i3c_master_send_ccc_cmd():
- gi3c->last_addr is never reset between DAA runs, so repeated DAA
invocations will only search above the previous address until the range is
exhausted.
- set_bit(addr, gi3c->newaddrslots) is called before
i3c_geni_execute_write_command() sends the dynamic address to the target.
If that write fails, geni_i3c_perform_daa() breaks out and returns 0 with
addr still set in newaddrslots, causing geni_i3c_master_do_daa() to call
i3c_master_add_i3c_dev_locked() for an unassigned address.
- geni_i3c_master_send_ccc_cmd() ignores the return value of
geni_i3c_perform_daa(gi3c).
Should I3C_ADDR_MASK be I3C_MAX_ADDR (0x7f) with newaddrslots sized to
I3C_MAX_ADDR + 1, and should set_bit(addr, gi3c->newaddrslots) happen only
after the address write succeeds?
[ ... ]
> +static const struct geni_i3c_clk_settings geni_i3c_clk_map[] = {
> + {
> + .clk_freq_out = 100 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 76,
> + .i2c_t_low_cnt = 90,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 8,
> + .i2c_t_cycle_cnt = 192,
> + },
> + {
> + .clk_freq_out = 400 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 12,
> + .i2c_t_low_cnt = 24,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 8,
> + .i2c_t_cycle_cnt = 48
> + },
> + {
> + .clk_freq_out = 1000 * HZ_PER_KHZ,
> + .clk_src_freq = 19200 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 4,
> + .i2c_t_low_cnt = 9,
> + .i3c_t_high_cnt = 7,
> + .i3c_t_cycle_cnt = 0,
> + .i2c_t_cycle_cnt = 19
> + },
> + {
> + .clk_freq_out = 12500 * HZ_PER_KHZ,
> + .clk_src_freq = 100000 * HZ_PER_KHZ,
> + .clk_div = 1,
> + .i2c_t_high_cnt = 45,
> + .i2c_t_low_cnt = 63,
> + .i3c_t_high_cnt = 6,
> + .i3c_t_cycle_cnt = 7,
> + .i2c_t_cycle_cnt = 110
> + }
> +};
> +
> +static int geni_i3c_clk_map_idx(struct geni_i3c_dev *gi3c)
> +{
> + const struct geni_i3c_clk_settings *clk_table_idx = geni_i3c_clk_map;
> + struct i3c_master_controller *m = &gi3c->ctrlr;
> + struct i3c_bus *bus = i3c_master_get_bus(m);
> +
> + for (int i = 0; i < ARRAY_SIZE(geni_i3c_clk_map); i++, clk_table_idx++) {
> + if (clk_table_idx->clk_freq_out == bus->scl_rate.i3c &&
> + clk_table_idx->clk_src_freq == gi3c->clk_src_freq)
> + gi3c->clk_cfg = clk_table_idx;
> +
> + if (clk_table_idx->clk_freq_out == bus->scl_rate.i2c)
> + gi3c->clk_od_cfg = clk_table_idx;
> + }
> +
> + if (!gi3c->clk_cfg || !gi3c->clk_od_cfg)
> + return -EINVAL;
> +
> + return 0;
> +}
[Severity: High]
How does geni_i3c_clk_map_idx() behave on a pure I3C bus or a mixed-slow
bus?
In i3c_bus_set_mode(), when bus->mode is I3C_BUS_MODE_PURE and no i2c-scl-hz
property is set in devicetree, bus->scl_rate.i2c remains 0. Because no entry
in geni_i3c_clk_map[] has clk_freq_out == 0, gi3c->clk_od_cfg stays NULL and
geni_i3c_master_bus_init() fails with -EINVAL.
Conversely, in I3C_BUS_MODE_MIXED_SLOW (where i3c_bus_set_mode() clamps
bus->scl_rate.i3c to bus->scl_rate.i2c), i3c_geni_resources_init() hardcodes
gi3c->clk_src_freq to 100 MHz while the 100 kHz, 400 kHz, and 1000 kHz
entries in geni_i3c_clk_map[] all have clk_src_freq set to 19.2 MHz, so
gi3c->clk_cfg stays NULL and geni_i3c_clk_map_idx() also returns -EINVAL.
Also, in the 1000 kHz entry of geni_i3c_clk_map[], i3c_t_cycle_cnt is 0 while
i3c_t_high_cnt is 7 (whereas the 100 kHz and 400 kHz entries use
i3c_t_cycle_cnt = 8). Is programming SE_I3C_SCL_CYCLE to 0 when
qcom_geni_i3c_conf(gi3c, OPEN_DRAIN_MODE) selects the 1000 kHz entry
intentional?
[ ... ]
> +static void geni_i3c_handle_err(struct geni_i3c_dev *gi3c, u32 status)
> +{
> + dev_dbg(gi3c->se.dev, "len:%d, %s\n",
> + gi3c->cur_len, gi3c->cur_is_write ? "write" : "read");
> +
> + if (status & M_GP_IRQ_0_EN) {
> + gi3c->err = -EINVAL;
> + dev_dbg(gi3c->se.dev, "I3C slave early read termination\n");
> + }
[ ... ]
> + if (status & M_CMD_ABORT_EN) {
> + gi3c->err = -ETIMEDOUT;
> + dev_dbg(gi3c->se.dev, "Abort after timeout successful\n");
> + }
> +
> + if (status == GENI_TIMEOUT) {
> + gi3c->err = -ETIMEDOUT;
> + dev_dbg(gi3c->se.dev, "I3C transaction timed out\n");
> + }
> +}
[Severity: High]
Won't geni_i3c_handle_err() clobber an earlier NACK error (-ENOTCONN or
-EBUSY) with -ETIMEDOUT when a transfer times out and aborts?
When a NACK interrupt fires without M_CMD_DONE_EN set, geni_i3c_irq() records
-ENOTCONN or -EBUSY in gi3c->err and waits for the timeout path in
_i3c_geni_execute_command() to abort the command. Both the timeout path
(passing GENI_TIMEOUT) and the subsequent M_CMD_ABORT_EN interrupt overwrite
gi3c->err with -ETIMEDOUT, so geni_i3c_master_send_ccc_cmd() no longer sees
-ENOTCONN or -EBUSY and cannot set cmd->err = I3C_ERROR_M2.
In addition, status in geni_i3c_handle_err() is checked both as an
SE_GENI_M_IRQ_STATUS bitmask and for equality with the enum constant
GENI_TIMEOUT (which has value 9, or M_CMD_FAILURE_EN | M_CMD_DONE_EN). And
if geni_i3c_irq() enters the error branch due to dm_rx_st & DM_I3C_CB_ERR or
M_CMD_FAILURE_EN alone, geni_i3c_handle_err(gi3c, m_stat) leaves gi3c->err as
0. Should gi3c->err only be updated when it is not already set, with a
fallback error code when none of the specific M_GP_IRQ_* bits match?
> +
> +static irqreturn_t geni_i3c_irq(int irq, void *dev)
> +{
> + struct geni_i3c_dev *gi3c = dev;
> + u32 m_stat_mask, dm_tx_st, dm_rx_st;
> + u32 m_stat, rx_st, dma_mode;
> +
> + m_stat = readl_relaxed(gi3c->se.base + SE_GENI_M_IRQ_STATUS);
> + m_stat_mask = readl_relaxed(gi3c->se.base + SE_GENI_M_IRQ_EN);
[Severity: Low]
In geni_i3c_irq(), m_stat_mask is assigned here but never used, which will
trigger an unused-but-set-variable compiler warning. Should m_stat be masked
with m_stat_mask, or should m_stat_mask be removed?
> + rx_st = readl_relaxed(gi3c->se.base + SE_GENI_RX_FIFO_STATUS);
> + dm_tx_st = readl_relaxed(gi3c->se.base + SE_DMA_TX_IRQ_STAT);
> + dm_rx_st = readl_relaxed(gi3c->se.base + SE_DMA_RX_IRQ_STAT);
> + dma_mode = readl_relaxed(gi3c->se.base + SE_GENI_DMA_MODE_EN);
> +
> + scoped_guard(spinlock, &gi3c->irq_lock) {
> + if (m_stat & SE_I3C_ERR || dm_rx_st & DM_I3C_CB_ERR) {
> + geni_i3c_handle_err(gi3c, m_stat);
> + /* Disable the TX Watermark interrupt to stop TX */
> + if (!dma_mode)
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + } else if (dma_mode) {
> + dev_dbg(gi3c->se.dev, "i3c dma tx:0x%x, dma rx:0x%x\n", dm_tx_st, dm_rx_st);
> + } else if ((m_stat & (M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN)) &&
> + !gi3c->cur_is_write && gi3c->cur_buf) {
> + /* FIFO RX handling */
> + u32 rxcnt = rx_st & RX_FIFO_WC_MSK;
> + int avail = rxcnt * 4;
> + int need = gi3c->cur_len - gi3c->cur_idx;
> + int n = min(need, avail);
> +
> + if (n > 0) {
> + i3c_readl_fifo(gi3c->se.base + SE_GENI_RX_FIFOn,
> + gi3c->cur_buf + gi3c->cur_idx, n);
> + gi3c->cur_idx += n;
> + }
[Severity: High]
What happens in geni_i3c_irq() when an I3C target returns a valid short read
response (for example, a 2-byte response to a 5-byte GETMXDS or 3-byte GETMRL
CCC read) or terminates a read early via the T-bit?
First, RX_FIFO_WC_MSK in SE_GENI_RX_FIFO_STATUS counts 32-bit FIFO words, and
valid bytes in the final partial word are indicated by RX_LAST and
RX_LAST_BYTE_VALID_MSK. Computing avail = rxcnt * 4 rounds a 2-byte response
up to 4 bytes, so geni_i3c_master_send_ccc_cmd() sets
cmd->dests[i].payload.actual_len = gi3c->cur_idx to 4 (for GETMXDS, causing
i3c_master_getmxds_locked() to fail with -EIO) or 3 (for GETMRL, reading a
padding byte as max_ibi_len).
Second, M_GP_IRQ_0_EN ("I3C slave early read termination") is included in
SE_I3C_ERR and sets gi3c->err = -EINVAL in geni_i3c_handle_err(). Because
the FIFO RX handler is in an else-if branch after if (m_stat & SE_I3C_ERR ...),
an early-terminated read fails with -EINVAL and leaves the received words
unread in the RX FIFO.
> + } else if ((m_stat & M_TX_FIFO_WATERMARK_EN) &&
> + gi3c->cur_is_write && gi3c->cur_buf) {
> + /* FIFO TX handling */
> + int avail = gi3c->tx_wm * 4;
> + int need = gi3c->cur_len - gi3c->cur_idx;
> + int n = min(need, avail);
> +
> + if (n > 0) {
> + i3c_writel_fifo(gi3c->se.base + SE_GENI_TX_FIFOn,
> + (void *)(gi3c->cur_buf + gi3c->cur_idx), n);
> + gi3c->cur_idx += n;
> + }
> + }
> +
> + if (!dma_mode && gi3c->cur_idx == gi3c->cur_len)
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + }
[Severity: High]
Can M_TX_FIFO_WATERMARK_EN cause an unhandled interrupt storm if gi3c->cur_buf
becomes NULL while gi3c->cur_idx < gi3c->cur_len?
In _i3c_geni_execute_command():
geni_se_setup_m_cmd(&gi3c->se, xfer->m_cmd, xfer->m_param);
...
if (xfer->mode == GENI_SE_FIFO && len > 0) /* Get FIFO IRQ */
writel_relaxed(1, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
Because geni_se_setup_m_cmd() is called before enabling
SE_GENI_TX_WATERMARK_REG without holding gi3c->irq_lock, an immediate error
or abort interrupt can run first and clear SE_GENI_TX_WATERMARK_REG, after
which _i3c_geni_execute_command() writes 1 to SE_GENI_TX_WATERMARK_REG and
sets gi3c->cur_buf = NULL. Likewise, on a transfer timeout,
_i3c_geni_execute_command() sets gi3c->cur_buf = NULL without clearing
SE_GENI_TX_WATERMARK_REG.
When M_TX_FIFO_WATERMARK_EN then fires in geni_i3c_irq() with
gi3c->cur_buf == NULL and gi3c->cur_idx < gi3c->cur_len, neither the TX
branch nor the cur_idx == cur_len check disables SE_GENI_TX_WATERMARK_REG,
leaving the level-triggered watermark interrupt asserted continuously.
> +
> + if (m_stat)
> + writel_relaxed(m_stat, gi3c->se.base + SE_GENI_M_IRQ_CLEAR);
> +
> + if (dma_mode) {
> + if (dm_tx_st)
> + writel_relaxed(dm_tx_st, gi3c->se.base + SE_DMA_TX_IRQ_CLR);
> + if (dm_rx_st)
> + writel_relaxed(dm_rx_st, gi3c->se.base + SE_DMA_RX_IRQ_CLR);
> + }
> +
> + /* if this is err with done-bit not set, handle that through timeout. */
> + if (m_stat & (M_CMD_DONE_EN | M_CMD_ABORT_EN)) {
> + writel_relaxed(0, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> + complete(&gi3c->done);
> + } else if ((dm_tx_st & TX_DMA_DONE) || (dm_rx_st & (RX_DMA_DONE | RX_RESET_DONE))) {
> + complete(&gi3c->done);
> + }
> +
> + return IRQ_HANDLED;
> +}
[ ... ]
> +static int _i3c_geni_execute_command(struct geni_i3c_dev *gi3c, struct geni_i3c_xfer_params *xfer)
> +{
[ ... ]
> + if (xfer->mode == GENI_SE_DMA) {
> + if (gi3c->err) {
> + if (is_write)
> + writel_relaxed(1, gi3c->se.base + SE_DMA_TX_FSM_RST);
> + else
> + writel_relaxed(1, gi3c->se.base + SE_DMA_RX_FSM_RST);
> +
> + time_remaining = wait_for_completion_timeout(&gi3c->done, XFER_TIMEOUT);
> + dev_dbg(gi3c->se.dev, "Timeout completing DMA transfer\n");
> + }
> +
> + if (is_write)
> + geni_se_tx_dma_unprep(&gi3c->se, tx_dma, len);
> + else
> + geni_se_rx_dma_unprep(&gi3c->se, rx_dma, len);
> + }
[Severity: High]
In _i3c_geni_execute_command() and geni_i3c_irq(), how is gi3c->done
synchronized across multi-command loops and DMA FSM resets?
1. Callers only call reinit_completion(&gi3c->done) once before their
transfer loops rather than in _i3c_geni_execute_command() before each
command and before waiting on SE_DMA_TX_FSM_RST / SE_DMA_RX_FSM_RST.
2. In geni_i3c_irq(), the DMA completion check handles RX_RESET_DONE in
dm_rx_st, but omits TX_RESET_DONE in dm_tx_st, so waiting for
SE_DMA_TX_FSM_RST in _i3c_geni_execute_command() will always time out
before geni_se_tx_dma_unprep() unmaps the buffer.
3. Because m_stat is not masked with SE_GENI_M_IRQ_EN, if M_CMD_DONE_EN is
set in m_stat while dma_mode is active, geni_i3c_irq() can complete
gi3c->done before TX_DMA_DONE or RX_DMA_DONE fires, causing
_i3c_geni_execute_command() to unmap the DMA buffer while DMA is still in
flight and leaving an extra completion in gi3c->done for the next command.
[ ... ]
> +static int geni_i3c_master_priv_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
> + int nxfers, enum i3c_xfer_mode mode)
> +{
> + struct i3c_master_controller *m = i3c_dev_get_master(dev);
> + struct geni_i3c_dev *gi3c = to_geni_i3c_master(m);
> + bool use_7e = false;
> + int ret;
> +
> + scoped_guard(mutex, &gi3c->lock) {
[ ... ]
> + qcom_geni_i3c_conf(gi3c, PUSH_PULL_MODE);
> +
> + for (int i = 0; i < nxfers; i++) {
> + bool stall = (i < (nxfers - 1));
> + struct geni_i3c_xfer_params xfer = { GENI_SE_FIFO };
> +
> + xfer.m_param = (stall ? STOP_STRETCH : 0);
> + xfer.m_param |= FIELD_PREP(SLAVE_ADDR_MASK, dev->info.dyn_addr);
> + xfer.m_param |= (use_7e) ? USE_7E : 0;
> +
> + /* use_7e = true only for last transfer */
> + use_7e = (i == nxfers - 1);
[Severity: High]
In geni_i3c_master_priv_xfers(), use_7e is initialized to false and only set
to (i == nxfers - 1) after xfer.m_param |= (use_7e) ? USE_7E : 0 has already
been evaluated.
On iterations i < nxfers - 1, use_7e is false; on the final iteration
i == nxfers - 1, use_7e is still false when xfer.m_param is built and only
becomes true right before the loop exits. As a result, USE_7E is never set on
any transfer (and shouldn't USE_7E be set on the first transfer i == 0, as in
geni_i3c_master_send_ccc_cmd() and geni_i3c_perform_daa()?).
> +
> + if (xfers[i].rnw) {
[ ... ]
> +static int geni_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, struct i2c_msg *msgs, int num)
> +{
[ ... ]
> + for (int i = 0; i < num; i++) {
> + struct geni_i3c_xfer_params xfer;
> +
> + xfer.m_cmd = (msgs[i].flags & I2C_M_RD) ? I2C_READ : I2C_WRITE;
> + xfer.m_param = (i < (num - 1)) ? STOP_STRETCH : 0;
> + xfer.m_param |= FIELD_PREP(SLAVE_ADDR_MASK, msgs[i].addr);
> + xfer.mode = msgs[i].len > (gi3c->tx_depth / 2) ?
> + GENI_SE_DMA : GENI_SE_FIFO;
> + if (msgs[i].flags & I2C_M_RD)
> + ret = i3c_geni_execute_read_command(gi3c, &xfer, msgs[i].buf,
> + msgs[i].len);
> + else
> + ret = i3c_geni_execute_write_command(gi3c, &xfer, msgs[i].buf,
> + msgs[i].len);
> + if (ret)
> + break;
> + }
[Severity: High]
In geni_i3c_master_i2c_xfers(), when msgs[i].len > (gi3c->tx_depth / 2),
msgs[i].buf is passed directly to GENI_SE_DMA and mapped with
dma_map_single() in geni_se_tx_dma_prep() / geni_se_rx_dma_prep().
Since i2c_msg buffers are not guaranteed to be DMA-safe (as noted in the
struct i3c_master_controller_ops documentation in include/linux/i3c/master.h),
should this use i2c_get_dma_safe_msg_buf() and i2c_put_dma_safe_msg_buf()?
Also, for GENI_SE_DMA transfers:
- In _i3c_geni_execute_command(), geni_se_setup_m_cmd() starts the hardware
command before geni_se_tx_dma_prep() or geni_se_rx_dma_prep() sets up the
DMA transfer (or falls back to GENI_SE_FIFO).
- In i3c_geni_resources_init(), avg_bw is set on GENI_TO_CORE and
CPU_TO_GENI, but gi3c->se.icc_paths[GENI_TO_DDR].avg_bw is left at 0.
[ ... ]
> +static int geni_i3c_hw_init(struct geni_i3c_dev *gi3c, struct platform_device *pdev)
> +{
> + u32 proto, fifo_disable;
> +
> + proto = geni_se_read_proto(&gi3c->se);
> + if (proto != GENI_SE_I3C) {
> + geni_se_resources_off(&gi3c->se);
> + return dev_err_probe(gi3c->se.dev, -ENXIO, "Invalid proto %d\n", proto);
> + }
> +
> + fifo_disable = readl_relaxed(gi3c->se.base + GENI_IF_DISABLE_RO);
> + if (fifo_disable) {
> + geni_se_resources_off(&gi3c->se);
> + return dev_err_probe(gi3c->se.dev, -ENXIO, "GPI DMA mode not supported\n");
> + }
[ ... ]
> +static int geni_i3c_probe(struct platform_device *pdev)
> +{
[ ... ]
> + ret = geni_se_resources_on(&gi3c->se);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret, "Error turning resources ON\n");
> +
> + ret = geni_i3c_hw_init(gi3c, pdev);
> + geni_se_resources_off(&gi3c->se);
> + if (ret)
> + return ret;
[ ... ]
> +static int geni_i3c_runtime_suspend(struct device *dev)
> +{
> + struct geni_i3c_dev *gi3c = dev_get_drvdata(dev);
> +
> + disable_irq(gi3c->irq);
> + geni_se_resources_off(&gi3c->se);
> +
> + return 0;
> +}
[Severity: Medium]
If geni_i3c_hw_init() fails, it calls geni_se_resources_off(&gi3c->se) before
returning, and then geni_i3c_probe() calls geni_se_resources_off(&gi3c->se) a
second time. Won't this underflow the enable/prepare counts on se->clk and
the shared QUP wrapper clocks?
Also, in geni_i3c_runtime_suspend(), if geni_se_resources_off(&gi3c->se)
fails (for instance if pinctrl_pm_select_sleep_state() returns an error
before clocks are disabled), should the error be propagated after re-enabling
gi3c->irq?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104111610.3739095-1-mukesh.savaliya@oss.qualcomm.com?part=2
next prev parent reply other threads:[~2026-11-04 11:43 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 11:16 [PATCH v7 1/3] dt-bindings: i3c: Add support for Qualcomm I3C controller Mukesh Kumar Savaliya
2026-11-04 11:16 ` Mukesh Kumar Savaliya
2026-11-04 11:16 ` [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver Mukesh Kumar Savaliya
2026-11-04 11:16 ` Mukesh Kumar Savaliya
2026-11-04 11:43 ` sashiko-bot [this message]
2026-11-04 11:43 ` sashiko-bot
2026-11-05 4:39 ` kernel test robot
2026-11-05 4:39 ` kernel test robot
2026-11-05 14:47 ` kernel test robot
2026-11-05 14:47 ` kernel test robot
2026-11-05 14:49 ` kernel test robot
2026-11-05 14:49 ` kernel test robot
2026-11-04 11:16 ` [PATCH v7 3/3] MAINTAINERS: Add maintainer for Qualcomm's " Mukesh Kumar Savaliya
2026-11-04 11:16 ` Mukesh Kumar Savaliya
2026-11-04 11:35 ` [PATCH v7 1/3] dt-bindings: i3c: Add support for Qualcomm I3C controller sashiko-bot
2026-11-04 11:35 ` sashiko-bot
2026-11-04 12:31 ` Mukesh Savaliya
2026-11-04 12:31 ` Mukesh Savaliya
2026-11-05 11:26 ` Krzysztof Kozlowski
2026-11-05 11:26 ` Krzysztof Kozlowski
-- strict thread matches above, loose matches on Subject: below --
2026-11-04 12:21 [PATCH v7 0/3] Add Qualcomm i3c controller driver support Mukesh Kumar Savaliya
2026-11-04 12:21 ` [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver Mukesh Kumar Savaliya
2026-11-04 12:21 ` Mukesh Kumar Savaliya
2026-11-04 12:43 ` sashiko-bot
2026-11-04 12:43 ` sashiko-bot
2026-11-04 19:47 ` Frank Li
2026-11-04 19:47 ` Frank Li
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=sashiko-outbox-197205@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=alexandre.belloni@bootlin.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=linux-i3c@lists.infradead.org \
--cc=mukesh.savaliya@oss.qualcomm.com \
--cc=robh@kernel.org \
--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.