Devicetree
 help / color / mirror / Atom feed
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 12:43:28 +0000	[thread overview]
Message-ID: <sashiko-outbox-197287@kernel.org> (raw)
In-Reply-To: <20261104122155.3789346-3-mukesh.savaliya@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] i3c: master: unsafe DMA mapping of i2c_msg in geni_i3c_master_i2c_xfers()
- [High] i3c: master: clock lookup failure in geni_i3c_clk_map_idx()
- [High] i3c: master: 6-bit address truncation and DAA bugs in geni_i3c_perform_daa()
- [High] i3c: master: use_7e is never set in geni_i3c_master_priv_xfers()
- [High] i3c: master: NACK clobbering and DMA reset bugs in geni_i3c_irq()
- [High] i3c: qcom-geni: missing reinit_completion() and TX_RESET_DONE in DMA xfer
- [High] i3c: qcom-geni: broken short read and RX_LAST handling in geni_i3c_irq()
- [High] i3c: qcom-geni: unclocked register access and disabled IRQ when !CONFIG_PM
- [High] i3c: qcom-geni: unlocked command start and TX watermark IRQ storm race
- [Medium] i3c: master: double geni_se_resources_off() on geni_i3c_hw_init() error
- [Medium] i3c: qcom-geni: unchecked geni_se_resources_off() in runtime suspend
- [Low] i3c: qcom-geni: unused variable m_stat_mask in geni_i3c_irq()
--

commit 142dbd5b9e6edda42ed3407ef95befa8d7a473b4
Author: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>

i3c: master: Add Qualcomm I3C controller driver

This commit adds the Qualcomm QUPv3 GENI-based I3C master controller driver
supporting SDR mode, CCC commands, dynamic address assignment, and 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
> @@ -0,0 +1,1133 @@

[ ... ]

> +#define I3C_AUTO_SUSPEND_DELAY	250
> +#define PACKING_BYTES_PER_WORD	4
> +#define XFER_TIMEOUT		250
> +
> +#define I3C_ADDR_MASK		(I2C_MAX_ADDR >> 1)

[ ... ]

> +	int cur_len;
> +	int cur_idx;
> +	DECLARE_BITMAP(newaddrslots, 64);

[ ... ]

> +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]
Will geni_i3c_clk_map_idx() fail with -EINVAL during
geni_i3c_master_bus_init() on pure I3C buses and mixed-slow buses?

In drivers/i3c/master.c, i3c_bus_set_mode() only populates
i3cbus->scl_rate.i3c when i3cbus->mode == I3C_BUS_MODE_PURE, leaving
i3cbus->scl_rate.i2c as 0 unless an optional i2c-scl-hz property is present in
firmware. Because no entry in geni_i3c_clk_map[] has clk_freq_out == 0,
gi3c->clk_od_cfg stays NULL and geni_i3c_clk_map_idx() returns -EINVAL.

Conversely, in I3C_BUS_MODE_MIXED_SLOW, i3c_bus_set_mode() clamps
bus->scl_rate.i3c to bus->scl_rate.i2c (at most 1 MHz). However,
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 use a
19.2 MHz clk_src_freq, leaving gi3c->clk_cfg NULL.

Also, in the 1000 kHz entry of geni_i3c_clk_map[] (the default I2C rate for
mixed-fast and mixed-limited buses), i3c_t_cycle_cnt is 0 while i3c_t_high_cnt
is 7. When qcom_geni_i3c_conf() configures OPEN_DRAIN_MODE, is writing 0 to
SE_I3C_SCL_CYCLE intentional while the 100 kHz and 400 kHz entries use 8?

[ ... ]

> +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_GP_IRQ_1_EN) {
> +		gi3c->err = -ENOTCONN;
> +		dev_dbg(gi3c->se.dev, "NACK: slave unresponsive, check power/reset\n");
> +	}
> +
> +	if (status & M_GP_IRQ_2_EN) {
> +		gi3c->err = -EINVAL;
> +		dev_dbg(gi3c->se.dev, "CRC or parity error\n");
> +	}
> +
> +	if (status & M_GP_IRQ_3_EN) {
> +		gi3c->err = -EPROTO;
> +		dev_dbg(gi3c->se.dev, "Bus proto Error, noisy/unexpected start/stop\n");
> +	}
> +
> +	if (status & M_GP_IRQ_4_EN) {
> +		gi3c->err = -EBUSY;
> +		dev_dbg(gi3c->se.dev, "NACK on 7E, unexpected protocol error\n");
> +	}
> +
> +	if (status & M_GP_IRQ_5_EN) {
> +		gi3c->err = -EINVAL;
> +		dev_dbg(gi3c->se.dev, "NACK on IBI\n");
> +	}
> +
> +	if (status & M_CMD_OVERRUN_EN) {
> +		gi3c->err = -EIO;
> +		dev_dbg(gi3c->se.dev, "Cmd overrun, check GENI cmd-state machine\n");
> +	}
> +
> +	if (status & M_ILLEGAL_CMD_EN) {
> +		gi3c->err = -EILSEQ;
> +		dev_dbg(gi3c->se.dev, "Illegal cmd, check GENI cmd-state machine\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]
Does geni_i3c_handle_err() overwrite earlier NACK error codes (-ENOTCONN and
-EBUSY) with -ETIMEDOUT during timeout and abort recovery?

When M_GP_IRQ_1_EN or M_GP_IRQ_4_EN fires in geni_i3c_irq() without
M_CMD_DONE_EN set, geni_i3c_handle_err() sets gi3c->err to -ENOTCONN or
-EBUSY, and _i3c_geni_execute_command() waits for the timeout to abort the
command. On timeout, _i3c_geni_execute_command() and geni_i3c_abort_xfer()
call geni_i3c_handle_err(gi3c, GENI_TIMEOUT), followed by M_CMD_ABORT_EN in
geni_i3c_irq(). Because both branches unconditionally set
gi3c->err = -ETIMEDOUT, the original NACK error is lost, preventing
geni_i3c_master_send_ccc_cmd() from reporting I3C_ERROR_M2.

Also, status in geni_i3c_handle_err() mixes hardware SE_GENI_M_IRQ_STATUS bit
flags with the enum value GENI_TIMEOUT (which is 9, equal to
M_CMD_FAILURE_EN | M_CMD_DONE_EN). And when geni_i3c_irq() enters the error
path via dm_rx_st & DM_I3C_CB_ERR or M_CMD_FAILURE_EN without M_GP_IRQ_0..4_EN
set in m_stat, geni_i3c_handle_err(gi3c, m_stat) leaves gi3c->err as 0 (while
M_GP_IRQ_5_EN is omitted from SE_I3C_ERR). Should geni_i3c_handle_err()
preserve an existing non-zero gi3c->err and separate hardware status bits from
driver error codes?

> +
> +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;
> +			}
> +		} else if ((m_stat & M_TX_FIFO_WATERMARK_EN) &&

[Severity: High]
How does FIFO RX in geni_i3c_irq() handle short or variable-length I3C reads?

When an I3C target terminates a read early via the T-bit, the controller sets
M_GP_IRQ_0_EN ("I3C slave early read termination"). Because M_GP_IRQ_0_EN is
included in SE_I3C_ERR, geni_i3c_irq() takes the error branch, sets
gi3c->err = -EINVAL, and skips draining the RX FIFO.

In addition, the FIFO RX calculation uses avail = rxcnt * 4 without checking
RX_LAST and RX_LAST_BYTE_VALID_MSK in SE_GENI_RX_FIFO_STATUS. If a target
returns a short response that is not a multiple of 4 bytes and is smaller than
gi3c->cur_len (for example, a 2-byte response to a 5-byte I3C_CCC_GETMXDS or a
3-byte I3C_CCC_GETMRL), avail is rounded up to 4 bytes, so gi3c->cur_idx and
cmd->dests[i].payload.actual_len are over-counted, causing
i3c_master_getmxds_locked() to fail with -EIO or i3c_master_getmrl_locked() to
read an uninitialized byte.

> +			   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);
> +	}
> +
> +	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)
> +{
> +	bool is_write = gi3c->cur_is_write;
> +	dma_addr_t tx_dma = 0, rx_dma = 0;
> +	unsigned long time_remaining;
> +	u32 len = gi3c->cur_len;
> +	int ret;
> +
> +	geni_se_select_mode(&gi3c->se, xfer->mode);
> +
> +	gi3c->err = 0;
> +	gi3c->cur_idx = 0;
> +
> +	if (is_write) {
> +		dev_dbg(gi3c->se.dev, "I3C cmd:0x%x param:0x%x WRITE len:%d\n",
> +			xfer->m_cmd, xfer->m_param, len);
> +
> +		writel_relaxed(len, gi3c->se.base + SE_I3C_TX_TRANS_LEN);
> +		geni_se_setup_m_cmd(&gi3c->se, xfer->m_cmd, xfer->m_param);
> +
> +		if (xfer->mode == GENI_SE_DMA) {
> +			ret = geni_se_tx_dma_prep(&gi3c->se, gi3c->cur_buf, len, &tx_dma);
> +			if (ret) {
> +				xfer->mode = GENI_SE_FIFO;
> +				geni_se_select_mode(&gi3c->se, xfer->mode);
> +			}
> +		}
> +
> +		if (xfer->mode == GENI_SE_FIFO && len > 0) /* Get FIFO IRQ */
> +			writel_relaxed(1, gi3c->se.base + SE_GENI_TX_WATERMARK_REG);
> +	} else {
> +		dev_dbg(gi3c->se.dev, "I3C cmd:0x%x param:0x%x READ len:%d\n",
> +			xfer->m_cmd, xfer->m_param, len);
> +		writel_relaxed(len, gi3c->se.base + SE_I3C_RX_TRANS_LEN);
> +		geni_se_setup_m_cmd(&gi3c->se, xfer->m_cmd, xfer->m_param);
> +		if (xfer->mode == GENI_SE_DMA) {
> +			ret = geni_se_rx_dma_prep(&gi3c->se, gi3c->cur_buf, len, &rx_dma);
> +			if (ret) {
> +				xfer->mode = GENI_SE_FIFO;
> +				geni_se_select_mode(&gi3c->se, xfer->mode);
> +			}
> +		}
> +	}
> +
> +	time_remaining = wait_for_completion_timeout(&gi3c->done, XFER_TIMEOUT);
> +	if (!time_remaining) {
> +		unsigned long flags;
> +
> +		dev_dbg(gi3c->se.dev, "Timeout completing transfer\n");
> +		spin_lock_irqsave(&gi3c->irq_lock, flags);
> +		geni_i3c_handle_err(gi3c, GENI_TIMEOUT);
> +		gi3c->cur_buf = NULL;
> +		spin_unlock_irqrestore(&gi3c->irq_lock, flags);
> +		geni_i3c_abort_xfer(gi3c);
> +	}

[Severity: High]
Can starting the command in _i3c_geni_execute_command() without holding
gi3c->irq_lock, or clearing gi3c->cur_buf on timeout without zeroing
SE_GENI_TX_WATERMARK_REG, cause an infinite M_TX_FIFO_WATERMARK_EN interrupt
storm?

In _i3c_geni_execute_command(), geni_se_setup_m_cmd() starts the hardware
command before SE_GENI_TX_WATERMARK_REG is set to 1. If the command
immediately fails on the bus (such as on an address or 0x7E NACK),
geni_i3c_irq() can run, clear SE_GENI_TX_WATERMARK_REG to 0, and complete
gi3c->done before _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 while
gi3c->cur_idx < gi3c->cur_len without writing 0 to 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 FIFO
branch nor the gi3c->cur_idx == gi3c->cur_len check disables
SE_GENI_TX_WATERMARK_REG, leaving the level-triggered watermark interrupt
asserted indefinitely.

> +
> +	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]
Are there completion synchronization and DMA reset issues between
_i3c_geni_execute_command() and geni_i3c_irq()?

1. reinit_completion(&gi3c->done) is only called once before the outer loops in
   geni_i3c_master_send_ccc_cmd(), geni_i3c_master_priv_xfers(), and
   geni_i3c_master_i2c_xfers(), rather than before each command in
   _i3c_geni_execute_command() and before the SE_DMA_TX_FSM_RST /
   SE_DMA_RX_FSM_RST wait.

2. In geni_i3c_irq(), m_stat & (M_CMD_DONE_EN | M_CMD_ABORT_EN) calls
   complete(&gi3c->done) even when dma_mode is active. In GENI_SE_DMA mode,
   if M_CMD_DONE_EN is observed before TX_DMA_DONE or RX_DMA_DONE, gi3c->done
   is completed early (unmapping the DMA buffer while DMA is still active) and
   then completed a second time when DMA finishes, leaving gi3c->done
   pre-signaled for the next transfer in a multi-message loop (such as the
   stack-allocated rx_buf and tx_buf transfers in geni_i3c_perform_daa()).

3. When gi3c->err is set on a DMA write, _i3c_geni_execute_command() writes 1
   to SE_DMA_TX_FSM_RST and waits on gi3c->done, but geni_i3c_irq() only checks
   (dm_tx_st & TX_DMA_DONE) || (dm_rx_st & (RX_DMA_DONE | RX_RESET_DONE)) and
   never checks TX_RESET_DONE in dm_tx_st.

4. In _i3c_geni_execute_command(), geni_se_setup_m_cmd() is called before
   geni_se_tx_dma_prep() / geni_se_rx_dma_prep(); if DMA mapping fails, it
   calls geni_se_select_mode() after the hardware command has already started.

[ ... ]

> +static int geni_i3c_perform_daa(struct geni_i3c_dev *gi3c)
> +{
> +	struct geni_i3c_xfer_params xfer = { GENI_SE_FIFO };
> +	bool first_device = true;
> +	int ret;
> +
> +	while (1) {
> +		u8 rx_buf[8], tx_buf[1];
> +		u8 addr;
> +		u64 pid;
> +
> +		/* Read PID[47:0], BCR, DCR during ENTDAA flow */
> +		xfer.m_cmd = I2C_READ;
> +		xfer.m_param = STOP_STRETCH | CONTINUOUS_MODE_DAA;
> +
> +		if (first_device) {
> +			xfer.m_param |= USE_7E;
> +			first_device = false;
> +		}
> +
> +		ret = i3c_geni_execute_read_command(gi3c, &xfer,
> +						    rx_buf, sizeof(rx_buf));
> +		if (ret) {
> +			dev_dbg(gi3c->se.dev,
> +				"DAA complete, no more slave devices\n");
> +			break;
> +		}
> +
> +		pid = FIELD_PREP(GENMASK_ULL(47, 40), (u64)rx_buf[0]) |
> +		      FIELD_PREP(GENMASK_ULL(39, 32), (u64)rx_buf[1]) |
> +		      FIELD_PREP(GENMASK_ULL(31, 24), (u64)rx_buf[2]) |
> +		      FIELD_PREP(GENMASK_ULL(23, 16), (u64)rx_buf[3]) |
> +		      FIELD_PREP(GENMASK_ULL(15, 8),  (u64)rx_buf[4]) |
> +		      FIELD_PREP(GENMASK_ULL(7, 0),   (u64)rx_buf[5]);
> +
> +		dev_dbg(gi3c->se.dev, "DAA device PID: 0x%012llx\n", pid);
> +
> +		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);
> +
> +		/* Dynamic address + odd parity bit */
> +		tx_buf[0] = (addr & I3C_ADDR_MASK) << 1;
> +		tx_buf[0] |= !parity8(addr & I3C_ADDR_MASK);
> +
> +		xfer.m_cmd = I2C_WRITE;
> +		xfer.m_param = STOP_STRETCH | BYPASS_ADDR_PHASE;
> +
> +		ret = i3c_geni_execute_write_command(gi3c, &xfer, tx_buf, 1);
> +		if (ret)
> +			break;
> +	}
> +
> +	return 0;
> +}

[Severity: High]
Does I3C_ADDR_MASK truncate 7-bit I3C dynamic addresses to 6 bits in
geni_i3c_perform_daa()?

In include/linux/i3c/master.h, I2C_MAX_ADDR and I3C_MAX_ADDR are both defined
as GENMASK(6, 0) (0x7f). Defining I3C_ADDR_MASK as (I2C_MAX_ADDR >> 1)
evaluates to 0x3f (63) and newaddrslots is sized to 64 bits, so
geni_i3c_perform_daa() rejects all valid 7-bit dynamic addresses from 0x40 to
0x7d.

There also appear to be a few related issues in the DAA flow:

1. i3c_master_get_free_addr() returns a negative errno (-ENOMEM) on failure,
   which is truncated into u8 addr.

2. gi3c->last_addr is never reset to 0 before DAA starts, so subsequent DAA
   runs or address searches past 0x3f will fail to wrap around to lower free
   addresses.

3. set_bit(addr, gi3c->newaddrslots) is called before
   i3c_geni_execute_write_command(); if writing the dynamic address fails, the
   unassigned address remains set in newaddrslots and
   geni_i3c_master_do_daa() will pass it to i3c_master_add_i3c_dev_locked(),
   permanently marking the address slot as used.

4. In geni_i3c_master_send_ccc_cmd(), the return value of
   geni_i3c_perform_daa(gi3c) is ignored, so returning -EINVAL after reading
   the 8-byte PID/BCR/DCR with STOP_STRETCH | CONTINUOUS_MODE_DAA drops the
   error and leaves the bus stalled without a STOP condition.

[ ... ]

> +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) {
> +		reinit_completion(&gi3c->done);
> +		ret = pm_runtime_get_sync(gi3c->se.dev);
> +		if (ret < 0) {
> +			dev_err(gi3c->se.dev, "error turning on SE resources:%d\n", ret);
> +			pm_runtime_put_noidle(gi3c->se.dev);
> +			pm_runtime_set_suspended(gi3c->se.dev);
> +			return ret;
> +		}
> +
> +		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(), is USE_7E ever set on any transfer?

use_7e is initialized to false, and inside the loop xfer.m_param |= (use_7e) ?
USE_7E : 0 is evaluated before use_7e = (i == nxfers - 1). For all iterations
before the last one, use_7e remains false; on the last iteration (i == nxfers -
1), use_7e is still false when xfer.m_param is constructed and is only set to
true right before the loop exits. Should USE_7E be set on the first transfer
(i == 0), matching geni_i3c_master_send_ccc_cmd() and geni_i3c_perform_daa()?

Also, if nxfers == 0, ret still holds the return value of
pm_runtime_get_sync(), which can be 1 when the device is already active.

[ ... ]

> +static int geni_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, struct i2c_msg *msgs, int num)
> +{
> +	struct i3c_master_controller *m = i2c_dev_get_master(dev);
> +	struct geni_i3c_dev *gi3c = to_geni_i3c_master(m);
> +	int ret;
> +
> +	guard(mutex)(&gi3c->lock);
> +	reinit_completion(&gi3c->done);
> +
> +	ret = pm_runtime_get_sync(gi3c->se.dev);
> +	if (ret < 0) {
> +		dev_err(gi3c->se.dev, "Error turning on SE resources:%d\n", ret);
> +		pm_runtime_put_noidle(gi3c->se.dev);
> +		pm_runtime_set_suspended(gi3c->se.dev);
> +		return ret;
> +	}
> +
> +	qcom_geni_i3c_conf(gi3c, OPEN_DRAIN_MODE);
> +
> +	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);

[Severity: Critical]
Is it safe to pass msgs[i].buf directly to GENI_SE_DMA in
geni_i3c_master_i2c_xfers() without i2c_get_dma_safe_msg_buf()?

The I2C/I3C core does not guarantee that i2c_msg buffers are DMA-safe (for
instance, i3c_master_i2c_funcs() advertises I2C_FUNC_SMBUS_EMUL, where
i2c_smbus_xfer_emulated() passes stack-allocated buffers up to 34 bytes). When
msgs[i].len > (gi3c->tx_depth / 2), _i3c_geni_execute_command() calls
geni_se_tx_dma_prep() / geni_se_rx_dma_prep(), which maps msgs[i].buf directly
with dma_map_single(). Should this use i2c_get_dma_safe_msg_buf() and
i2c_put_dma_safe_msg_buf() like i2c-qcom-geni.c does?

Also, in i3c_geni_resources_init(), gi3c->se.icc_paths[GENI_TO_DDR].avg_bw is
left at 0 before calling geni_icc_set_bw(&gi3c->se), so the DDR interconnect
path is not voted for when GENI_SE_DMA is used.

[ ... ]

> +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");
> +	}

[ ... ]

> +	ret = devm_request_irq(&pdev->dev, gi3c->irq, geni_i3c_irq,
> +			       IRQF_NO_AUTOEN, dev_name(&pdev->dev), gi3c);
> +	if (ret)
> +		return dev_err_probe(&pdev->dev, ret, "Error registering core IRQ\n");
> +
> +	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;

[Severity: Medium]
If geni_i3c_hw_init() fails, doesn't geni_i3c_probe() call
geni_se_resources_off(&gi3c->se) a second time?

Both error paths in geni_i3c_hw_init() already call
geni_se_resources_off(&gi3c->se) before returning, and geni_i3c_probe() then
unconditionally calls geni_se_resources_off(&gi3c->se) again, underflowing the
clock enable/prepare counts on se->clk and the parent QUP wrapper clocks.

[Severity: High]
What happens during geni_i3c_probe() when CONFIG_PM is disabled?

geni_i3c_probe() requests gi3c->irq with IRQF_NO_AUTOEN and calls
geni_se_resources_off(&gi3c->se) before i3c_master_register(). When CONFIG_PM
is disabled, pm_runtime_resume_and_get() in geni_i3c_master_bus_init() and
pm_runtime_get_sync() in the transfer paths do not invoke
geni_i3c_runtime_resume(), so qcom_geni_i3c_conf() will access unclocked GENI
registers during i3c_master_register() and the IRQ will remain disabled.

[ ... ]

> +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]
Should geni_i3c_runtime_suspend() check the return value of
geni_se_resources_off()?

If pinctrl_pm_select_sleep_state() fails inside geni_se_resources_off(), the
clocks remain enabled while geni_i3c_runtime_suspend() leaves gi3c->irq
disabled and returns 0, causing the next runtime resume to call
geni_se_resources_on() again and leak the clock enable count.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104122155.3789346-1-mukesh.savaliya@oss.qualcomm.com?part=2

  reply	other threads:[~2026-11-04 12:43 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 1/3] dt-bindings: i3c: Add support for Qualcomm I3C controller Mukesh Kumar Savaliya
2026-11-04 12:35   ` sashiko-bot
2026-11-04 12:21 ` [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver Mukesh Kumar Savaliya
2026-11-04 12:43   ` sashiko-bot [this message]
2026-11-04 12:21 ` [PATCH v7 3/3] MAINTAINERS: Add maintainer for Qualcomm's " Mukesh Kumar Savaliya
  -- strict thread matches above, loose matches on Subject: below --
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 ` [PATCH v7 2/3] i3c: master: Add Qualcomm I3C controller driver Mukesh Kumar Savaliya
2026-11-04 11:43   ` 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=sashiko-outbox-197287@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox