lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250820153125.22002-3-jszhang@kernel.org>
Date: Wed, 20 Aug 2025 23:31:25 +0800
From: Jisheng Zhang <jszhang@...nel.org>
To: Jarkko Nikula <jarkko.nikula@...ux.intel.com>,
	Andy Shevchenko <andriy.shevchenko@...ux.intel.com>,
	Mika Westerberg <mika.westerberg@...ux.intel.com>,
	Jan Dabros <jsd@...ihalf.com>,
	Andi Shyti <andi.shyti@...nel.org>
Cc: linux-kernel@...r.kernel.org,
	linux-i2c@...r.kernel.org
Subject: [PATCH 2/2] i2c: designware: Implement atomic transfer suppot

Rework the read and write code paths in the driver to support operation
in atomic contexts. To achieve this, the driver must not rely on IRQs
or perform any scheduling, e.g., via a sleep or schedule routine.

Implement atomic, sleep-free, and IRQ-less operation. This increases
complexity but is necessary for atomic I2C transfers required by some
hardware configurations, e.g., to trigger reboots on an external PMIC chip.

Signed-off-by: Jisheng Zhang <jszhang@...nel.org>
---
 drivers/i2c/busses/i2c-designware-common.c | 38 +++++++---
 drivers/i2c/busses/i2c-designware-core.h   |  5 +-
 drivers/i2c/busses/i2c-designware-master.c | 80 +++++++++++++++++++---
 3 files changed, 100 insertions(+), 23 deletions(-)

diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index b4e38bc0f876..0b24ac0357ad 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -532,15 +532,23 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev)
 			 * 25us) to ensure the I2C ENABLE bit is already set
 			 * as described in the DesignWare I2C databook.
 			 */
-			fsleep(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
+			if (dev->atomic)
+				udelay(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
+			else
+				fsleep(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
 			/* Set ENABLE bit before setting ABORT */
 			enable |= DW_IC_ENABLE_ENABLE;
 		}
 
 		regmap_write(dev->map, DW_IC_ENABLE, enable | DW_IC_ENABLE_ABORT);
-		ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable,
-					       !(enable & DW_IC_ENABLE_ABORT), 10,
-					       100);
+		if (dev->atomic)
+			ret = regmap_read_poll_timeout_atomic(dev->map, DW_IC_ENABLE, enable,
+							      !(enable & DW_IC_ENABLE_ABORT), 10,
+							      100);
+		else
+			ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable,
+						       !(enable & DW_IC_ENABLE_ABORT), 10,
+						       100);
 		if (ret)
 			dev_err(dev->dev, "timeout while trying to abort current transfer\n");
 	}
@@ -560,7 +568,10 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev)
 		 * transfer supported by the driver (for 400kHz this is
 		 * 25us) as described in the DesignWare I2C databook.
 		 */
-		usleep_range(25, 250);
+		if (dev->atomic)
+			udelay(25);
+		else
+			usleep_range(25, 250);
 	} while (timeout--);
 
 	dev_warn(dev->dev, "timeout in disabling adapter\n");
@@ -607,7 +618,7 @@ int i2c_dw_acquire_lock(struct dw_i2c_dev *dev)
 {
 	int ret;
 
-	if (!dev->acquire_lock)
+	if (dev->atomic || !dev->acquire_lock)
 		return 0;
 
 	ret = dev->acquire_lock();
@@ -621,7 +632,7 @@ int i2c_dw_acquire_lock(struct dw_i2c_dev *dev)
 
 void i2c_dw_release_lock(struct dw_i2c_dev *dev)
 {
-	if (dev->release_lock)
+	if (!dev->atomic && dev->release_lock)
 		dev->release_lock();
 }
 
@@ -633,11 +644,18 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
 	unsigned int status;
 	int ret;
 
-	ret = regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
-				       !(status & DW_IC_STATUS_ACTIVITY),
-				       1100, 20000);
+	if (dev->atomic)
+		ret = regmap_read_poll_timeout_atomic(dev->map, DW_IC_STATUS, status,
+						      !(status & DW_IC_STATUS_ACTIVITY),
+						      1100, 20000);
+	else
+		ret = regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
+					       !(status & DW_IC_STATUS_ACTIVITY),
+					       1100, 20000);
 	if (ret) {
 		dev_warn(dev->dev, "timeout waiting for bus ready\n");
+		if (dev->atomic)
+			return ret;
 
 		i2c_recover_bus(&dev->adapter);
 
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 347843b4f5dd..7384ec5a7ab7 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -299,6 +299,7 @@ struct dw_i2c_dev {
 	void			(*release_lock)(void);
 	int			semaphore_idx;
 	bool			shared_with_punit;
+	bool			atomic;
 	int			(*init)(struct dw_i2c_dev *dev);
 	int			(*set_sda_hold_time)(struct dw_i2c_dev *dev);
 	int			mode;
@@ -365,7 +366,7 @@ static inline void __i2c_dw_disable_nowait(struct dw_i2c_dev *dev)
 static inline void __i2c_dw_write_intr_mask(struct dw_i2c_dev *dev,
 					    unsigned int intr_mask)
 {
-	unsigned int val = dev->flags & ACCESS_POLLING ? 0 : intr_mask;
+	unsigned int val = (dev->atomic || dev->flags & ACCESS_POLLING) ? 0 : intr_mask;
 
 	regmap_write(dev->map, DW_IC_INTR_MASK, val);
 	dev->sw_mask = intr_mask;
@@ -374,7 +375,7 @@ static inline void __i2c_dw_write_intr_mask(struct dw_i2c_dev *dev,
 static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev,
 					   unsigned int *intr_mask)
 {
-	if (!(dev->flags & ACCESS_POLLING))
+	if (!(dev->flags & ACCESS_POLLING) && !dev->atomic)
 		regmap_read(dev->map, DW_IC_INTR_MASK, intr_mask);
 	else
 		*intr_mask = dev->sw_mask;
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index cbd88ffa5610..333ec9bacae4 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -312,9 +312,14 @@ static bool i2c_dw_is_controller_active(struct dw_i2c_dev *dev)
 	if (!(status & DW_IC_STATUS_MASTER_ACTIVITY))
 		return false;
 
-	return regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
-				       !(status & DW_IC_STATUS_MASTER_ACTIVITY),
-				       1100, 20000) != 0;
+	if (dev->atomic)
+		return regmap_read_poll_timeout_atomic(dev->map, DW_IC_STATUS, status,
+						       !(status & DW_IC_STATUS_MASTER_ACTIVITY),
+						       1100, 20000) != 0;
+	else
+		return regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
+					       !(status & DW_IC_STATUS_MASTER_ACTIVITY),
+					       1100, 20000) != 0;
 }
 
 static int i2c_dw_check_stopbit(struct dw_i2c_dev *dev)
@@ -654,7 +659,7 @@ static u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
 	 *
 	 * The raw version might be useful for debugging purposes.
 	 */
-	if (!(dev->flags & ACCESS_POLLING)) {
+	if (!(dev->flags & ACCESS_POLLING) && !dev->atomic) {
 		regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
 	} else {
 		regmap_read(dev->map, DW_IC_RAW_INTR_STAT, &stat);
@@ -801,11 +806,32 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev)
 	return ret ? 0 : -ETIMEDOUT;
 }
 
+static int i2c_dw_wait_transfer_atomic(struct dw_i2c_dev *dev)
+{
+	ktime_t timeout = ktime_add_us(ktime_get(), jiffies_to_usecs(dev->adapter.timeout));
+	unsigned int stat;
+	int ret;
+
+	do {
+		ret = try_wait_for_completion(&dev->cmd_complete);
+		if (ret)
+			break;
+
+		stat = i2c_dw_read_clear_intrbits(dev);
+		if (stat)
+			i2c_dw_process_transfer(dev, stat);
+		else
+			udelay(15);
+	} while (ktime_compare(ktime_get(), timeout) < 0);
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
 /*
  * Prepare controller for a transaction and call i2c_dw_xfer_msg.
  */
 static int
-i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+i2c_dw_xfer_core(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 {
 	struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
 	int ret;
@@ -816,13 +842,19 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 
 	switch (dev->flags & MODEL_MASK) {
 	case MODEL_AMD_NAVI_GPU:
+		if (dev->atomic) {
+			ret = -EOPNOTSUPP;
+			goto done_nolock;
+		}
+
 		ret = amd_i2c_dw_xfer_quirk(adap, msgs, num);
 		goto done_nolock;
 	default:
 		break;
 	}
 
-	reinit_completion(&dev->cmd_complete);
+	if (!dev->atomic)
+		reinit_completion(&dev->cmd_complete);
 	dev->msgs = msgs;
 	dev->msgs_num = num;
 	dev->cmd_err = 0;
@@ -845,12 +877,18 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 	i2c_dw_xfer_init(dev);
 
 	/* Wait for tx to complete */
-	ret = i2c_dw_wait_transfer(dev);
+	if (dev->atomic)
+		ret = i2c_dw_wait_transfer_atomic(dev);
+	else
+		ret = i2c_dw_wait_transfer(dev);
+
 	if (ret) {
 		dev_err(dev->dev, "controller timed out\n");
-		/* i2c_dw_init_master() implicitly disables the adapter */
-		i2c_recover_bus(&dev->adapter);
-		i2c_dw_init_master(dev);
+		if (!dev->atomic) {
+			/* i2c_dw_init_master() implicitly disables the adapter */
+			i2c_recover_bus(&dev->adapter);
+			i2c_dw_init_master(dev);
+		}
 		goto done;
 	}
 
@@ -907,7 +945,25 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 	return ret;
 }
 
-static const struct i2c_algorithm i2c_dw_algo = {
+static int
+i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+	struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+	dev->atomic = false;
+	return i2c_dw_xfer_core(adap, msgs, num);
+}
+
+static int
+i2c_dw_xfer_atomic(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+	struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+	dev->atomic = true;
+	return i2c_dw_xfer_core(adap, msgs, num);
+}
+
+static struct i2c_algorithm i2c_dw_algo = {
 	.xfer = i2c_dw_xfer,
 	.functionality = i2c_dw_func,
 };
@@ -1048,6 +1104,8 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 			  "Synopsys DesignWare I2C adapter");
 	adap->retries = 3;
 	adap->algo = &i2c_dw_algo;
+	if (!dev->acquire_lock)
+		i2c_dw_algo.xfer_atomic = i2c_dw_xfer_atomic,
 	adap->quirks = &i2c_dw_quirks;
 	adap->dev.parent = dev->dev;
 	i2c_set_adapdata(adap, dev);
-- 
2.50.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ