Re: [PATCH v7 2/5] Re: i2c: core: run atomic i2c xfer when !preemptible

From: Benjamin Bara
Date: Tue Jan 02 2024 - 16:02:29 EST


Hi Michael,

On Tue, 2 Jan 2024 at 16:03, Michael Walle <mwalle@xxxxxxxxxx> wrote:
> With preemption disabled, this boils down to
> return system_state > SYSTEM_RUNNING (&& !0)
>
> and will then generate a backtrace splash on each reboot on our
> board:
>
> # reboot -f
> [ 12.687169] No atomic I2C transfer handler for 'i2c-0'
> ...
> [ 12.806359] Call trace:
> [ 12.808793] i2c_smbus_xfer+0x100/0x118
> ...
>
> I'm not sure if this is now the expected behavior or not. There will be
> no backtraces, if I build a preemptible kernel, nor will there be
> backtraces if I revert this patch.


thanks for the report.

In your case, the warning comes from shutting down a regulator during
device_shutdown(), so nothing really problematic here. However, later in
the "restart sequence", IRQs are disabled before the restart handlers
are called. If the reboot handlers would rely on irq-based
("non-atomic") i2c transfer, they might not work properly.

> OTOH, the driver I'm using (drivers/i2c/busses/i2c-mt65xx.c) has no
> *_atomic(). So the warning is correct. There is also [1], which seems to
> be the same issue I'm facing.
>
> -michael
>
> [1] https://lore.kernel.org/linux-i2c/13271b9b-4132-46ef-abf8-2c311967bb46@xxxxxxxxxxx/


I tried to implement an atomic handler for the mt65xx, but I don't have
the respective hardware available to test it. I decided to use a similar
approach as done in drivers/i2c/busses/i2c-rk3x.c, which calls the IRQ
handler in a while loop if an atomic xfer is requested. IMHO, this
should work with IRQs enabled and disabled, but I am not sure if this is
the best approach...

diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c
index a8b5719c3372..3c18305e6059 100644
--- a/drivers/i2c/busses/i2c-mt65xx.c
+++ b/drivers/i2c/busses/i2c-mt65xx.c
@@ -16,6 +16,7 @@
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
@@ -307,6 +308,8 @@ struct mtk_i2c {
bool ignore_restart_irq;
struct mtk_i2c_ac_timing ac_timing;
const struct mtk_i2c_compatible *dev_comp;
+ bool atomic_xfer;
+ bool xfer_complete;
};

/**
@@ -994,6 +997,20 @@ static void i2c_dump_register(struct mtk_i2c *i2c)
readl(i2c->pdmabase + OFFSET_RX_4G_MODE));
}

+static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id);
+
+static int mtk_i2c_wait_xfer_atomic(struct mtk_i2c *i2c)
+{
+ unsigned long timeout = jiffies + i2c->adap.timeout;
+
+ do {
+ udelay(10);
+ mtk_i2c_irq(0, i2c);
+ } while (!i2c->xfer_complete && time_before(jiffies, timeout));
+
+ return i2c->xfer_complete;
+}
+
static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs,
int num, int left_num)
{
@@ -1015,7 +1032,10 @@ static int mtk_i2c_do_transfer(struct mtk_i2c
*i2c, struct i2c_msg *msgs,
if (i2c->auto_restart)
restart_flag = I2C_RS_TRANSFER;

- reinit_completion(&i2c->msg_complete);
+ if (i2c->atomic_xfer)
+ i2c->xfer_complete = false;
+ else
+ reinit_completion(&i2c->msg_complete);

if (i2c->dev_comp->apdma_sync &&
i2c->op != I2C_MASTER_WRRD && num > 1) {
@@ -1195,8 +1215,12 @@ static int mtk_i2c_do_transfer(struct mtk_i2c
*i2c, struct i2c_msg *msgs,
}
mtk_i2c_writew(i2c, start_reg, OFFSET_START);

- ret = wait_for_completion_timeout(&i2c->msg_complete,
- i2c->adap.timeout);
+ if (i2c->atomic_xfer)
+ /* We can't rely on wait_for_completion* calls in atomic mode. */
+ ret = mtk_i2c_wait_xfer_atomic(i2c);
+ else
+ ret = wait_for_completion_timeout(&i2c->msg_complete,
+ i2c->adap.timeout);

/* Clear interrupt mask */
mtk_i2c_writew(i2c, ~(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
@@ -1238,8 +1262,8 @@ static int mtk_i2c_do_transfer(struct mtk_i2c
*i2c, struct i2c_msg *msgs,
return 0;
}

-static int mtk_i2c_transfer(struct i2c_adapter *adap,
- struct i2c_msg msgs[], int num)
+static int mtk_i2c_transfer_common(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num, bool atomic)
{
int ret;
int left_num = num;
@@ -1249,6 +1273,7 @@ static int mtk_i2c_transfer(struct i2c_adapter *adap,
if (ret)
return ret;

+ i2c->atomic_xfer = atomic;
i2c->auto_restart = i2c->dev_comp->auto_restart;

/* checking if we can skip restart and optimize using WRRD mode */
@@ -1303,6 +1328,18 @@ static int mtk_i2c_transfer(struct i2c_adapter *adap,
return ret;
}

+static int mtk_i2c_transfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ return mtk_i2c_transfer_common(adap, msgs, num, false);
+}
+
+static int mtk_i2c_transfer_atomic(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ return mtk_i2c_transfer_common(adap, msgs, num, true);
+}
+
static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id)
{
struct mtk_i2c *i2c = dev_id;
@@ -1328,8 +1365,12 @@ static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id)
mtk_i2c_writew(i2c, I2C_RS_MUL_CNFG | I2C_RS_MUL_TRIG |
I2C_TRANSAC_START, OFFSET_START);
} else {
- if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag))
- complete(&i2c->msg_complete);
+ if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag)) {
+ if (i2c->atomic_xfer)
+ i2c->xfer_complete = true;
+ else
+ complete(&i2c->msg_complete);
+ }
}

return IRQ_HANDLED;
@@ -1346,6 +1387,7 @@ static u32 mtk_i2c_functionality(struct i2c_adapter *adap)

static const struct i2c_algorithm mtk_i2c_algorithm = {
.master_xfer = mtk_i2c_transfer,
+ .master_xfer_atomic = mtk_i2c_transfer_atomic,
.functionality = mtk_i2c_functionality,
};