linux-gbmc: pmbus: protect regulator ops with mutex

Backport upstream patches for CVE-2026-31486 and CVE-2026-72395
to linux-gbmc 6.12 (linux-gbmc_lts.bb).

In the PMBus core driver, regulator routines get_voltage, set_voltage,
and list_voltage previously omitted data->update_lock acquisition,
allowing concurrent threads (e.g. hwmon polling) to interleave
PMBUS_PAGE register switching and corrupt telemetry or direct voltage
writes to the wrong rail.

This backport:
1. Protects regulator operations with update_lock.
2. Defers regulator_notifier_call_chain() dispatch to an asynchronous
   workqueue outside update_lock to prevent recursive mutex deadlock
   with pmbus_fault_handler().
3. Iterates over atomic fault event masks bit-by-bit to ensure
   regulator_handle_critical() processes all simultaneous events.

Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=754bd2b4a084b90b5e7b630e1f423061a9b9b761]
Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b0ff6b6ae9c5183ef701ece7016698bde5a5bfba]
CVE: CVE-2026-31486
CVE: CVE-2026-72395

Tested:
- Successfully compiled full firmware image via BitBake:
  bitbake obmc-phosphor-image (10,484 tasks passed, 0 patch fuzz).
- Signed and flashed image on real hardware (wkcw14-nfd11).
- Executed 5,000-cycle high-frequency concurrent page-switching stress
  test (in1_input vs in2_input) without read failures or bus errors:
  Total Iterations: 5000 | Read Failures: 0 | New Kernel Errors: 0

Google-Bug-Id: 540140798
Change-Id: Id5a541cf765384d6047a5fe2741e6ad149843007
Signed-off-by: Joseph Chan <josephctchan@google.com>
diff --git a/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch b/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch
new file mode 100644
index 0000000..60fbda7
--- /dev/null
+++ b/recipes-kernel/linux/files/0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch
@@ -0,0 +1,196 @@
+From 754bd2b4a084b90b5e7b630e1f423061a9b9b761 Mon Sep 17 00:00:00 2001
+From: Guenter Roeck <linux@roeck-us.net>
+Date: Wed, 18 Feb 2026 10:14:22 -0800
+Subject: [PATCH] hwmon: (pmbus/core) Protect regulator operations with mutex
+
+Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=754bd2b4a084b90b5e7b630e1f423061a9b9b761]
+CVE: CVE-2026-31486
+
+PMBus regulator operations get_voltage, set_voltage, and list_voltage
+access PMBus registers without holding data->update_lock. This can
+lead to race conditions where the PMBus page is switched by another
+thread (such as hwmon sensor reads) while a regulator operation is
+in progress, leading to corrupted data or writes deployed to the wrong rail.
+
+Simply acquiring update_lock in these functions can result in deadlocks
+because pmbus_regulator_notify() is called with update_lock held (e.g.
+from pmbus_fault_handler), and notifier callbacks may invoke regulator
+functions that attempt to acquire the same lock.
+
+Rework pmbus_regulator_notify() to defer notification dispatch to a
+worker thread outside of update_lock using atomic event bitmasks, and
+protect get_voltage, set_voltage, and list_voltage with update_lock.
+
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/pmbus/pmbus_core.c | 66 ++++++++++++++++++++++++++++----
+ 1 file changed, 58 insertions(+), 8 deletions(-)
+
+diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
+index ba7e00b5da2a..c208df3498f3 100644
+--- a/drivers/hwmon/pmbus/pmbus_core.c
++++ b/drivers/hwmon/pmbus/pmbus_core.c
+@@ -118,6 +118,9 @@ struct pmbus_data {
+ 	int vout_low[PMBUS_PAGES];	/* voltage low margin */
+ 	int vout_high[PMBUS_PAGES];	/* voltage high margin */
+ 	ktime_t next_access_backoff;	/* Wait until at least this time */
++
++	struct work_struct notify_work;
++	atomic_t pmbus_notif[PMBUS_PAGES];
+ };
+ 
+ struct pmbus_debugfs_entry {
+@@ -3136,13 +3139,19 @@ static int pmbus_regulator_get_voltage(struct regulator_dev *rdev)
+ 		.page = rdev_get_id(rdev),
+ 		.class = PSC_VOLTAGE_OUT,
+ 		.convert = true,
+ 	};
++	int ret;
+ 
++	mutex_lock(&data->update_lock);
+ 	s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT);
+-	if (s.data < 0)
+-		return s.data;
++	if (s.data < 0) {
++		ret = s.data;
++		goto unlock;
++	}
+ 
+-	return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
++	ret = (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */
++unlock:
++	mutex_unlock(&data->update_lock);
++	return ret;
+ }
+ 
+ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
+@@ -3157,17 +3166,23 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
+ 		.convert = true,
+ 		.data = -1,
+ 	};
+ 	int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */
+-	int low, high;
++	int low, high, ret;
+ 
+ 	*selector = 0;
+ 
++	mutex_lock(&data->update_lock);
+ 	low = pmbus_regulator_get_low_margin(client, s.page);
+-	if (low < 0)
+-		return low;
++	if (low < 0) {
++		ret = low;
++		goto unlock;
++	}
+ 
+ 	high = pmbus_regulator_get_high_margin(client, s.page);
+-	if (high < 0)
+-		return high;
++	if (high < 0) {
++		ret = high;
++		goto unlock;
++	}
+ 
+ 	/* Make sure we are within margins */
+ 	if (low > val)
+@@ -3177,7 +3192,10 @@ static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv,
+ 
+ 	val = pmbus_data2reg(data, &s, val);
+ 
+-	return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
++	ret = _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val);
++unlock:
++	mutex_unlock(&data->update_lock);
++	return ret;
+ }
+ 
+ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
+@@ -3185,26 +3203,58 @@ static int pmbus_regulator_list_voltage(struct regulator_dev *rdev,
+ {
+ 	struct device *dev = rdev_get_dev(rdev);
+ 	struct i2c_client *client = to_i2c_client(dev->parent);
+-	int val, low, high;
++	struct pmbus_data *data = i2c_get_clientdata(client);
++	int val, low, high, ret;
+ 
+ 	if (selector >= rdev->desc->n_voltages ||
+ 	    selector < rdev->desc->linear_min_sel)
+ 		return -EINVAL;
+ 
+ 	selector -= rdev->desc->linear_min_sel;
+ 	val = DIV_ROUND_CLOSEST(rdev->desc->min_uV +
+ 				(rdev->desc->uV_step * selector), 1000); /* convert to mV */
+ 
++	mutex_lock(&data->update_lock);
+ 	low = pmbus_regulator_get_low_margin(client, rdev_get_id(rdev));
+-	if (low < 0)
+-		return low;
++	if (low < 0) {
++		ret = low;
++		goto unlock;
++	}
+ 
+ 	high = pmbus_regulator_get_high_margin(client, rdev_get_id(rdev));
+-	if (high < 0)
+-		return high;
++	if (high < 0) {
++		ret = high;
++		goto unlock;
++	}
+ 
+ 	if (val >= low && val <= high)
+-		return val * 1000; /* unit is uV */
++		ret = val * 1000; /* unit is uV */
++	else
++		ret = 0;
+ 
+-	return 0;
++unlock:
++	mutex_unlock(&data->update_lock);
++	return ret;
+ }
++
++static void pmbus_regulator_notify_work(struct work_struct *work)
++{
++	struct pmbus_data *data = container_of(work, struct pmbus_data, notify_work);
++	int i, j;
++
++	for (i = 0; i < data->info->pages; i++) {
++		int event = atomic_xchg(&data->pmbus_notif[i], 0);
++
++		if (!event)
++			continue;
++
++		for (j = 0; j < data->info->num_regulators; j++) {
++			if (i == rdev_get_id(data->rdevs[j])) {
++				regulator_notifier_call_chain(data->rdevs[j], event, NULL);
++				break;
++			}
++		}
++	}
++}
++
++static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event)
++{
++	atomic_or(event, &data->pmbus_notif[page]);
++	schedule_work(&data->notify_work);
++	return 0;
++}
++
++static void pmbus_regulator_cancel_work(void *data)
++{
++	struct pmbus_data *pdata = data;
++
++	cancel_work_sync(&pdata->notify_work);
++}
+@@ -3233,6 +3283,10 @@ static int pmbus_regulator_register(struct pmbus_data *data)
+ 	if (!data->rdevs)
+ 		return -ENOMEM;
+ 
++	INIT_WORK(&data->notify_work, pmbus_regulator_notify_work);
++	ret = devm_add_action_or_reset(dev, pmbus_regulator_cancel_work, data);
++	if (ret)
++		return ret;
++
+ 	for (i = 0; i < info->num_regulators; i++) {
+-- 
diff --git a/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch b/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch
new file mode 100644
index 0000000..b20eaa8
--- /dev/null
+++ b/recipes-kernel/linux/files/0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch
@@ -0,0 +1,41 @@
+From b0ff6b6ae9c5183ef701ece7016698bde5a5bfba Mon Sep 17 00:00:00 2001
+From: Guenter Roeck <linux@roeck-us.net>
+Date: Fri, 20 Feb 2026 14:32:01 -0800
+Subject: [PATCH] hwmon: (pmbus/core) Fix critical regulator event notification
+
+Upstream-Status: Backport [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b0ff6b6ae9c5183ef701ece7016698bde5a5bfba]
+CVE: CVE-2026-72395
+
+Commit 754bd2b4a084 ("hwmon: (pmbus/core) Protect regulator operations
+with mutex") passed a combined bitmask of events to
+regulator_notifier_call_chain(). Because regulator_handle_critical()
+evaluates event types with a strict switch statement, multi-bit flags
+fail to match, causing critical hardware protection triggers to be bypassed.
+
+Iterate through the event bitmask and pass each event individually.
+
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ drivers/hwmon/pmbus/pmbus_core.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
+index c208df3498f3..dbce73e8912b 100644
+--- a/drivers/hwmon/pmbus/pmbus_core.c
++++ b/drivers/hwmon/pmbus/pmbus_core.c
+@@ -3242,8 +3242,11 @@ static void pmbus_regulator_notify_work(struct work_struct *work)
+ 
+ 		for (j = 0; j < data->info->num_regulators; j++) {
+ 			if (i == rdev_get_id(data->rdevs[j])) {
+-				regulator_notifier_call_chain(data->rdevs[j], event, NULL);
+-				break;
++				unsigned long ev = event;
++				int bit;
++
++				for_each_set_bit(bit, &ev, BITS_PER_LONG)
++					regulator_notifier_call_chain(data->rdevs[j], BIT(bit), NULL);
++				break;
+ 			}
+ 		}
+ 	}
+-- 
diff --git a/recipes-kernel/linux/linux-gbmc_lts.bb b/recipes-kernel/linux/linux-gbmc_lts.bb
index 766d832..4680c1e 100644
--- a/recipes-kernel/linux/linux-gbmc_lts.bb
+++ b/recipes-kernel/linux/linux-gbmc_lts.bb
@@ -26,6 +26,8 @@
   file://0006-i2c-npcm-Enable-slave-in-eob-interrupt.patch \
   file://0001-i2c-npcm-Add-clock-toggle-recovery.patch \
   file://DOWNSTREAM_0001-i2c-npcm-speed-set.patch \
+  file://0001-hwmon-pmbus-core-Protect-regulator-operations-with-m.patch \
+  file://0002-hwmon-pmbus-core-Fix-critical-regulator-event-notifi.patch \
   "
 
 # Newer kernels don't have HGPIO in pinctrl names