* [RFC PATCH 01/14] treewide: Add backend for macsmc
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
@ 2026-10-29 8:51 ` Atharva Tiwari
2026-10-29 9:20 ` sashiko-bot
2026-10-29 8:51 ` [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs Atharva Tiwari
` (12 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:51 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Split the Apple SMC driver into a common core and a backend
that handles the actual communication. The existing RTKit
code moves to its own backend, and nothing changes for
Apple Silicon.
This lets the same SMC drivers run on other kinds of
hardware, starting with Intel Macs, as a replacement for
applesmc.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
MAINTAINERS | 2 +-
drivers/gpio/Kconfig | 2 +-
drivers/gpio/gpio-macsmc.c | 13 +-
drivers/hwmon/Kconfig | 2 +-
drivers/mfd/Kconfig | 15 +-
drivers/mfd/Makefile | 2 +-
drivers/mfd/macsmc-core.c | 164 ++++++++++++++++
drivers/power/reset/Kconfig | 2 +-
drivers/power/supply/Kconfig | 2 +-
drivers/power/supply/macsmc-power.c | 4 +-
drivers/rtc/Kconfig | 2 +-
drivers/soc/apple/Kconfig | 14 ++
drivers/soc/apple/Makefile | 2 +
.../macsmc.c => soc/apple/macsmc-rtkit.c} | 185 ++++++++----------
include/linux/mfd/macsmc.h | 81 +++++---
15 files changed, 326 insertions(+), 166 deletions(-)
create mode 100644 drivers/mfd/macsmc-core.c
rename drivers/{mfd/macsmc.c => soc/apple/macsmc-rtkit.c} (70%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 03649f08c..1e5ae12ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2727,7 +2727,7 @@ F: drivers/input/touchscreen/apple_z2.c
F: drivers/iommu/apple-dart.c
F: drivers/iommu/io-pgtable-dart.c
F: drivers/irqchip/irq-apple-aic.c
-F: drivers/mfd/macsmc.c
+F: drivers/mfd/macsmc-core.c
F: drivers/nvme/host/apple.c
F: drivers/nvmem/apple-efuses.c
F: drivers/nvmem/apple-spmi-nvmem.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 55e428002..63a2b1e2e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1582,7 +1582,7 @@ config GPIO_LP87565
config GPIO_MACSMC
tristate "Apple Mac SMC GPIO"
- depends on MFD_MACSMC
+ depends on MACSMC_RTKIT
help
Support for GPIOs controlled by the SMC microcontroller on Apple Mac
systems.
diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c
index b0952d066..8c7a942ad 100644
--- a/drivers/gpio/gpio-macsmc.c
+++ b/drivers/gpio/gpio-macsmc.c
@@ -97,20 +97,9 @@ static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp)
{
struct apple_smc *smc = smcgp->smc;
smc_key key = macsmc_gpio_key(0);
- smc_key first_key, last_key;
int start, count, ret;
- /* Return early if the key is out of bounds */
- ret = apple_smc_get_key_by_index(smc, 0, &first_key);
- if (ret)
- return ret;
- if (key <= first_key)
- return -ENODEV;
-
- ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &last_key);
- if (ret)
- return ret;
- if (key > last_key)
+ if (key <= smc->first_key || key > smc->last_key)
return -ENODEV;
/* Binary search to find index of first SMC key bigger or equal to key */
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 18c682e1a..91ca7031c 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1245,7 +1245,7 @@ config SENSORS_LTQ_CPUTEMP
config SENSORS_MACSMC_HWMON
tristate "Apple SMC (Apple Silicon)"
- depends on MFD_MACSMC && OF
+ depends on MFD_MACSMC_CORE
help
This driver enables hwmon support for current, power, temperature,
and voltage sensors, as well as fan speed reporting and control
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 87ed55c15..c58cc3315 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -339,24 +339,15 @@ config MFD_LOCHNAGAR
help
Support for Cirrus Logic Lochnagar audio development board.
-config MFD_MACSMC
- tristate "Apple Silicon System Management Controller (SMC)"
- depends on ARCH_APPLE || COMPILE_TEST
- depends on OF
- depends on APPLE_RTKIT
+config MFD_MACSMC_CORE
+ tristate "Apple System Management Controller (SMC) Core"
select MFD_CORE
help
- The System Management Controller (SMC) on Apple Silicon machines is a
+ The System Management Controller (SMC) on Apple machines is a
piece of hardware that exposes various functionalities such as
temperature sensors, voltage/power meters, shutdown/reboot handling,
GPIOs and more.
- Communication happens via a shared mailbox using the RTKit protocol
- which is also used for other co-processors. The SMC protocol then
- allows reading and writing many different keys which implement the
- various features. The MFD core device handles this protocol and
- exposes it to the sub-devices.
-
config MFD_MADERA
tristate "Cirrus Logic Madera codecs"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f8101d2a9..0b0ccf91e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -22,7 +22,7 @@ obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o
obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o
-obj-$(CONFIG_MFD_MACSMC) += macsmc.o
+obj-$(CONFIG_MFD_MACSMC_CORE) += macsmc-core.o
obj-$(CONFIG_MFD_TI_LP873X) += lp873x.o
obj-$(CONFIG_MFD_TI_LP87565) += lp87565.o
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
new file mode 100644
index 000000000..b8905c834
--- /dev/null
+++ b/drivers/mfd/macsmc-core.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC core framework
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+static const struct mfd_cell apple_smc_rtkit_devs[] = {
+ MFD_CELL_NAME("macsmc-input"),
+ MFD_CELL_NAME("macsmc-power"),
+ MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"),
+ MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"),
+ MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"),
+ MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"),
+};
+
+/*
+ * There isn't device tree on acpi backend, so we need to use
+ * apple_smc_key_exists to register mfd devs
+ */
+static const struct mfd_cell apple_smc_acpi_common[] = {
+ MFD_CELL_NAME("macsmc-hwmon"),
+ MFD_CELL_NAME("macsmc-power"),
+};
+
+static const struct mfd_cell apple_smc_acpi_rtc[] = {
+ MFD_CELL_NAME("macsmc-rtc"),
+};
+
+int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+ guard(mutex)(&smc->mutex);
+ return smc->be->read(smc->be_cookie, key, buf, size);
+}
+EXPORT_SYMBOL(apple_smc_read);
+
+int apple_smc_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
+{
+ guard(mutex)(&smc->mutex);
+ return smc->be->write(smc->be_cookie, key, buf, size);
+}
+EXPORT_SYMBOL(apple_smc_write);
+
+int apple_smc_enter_atomic(struct apple_smc *smc)
+{
+ guard(mutex)(&smc->mutex);
+ return smc->be->enter_atomic(smc->be_cookie);
+}
+EXPORT_SYMBOL(apple_smc_enter_atomic);
+
+int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
+{
+ guard(spinlock_irqsave)(&smc->lock);
+ return smc->be->write_atomic(smc->be_cookie, key, buf, size);
+}
+EXPORT_SYMBOL(apple_smc_write_atomic);
+
+int apple_smc_rw(struct apple_smc *smc, smc_key key, const void *wbuf, size_t wsize,
+ void *rbuf, size_t rsize)
+{
+ guard(mutex)(&smc->mutex);
+ return smc->be->rw(smc->be_cookie, key, wbuf, wsize, rbuf, rsize);
+}
+EXPORT_SYMBOL(apple_smc_rw);
+
+int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
+{
+ guard(mutex)(&smc->mutex);
+ int ret;
+
+ ret = smc->be->get_key_by_index(smc->be_cookie, index, key);
+ *key = swab32(*key);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_get_key_by_index);
+
+int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info)
+{
+ guard(mutex)(&smc->mutex);
+ return smc->be->get_key_info(smc->be_cookie, key, info);
+}
+EXPORT_SYMBOL(apple_smc_get_key_info);
+
+static void apple_smc_disable_notifications(void *data)
+{
+ struct apple_smc *smc = data;
+
+ guard(mutex)(&smc->mutex);
+ smc->be->disable_notifications(smc->be_cookie);
+}
+
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
+ void *cookie, struct blocking_notifier_head *event_handlers, bool is_acpi)
+{
+ struct apple_smc *smc;
+ __be32 count;
+ int ret;
+
+ smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
+ if (!smc)
+ return -ENOMEM;
+
+ smc->dev = dev;
+ smc->be_cookie = cookie;
+ smc->be = ops;
+ smc->is_acpi = is_acpi;
+ smc->event_handlers = event_handlers;
+ mutex_init(&smc->mutex);
+ spin_lock_init(&smc->lock);
+
+ ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
+ if (ret < 0)
+ return ret;
+ smc->key_count = be32_to_cpu(count);
+
+ ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key);
+ if (ret < 0)
+ return ret;
+
+ ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key);
+ if (ret < 0)
+ return ret;
+
+ dev_set_drvdata(dev, smc);
+
+ ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc);
+ if (ret)
+ return ret;
+
+ if (smc->is_acpi) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_common,
+ ARRAY_SIZE(apple_smc_acpi_common), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (apple_smc_key_exists(smc, SMC_KEY(CLKO))) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_rtc,
+ ARRAY_SIZE(apple_smc_acpi_rtc), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ } else
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
+ ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "Successfully Initialized (%d keys First key: %p4ch Last key: %p4ch)\n",
+ smc->key_count, &smc->first_key, &smc->last_key);
+ return 0;
+}
+EXPORT_SYMBOL(apple_smc_probe);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC core");
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index bce996bbe..14d62afc1 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -142,7 +142,7 @@ config POWER_RESET_MCF_RCM
config POWER_RESET_MACSMC
tristate "Apple SMC reset/power-off driver"
- depends on MFD_MACSMC
+ depends on MACSMC_RTKIT
help
This driver supports reset and power-off on Apple Mac machines
that implement this functionality via the SMC.
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index b89ef40df..6e944cc59 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -1193,7 +1193,7 @@ config FUEL_GAUGE_MM8013
config MACSMC_POWER
tristate "Apple SMC Battery and Power Driver"
- depends on MFD_MACSMC
+ depends on MFD_MACSMC_CORE
help
This driver provides support for the battery and AC adapter on
Apple Silicon machines. It exposes battery telemetry (voltage,
diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c
index ef735f863..ced37a701 100644
--- a/drivers/power/supply/macsmc-power.c
+++ b/drivers/power/supply/macsmc-power.c
@@ -859,7 +859,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
return -ENODEV;
power->nb.notifier_call = macsmc_power_event;
- blocking_notifier_chain_register(&smc->event_handlers, &power->nb);
+ blocking_notifier_chain_register(smc->event_handlers, &power->nb);
return 0;
}
@@ -868,7 +868,7 @@ static void macsmc_power_remove(struct platform_device *pdev)
{
struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
- blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb);
+ blocking_notifier_chain_unregister(power->smc->event_handlers, &power->nb);
}
static const struct platform_device_id macsmc_power_id[] = {
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 05b9233b9..8cc27891b 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -2096,7 +2096,7 @@ config RTC_DRV_WILCO_EC
config RTC_DRV_MACSMC
tristate "Apple Mac System Management Controller RTC"
- depends on MFD_MACSMC
+ depends on MFD_MACSMC_CORE
help
If you say yes here you get support for RTC functions
inside Apple SPMI PMUs accessed through the SoC's
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index db30c0ade..e89bc9cb4 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -52,6 +52,20 @@ config APPLE_TUNABLE
tristate
depends on ARCH_APPLE || COMPILE_TEST
+config MACSMC_RTKIT
+ tristate "Apple Silicon System Management Controller (SMC) Backend"
+ depends on ARCH_APPLE || COMPILE_TEST
+ depends on OF
+ depends on APPLE_RTKIT
+ depends on MFD_MACSMC_CORE
+ help
+ MACSMC RTKIT allows communicatation with the System Management
+ Controller on M-series Macs. It is required to use GPIO, control fans
+ and to control battery start and end threshold.
+
+ Say 'y' here if you have an Apple Soc
+
+
endmenu
endif
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 0481f7f43..7227cf69e 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -13,3 +13,5 @@ apple-sart-y = sart.o
obj-$(CONFIG_APPLE_TUNABLE) += apple-tunable.o
apple-tunable-y = tunable.o
+
+obj-$(CONFIG_MACSMC_RTKIT) += macsmc-rtkit.o
diff --git a/drivers/mfd/macsmc.c b/drivers/soc/apple/macsmc-rtkit.c
similarity index 70%
rename from drivers/mfd/macsmc.c
rename to drivers/soc/apple/macsmc-rtkit.c
index 514cba7dc..eaebeb1be 100644
--- a/drivers/mfd/macsmc.c
+++ b/drivers/soc/apple/macsmc-rtkit.c
@@ -11,7 +11,6 @@
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/math.h>
-#include <linux/mfd/core.h>
#include <linux/mfd/macsmc.h>
#include <linux/notifier.h>
#include <linux/of.h>
@@ -27,14 +26,6 @@
#define SMC_SHMEM_SIZE 0x1000
#define SMC_MAX_SIZE 255
-#define SMC_MSG_READ_KEY 0x10
-#define SMC_MSG_WRITE_KEY 0x11
-#define SMC_MSG_GET_KEY_BY_INDEX 0x12
-#define SMC_MSG_GET_KEY_INFO 0x13
-#define SMC_MSG_INITIALIZE 0x17
-#define SMC_MSG_NOTIFICATION 0x18
-#define SMC_MSG_RW_KEY 0x20
-
#define SMC_DATA GENMASK_ULL(63, 32)
#define SMC_WSIZE GENMASK_ULL(31, 24)
#define SMC_SIZE GENMASK_ULL(23, 16)
@@ -44,24 +35,35 @@
#define SMC_TIMEOUT_MS 500
-static const struct mfd_cell apple_smc_devs[] = {
- MFD_CELL_NAME("macsmc-input"),
- MFD_CELL_NAME("macsmc-power"),
- MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"),
- MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"),
- MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"),
- MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"),
+struct apple_smc_rtkit {
+ struct device *dev;
+
+ struct blocking_notifier_head event_handlers;
+
+ struct apple_rtkit *rtk;
+
+ struct completion init_done;
+ enum apple_smc_boot_stage boot_stage;
+
+ struct resource *sram;
+ void __iomem *sram_base;
+ struct apple_rtkit_shmem shmem;
+
+ unsigned int msg_id;
+
+ bool atomic_mode;
+ bool atomic_pending;
+ struct completion cmd_done;
+ u64 cmd_ret;
};
-static int apple_smc_cmd_locked(struct apple_smc *smc, u64 cmd, u64 arg,
+static int apple_smc_rtkit_cmd_locked(struct apple_smc_rtkit *smc, u64 cmd, u64 arg,
u64 size, u64 wsize, u32 *ret_data)
{
u8 result;
int ret;
u64 msg;
- lockdep_assert_held(&smc->mutex);
-
if (smc->boot_stage != APPLE_SMC_INITIALIZED)
return -EIO;
if (smc->atomic_mode)
@@ -103,15 +105,7 @@ static int apple_smc_cmd_locked(struct apple_smc *smc, u64 cmd, u64 arg,
return FIELD_GET(SMC_SIZE, smc->cmd_ret);
}
-static int apple_smc_cmd(struct apple_smc *smc, u64 cmd, u64 arg,
- u64 size, u64 wsize, u32 *ret_data)
-{
- guard(mutex)(&smc->mutex);
-
- return apple_smc_cmd_locked(smc, cmd, arg, size, wsize, ret_data);
-}
-
-static int apple_smc_rw_locked(struct apple_smc *smc, smc_key key,
+static int apple_smc_rtkit_rw_locked(struct apple_smc_rtkit *smc, smc_key key,
const void *wbuf, size_t wsize,
void *rbuf, size_t rsize)
{
@@ -120,8 +114,6 @@ static int apple_smc_rw_locked(struct apple_smc *smc, smc_key key,
int ret;
u64 cmd;
- lockdep_assert_held(&smc->mutex);
-
if (rsize > SMC_MAX_SIZE)
return -EINVAL;
if (wsize > SMC_MAX_SIZE)
@@ -149,7 +141,7 @@ static int apple_smc_rw_locked(struct apple_smc *smc, smc_key key,
return -EINVAL;
}
- ret = apple_smc_cmd_locked(smc, cmd, key, smc_size, smc_wsize, &rdata);
+ ret = apple_smc_rtkit_cmd_locked(smc, cmd, key, smc_size, smc_wsize, &rdata);
if (ret < 0)
return ret;
@@ -169,48 +161,42 @@ static int apple_smc_rw_locked(struct apple_smc *smc, smc_key key,
return ret;
}
-int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+static int apple_smc_rtkit_read(void *cookie, smc_key key, void *buf, size_t size)
{
- guard(mutex)(&smc->mutex);
+ struct apple_smc_rtkit *smc = cookie;
- return apple_smc_rw_locked(smc, key, NULL, 0, buf, size);
+ return apple_smc_rtkit_rw_locked(smc, key, NULL, 0, buf, size);
}
-EXPORT_SYMBOL(apple_smc_read);
-int apple_smc_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
+static int apple_smc_rtkit_write(void *cookie, smc_key key, const void *buf, size_t size)
{
- guard(mutex)(&smc->mutex);
+ struct apple_smc_rtkit *smc = cookie;
- return apple_smc_rw_locked(smc, key, buf, size, NULL, 0);
+ return apple_smc_rtkit_rw_locked(smc, key, buf, size, NULL, 0);
}
-EXPORT_SYMBOL(apple_smc_write);
-int apple_smc_rw(struct apple_smc *smc, smc_key key, const void *wbuf, size_t wsize,
+static int apple_smc_rtkit_rw(void *cookie, smc_key key, const void *wbuf, size_t wsize,
void *rbuf, size_t rsize)
{
- guard(mutex)(&smc->mutex);
+ struct apple_smc_rtkit *smc = cookie;
- return apple_smc_rw_locked(smc, key, wbuf, wsize, rbuf, rsize);
+ return apple_smc_rtkit_rw_locked(smc, key, wbuf, wsize, rbuf, rsize);
}
-EXPORT_SYMBOL(apple_smc_rw);
-int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
+static int apple_smc_rtkit_get_key_by_index(void *cookie, int index, smc_key *key)
{
- int ret;
+ struct apple_smc_rtkit *smc = cookie;
- ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key);
-
- *key = swab32(*key);
- return ret;
+ return apple_smc_rtkit_cmd_locked(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key);
}
-EXPORT_SYMBOL(apple_smc_get_key_by_index);
-int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info)
+static int apple_smc_rtkit_get_key_info(void *cookie, smc_key key, struct apple_smc_key_info *info)
{
+ struct apple_smc_rtkit *smc = cookie;
u8 key_info[6];
int ret;
- ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL);
+ ret = apple_smc_rtkit_cmd_locked(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL);
if (ret >= 0 && info) {
memcpy_fromio(key_info, smc->shmem.iomem, sizeof(key_info));
info->size = key_info[0];
@@ -219,12 +205,9 @@ int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_
}
return ret;
}
-EXPORT_SYMBOL(apple_smc_get_key_info);
-int apple_smc_enter_atomic(struct apple_smc *smc)
+static int apple_smc_rtkit_enter_atomic(void *cookie)
{
- guard(mutex)(&smc->mutex);
-
/*
* Disable notifications since this is called before shutdown and no
* notification handler will be able to handle the notification
@@ -233,19 +216,19 @@ int apple_smc_enter_atomic(struct apple_smc *smc)
* We can't use apple_smc_write_flag here since that would try to lock
* smc->mutex again.
*/
+ struct apple_smc_rtkit *smc = cookie;
const u8 flag = 0;
- apple_smc_rw_locked(smc, SMC_KEY(NTAP), &flag, sizeof(flag), NULL, 0);
+ apple_smc_rtkit_rw_locked(smc, SMC_KEY(NTAP), &flag, sizeof(flag), NULL, 0);
smc->atomic_mode = true;
return 0;
}
-EXPORT_SYMBOL(apple_smc_enter_atomic);
-int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
+static int apple_smc_rtkit_write_atomic(void *cookie, smc_key key, const void *buf, size_t size)
{
- guard(spinlock_irqsave)(&smc->lock);
+ struct apple_smc_rtkit *smc = cookie;
u8 result;
int ret;
u64 msg;
@@ -293,11 +276,28 @@ int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, const void *buf,
return FIELD_GET(SMC_SIZE, smc->cmd_ret);
}
-EXPORT_SYMBOL(apple_smc_write_atomic);
+
+static void apple_smc_rtkit_disable_notifications(void *cookie)
+{
+ const u8 flag = 0;
+
+ apple_smc_rtkit_write(cookie, SMC_KEY(NTAP), &flag, sizeof(flag));
+}
+
+static const struct apple_smc_backend_ops apple_smc_rtkit_backend_ops = {
+ .read = apple_smc_rtkit_read,
+ .write = apple_smc_rtkit_write,
+ .enter_atomic = apple_smc_rtkit_enter_atomic,
+ .write_atomic = apple_smc_rtkit_write_atomic,
+ .rw = apple_smc_rtkit_rw,
+ .get_key_by_index = apple_smc_rtkit_get_key_by_index,
+ .get_key_info = apple_smc_rtkit_get_key_info,
+ .disable_notifications = apple_smc_rtkit_disable_notifications,
+};
static void apple_smc_rtkit_crashed(void *cookie, const void *bfr, size_t bfr_len)
{
- struct apple_smc *smc = cookie;
+ struct apple_smc_rtkit *smc = cookie;
smc->boot_stage = APPLE_SMC_ERROR_CRASHED;
dev_err(smc->dev, "SMC crashed! Your system will reboot in a few seconds...\n");
@@ -305,7 +305,7 @@ static void apple_smc_rtkit_crashed(void *cookie, const void *bfr, size_t bfr_le
static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
{
- struct apple_smc *smc = cookie;
+ struct apple_smc_rtkit *smc = cookie;
size_t bfr_end;
if (!bfr->iova) {
@@ -332,7 +332,7 @@ static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *b
static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message)
{
- struct apple_smc *smc = cookie;
+ struct apple_smc_rtkit *smc = cookie;
if (endpoint != SMC_ENDPOINT) {
dev_warn(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
@@ -368,7 +368,7 @@ static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message)
static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message)
{
- struct apple_smc *smc = cookie;
+ struct apple_smc_rtkit *smc = cookie;
if (endpoint != SMC_ENDPOINT) {
dev_warn(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
@@ -392,32 +392,24 @@ static const struct apple_rtkit_ops apple_smc_rtkit_ops = {
static void apple_smc_rtkit_shutdown(void *data)
{
- struct apple_smc *smc = data;
+ struct apple_smc_rtkit *smc = data;
/* Shut down SMC firmware, if it's not completely wedged */
if (apple_rtkit_is_running(smc->rtk))
apple_rtkit_quiesce(smc->rtk);
}
-static void apple_smc_disable_notifications(void *data)
-{
- struct apple_smc *smc = data;
-
- apple_smc_write_flag(smc, SMC_KEY(NTAP), false);
-}
-
-static int apple_smc_probe(struct platform_device *pdev)
+static int apple_smc_rtkit_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct apple_smc *smc;
- __be32 count;
+ struct apple_smc_rtkit *smc;
+ const u8 flag = 1;
int ret;
smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
if (!smc)
return -ENOMEM;
- mutex_init(&smc->mutex);
smc->dev = &pdev->dev;
smc->sram_base = devm_platform_get_and_ioremap_resource(pdev, 1, &smc->sram);
if (IS_ERR(smc->sram_base))
@@ -458,49 +450,32 @@ static int apple_smc_probe(struct platform_device *pdev)
return -EIO;
}
- dev_set_drvdata(&pdev->dev, smc);
BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
- ret = apple_smc_read(smc, SMC_KEY(#KEY), &count, sizeof(count));
- if (ret >= 0 && ret != sizeof(count))
- ret = -EINVAL;
- if (ret < 0)
- return dev_err_probe(smc->dev, ret, "Failed to get key count");
- smc->key_count = be32_to_cpu(count);
-
/* Enable notifications */
- apple_smc_write_flag(smc, SMC_KEY(NTAP), true);
- ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc);
- if (ret)
- return ret;
+ apple_smc_rtkit_rw_locked(smc, SMC_KEY(NTAP), &flag, sizeof(flag), NULL, 0);
- ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
- apple_smc_devs, ARRAY_SIZE(apple_smc_devs),
- NULL, 0, NULL);
- if (ret)
- return dev_err_probe(smc->dev, ret, "Failed to register sub-devices");
-
-
- return 0;
+ return apple_smc_probe(dev, &apple_smc_rtkit_backend_ops, smc,
+ &smc->event_handlers, false);
}
-static const struct of_device_id apple_smc_of_match[] = {
+static const struct of_device_id apple_smc_rtkit_of_match[] = {
{ .compatible = "apple,t8103-smc" },
{ .compatible = "apple,smc" },
{},
};
-MODULE_DEVICE_TABLE(of, apple_smc_of_match);
+MODULE_DEVICE_TABLE(of, apple_smc_rtkit_of_match);
-static struct platform_driver apple_smc_driver = {
+static struct platform_driver apple_smc_rtkit_driver = {
.driver = {
- .name = "macsmc",
- .of_match_table = apple_smc_of_match,
+ .name = "macsmc-rtkit",
+ .of_match_table = apple_smc_rtkit_of_match,
},
- .probe = apple_smc_probe,
+ .probe = apple_smc_rtkit_probe,
};
-module_platform_driver(apple_smc_driver);
+module_platform_driver(apple_smc_rtkit_driver);
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
MODULE_LICENSE("Dual MIT/GPL");
-MODULE_DESCRIPTION("Apple SMC driver");
+MODULE_DESCRIPTION("Apple SMC RTKIT Backend driver");
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
index cc09ecce0..4b4368b99 100644
--- a/include/linux/mfd/macsmc.h
+++ b/include/linux/mfd/macsmc.h
@@ -47,6 +47,14 @@ typedef u32 smc_key;
#define APPLE_SMC_WRITABLE BIT(6)
#define APPLE_SMC_FUNCTION BIT(4)
+#define SMC_MSG_READ_KEY 0x10
+#define SMC_MSG_WRITE_KEY 0x11
+#define SMC_MSG_GET_KEY_BY_INDEX 0x12
+#define SMC_MSG_GET_KEY_INFO 0x13
+#define SMC_MSG_INITIALIZE 0x17
+#define SMC_MSG_NOTIFICATION 0x18
+#define SMC_MSG_RW_KEY 0x20
+
/**
* struct apple_smc_key_info - Information for a SMC key as returned by SMC
* @type_code: FourCC code indicating the type for this key.
@@ -80,24 +88,41 @@ enum apple_smc_boot_stage {
APPLE_SMC_ERROR_CRASHED
};
+/**
+ * struct apple_smc_backend_ops - SMC backend interface
+ * @read: Read SMC key data
+ * @write: Write SMC key data
+ * @enter_atomic: Enter atomic access mode
+ * @write_atomic: Atomic write to SMC key
+ * @rw: Combined write/read transaction
+ * @get_key_by_index: Resolve key by index
+ * @get_key_info: Get metadata for key
+ * @disable_notifications: Disable notfications on removal
+ */
+struct apple_smc_backend_ops {
+ int (*read)(void *cookie, smc_key key, void *buf, size_t size);
+ int (*write)(void *cookie, smc_key key, const void *buf, size_t size);
+ int (*enter_atomic)(void *cookie);
+ int (*write_atomic)(void *cookie, smc_key key, const void *buf, size_t size);
+ int (*rw)(void *cookie, smc_key key, const void *wbuf, size_t wsize,
+ void *rbuf, size_t rsize);
+ int (*get_key_by_index)(void *cookie, int index, smc_key *key);
+ int (*get_key_info)(void *cookie, smc_key key,
+ struct apple_smc_key_info *info);
+ void (*disable_notifications)(void *cookie);
+};
+
+
/**
* struct apple_smc
* @dev: Underlying device struct for the physical backend device
* @key_count: Number of available SMC keys
* @first_key: First valid SMC key
* @last_key: Last valid SMC key
- * @event_handlers: Notifier call chain for events received from SMC
- * @rtk: Pointer to Apple RTKit instance
- * @init_done: Completion for initialization
- * @boot_stage: Current boot stage of SMC
- * @sram: Pointer to SRAM resource
- * @sram_base: SRAM base address
- * @shmem: RTKit shared memory structure for SRAM
- * @msg_id: Current message id for commands, will be incremented for each command
- * @atomic_mode: Flag set when atomic mode is entered
- * @atomic_pending: Flag indicating pending atomic command
- * @cmd_done: Completion for command execution in non-atomic mode
- * @cmd_ret: Return value from SMC for last command
+ * @event_handlers: Pointer to Notifier call chain from the backend
+ * @be: Backend operations for SMC access
+ * @be_cookie: Backend context passed to ops callbacks
+ * @is_acpi: Is the backend ACPI based
* @mutex: Mutex for non-atomic mode
* @lock: Spinlock for atomic mode
*/
@@ -108,23 +133,11 @@ struct apple_smc {
smc_key first_key;
smc_key last_key;
- struct blocking_notifier_head event_handlers;
-
- struct apple_rtkit *rtk;
+ struct blocking_notifier_head *event_handlers;
+ const struct apple_smc_backend_ops *be;
+ void *be_cookie;
- struct completion init_done;
- enum apple_smc_boot_stage boot_stage;
-
- struct resource *sram;
- void __iomem *sram_base;
- struct apple_rtkit_shmem shmem;
-
- unsigned int msg_id;
-
- bool atomic_mode;
- bool atomic_pending;
- struct completion cmd_done;
- u64 cmd_ret;
+ bool is_acpi;
struct mutex mutex;
spinlock_t lock;
@@ -225,6 +238,18 @@ static inline bool apple_smc_key_exists(struct apple_smc *smc, smc_key key)
return apple_smc_get_key_info(smc, key, NULL) >= 0;
}
+/**
+ * apple_smc_probe - Initialize Apple SMC device
+ * @dev: Device for the SMC instance
+ * @ops: Backend operations for SMC access
+ * @cookie: Opaque backend context
+ * @event_handlers: Pointer to Notifier call chain from backend
+ * @is_acpi: Is the backend ACPI based.
+ * Return: Zero on success, negative errno on error
+ */
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
+ void *cookie, struct blocking_notifier_head *event_handlers, bool is_acpi);
+
#define APPLE_SMC_TYPE_OPS(type) \
static inline int apple_smc_read_##type(struct apple_smc *smc, smc_key key, type *p) \
{ \
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 01/14] treewide: Add backend for macsmc
2026-10-29 8:51 ` [RFC PATCH 01/14] treewide: Add backend for macsmc Atharva Tiwari
@ 2026-10-29 9:20 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:20 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] mfd: macsmc: Kconfig split disables Apple SMC drivers in arm64 defconfig
- [Medium] mfd: macsmc: missing notification cleanup on apple_smc_probe() error
- [Low] mfd: macsmc: endianness type mismatch in apple_smc_probe() #KEY read
--
commit f3824312f6532aec08553002200ae557d8b3b0da
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
treewide: Add backend for macsmc
This commit splits the Apple SMC driver into a core MFD driver
(drivers/mfd/macsmc-core.c) and an RTKit backend driver
(drivers/soc/apple/macsmc-rtkit.c) so that additional SMC backends such as
ACPI on Intel Macs can share the core and sub-drivers.
[ ... ]
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 87ed55c155f03..c58cc3315f22a 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -339,24 +339,15 @@ config MFD_LOCHNAGAR
> help
> Support for Cirrus Logic Lochnagar audio development board.
>
> -config MFD_MACSMC
> - tristate "Apple Silicon System Management Controller (SMC)"
> - depends on ARCH_APPLE || COMPILE_TEST
> - depends on OF
> - depends on APPLE_RTKIT
> +config MFD_MACSMC_CORE
> + tristate "Apple System Management Controller (SMC) Core"
> select MFD_CORE
[Severity: High]
Does removing this symbol without updating arch/arm64/configs/defconfig or
providing defaults cause a regression where all Apple SMC drivers become
disabled on arm64 builds?
In arch/arm64/configs/defconfig:
CONFIG_MFD_MACSMC=m
With that symbol removed and neither the new core option nor the new rtkit
backend option in drivers/soc/apple/Kconfig having a default, running
olddefconfig or building arm64 defconfig drops the unrecognized symbol and
disables the core, backend, and all dependent sub-drivers (gpio, reset,
hwmon, power supply, and rtc).
Should arch/arm64/configs/defconfig be updated and/or should the rtkit
backend select the core option and default to m or y when the Apple platform
dependencies are enabled?
[ ... ]
> diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
> new file mode 100644
> index 0000000000000..b8905c834c448
> --- /dev/null
> +++ b/drivers/mfd/macsmc-core.c
> @@ -0,0 +1,164 @@
[ ... ]
> +int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
> + void *cookie, struct blocking_notifier_head *event_handlers, bool is_acpi)
> +{
> + struct apple_smc *smc;
> + __be32 count;
> + int ret;
> +
> + smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
> + if (!smc)
> + return -ENOMEM;
> +
> + smc->dev = dev;
> + smc->be_cookie = cookie;
> + smc->be = ops;
> + smc->is_acpi = is_acpi;
> + smc->event_handlers = event_handlers;
> + mutex_init(&smc->mutex);
> + spin_lock_init(&smc->lock);
> +
> + ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
> + if (ret < 0)
> + return ret;
> + smc->key_count = be32_to_cpu(count);
[Severity: Low]
Doesn't passing &count (declared as __be32) to apple_smc_read_u32() trigger
a sparse endianness warning?
In include/linux/mfd/macsmc.h, apple_smc_read_u32() expects a host-endian
u32 * pointer:
static inline int apple_smc_read_u32(struct apple_smc *smc, smc_key key, u32 *p)
{
int ret = apple_smc_read(smc, key, p, sizeof(*p));
return (ret < 0) ? ret : ((ret != sizeof(*p)) ? -EINVAL : 0);
}
Previously, apple_smc_probe() called apple_smc_read() directly with &count,
which took a void * buffer. Should apple_smc_probe() either call
apple_smc_read() as before or use a helper that accepts __be32?
[ ... ]
> diff --git a/drivers/mfd/macsmc.c b/drivers/soc/apple/macsmc-rtkit.c
> similarity index 70%
> rename from drivers/mfd/macsmc.c
> rename to drivers/soc/apple/macsmc-rtkit.c
> index 514cba7dc897a..eaebeb1be3734 100644
> --- a/drivers/mfd/macsmc.c
> +++ b/drivers/soc/apple/macsmc-rtkit.c
[ ... ]
> @@ -458,49 +450,32 @@ static int apple_smc_probe(struct platform_device *pdev)
> return -EIO;
> }
>
> - dev_set_drvdata(&pdev->dev, smc);
> BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
>
> - ret = apple_smc_read(smc, SMC_KEY(#KEY), &count, sizeof(count));
> - if (ret >= 0 && ret != sizeof(count))
> - ret = -EINVAL;
> - if (ret < 0)
> - return dev_err_probe(smc->dev, ret, "Failed to get key count");
> - smc->key_count = be32_to_cpu(count);
> -
> /* Enable notifications */
> - apple_smc_write_flag(smc, SMC_KEY(NTAP), true);
> - ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc);
> - if (ret)
> - return ret;
> + apple_smc_rtkit_rw_locked(smc, SMC_KEY(NTAP), &flag, sizeof(flag), NULL, 0);
>
> - ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
> - apple_smc_devs, ARRAY_SIZE(apple_smc_devs),
> - NULL, 0, NULL);
> - if (ret)
> - return dev_err_probe(smc->dev, ret, "Failed to register sub-devices");
> -
> -
> - return 0;
> + return apple_smc_probe(dev, &apple_smc_rtkit_backend_ops, smc,
> + &smc->event_handlers, false);
> }
[Severity: Medium]
If apple_smc_probe() fails before registering the
apple_smc_disable_notifications() cleanup action, won't SMC notifications
remain enabled on the error path?
In apple_smc_rtkit_probe(), notifications are enabled before calling
apple_smc_probe(). Inside apple_smc_probe() in drivers/mfd/macsmc-core.c,
several operations can fail and return early before
devm_add_action_or_reset() is called:
smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
if (!smc)
return -ENOMEM;
...
ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
if (ret < 0)
return ret;
smc->key_count = be32_to_cpu(count);
ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key);
if (ret < 0)
return ret;
ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key);
if (ret < 0)
return ret;
dev_set_drvdata(dev, smc);
ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc);
Previously, notifications were only enabled after reading the key count and
the devm cleanup action was registered immediately afterward. In addition,
apple_smc_disable_notifications(), apple_smc_enter_atomic(),
apple_smc_write_atomic(), and apple_smc_rw() in drivers/mfd/macsmc-core.c
unconditionally call backend ops without checking if the callback pointer is
null.
Could notification enabling and its devm cleanup action be kept together
(or guarded for backends that do not implement all callbacks)?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=1
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
2026-10-29 8:51 ` [RFC PATCH 01/14] treewide: Add backend for macsmc Atharva Tiwari
@ 2026-10-29 8:51 ` Atharva Tiwari
2026-10-29 9:23 ` sashiko-bot
2026-10-29 12:59 ` Ilpo Järvinen
2026-10-29 8:52 ` [RFC PATCH 03/14] watchdog: add driver for apple smc Atharva Tiwari
` (11 subsequent siblings)
13 siblings, 2 replies; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:51 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a backend that talks to the SMC on Intel Macs over ACPI.
It supports the old I/O port interface, and also MMIO and
interrupt-driven completion, which applesmc doesn't have.
If an interrupt is missed it falls back to polling.
This is the base that lets the macsmc drivers replace
applesmc.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/macsmc-acpi.c | 642 +++++++++++++++++++++++++++++
3 files changed, 653 insertions(+)
create mode 100644 drivers/platform/x86/macsmc-acpi.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 4851bf451..448aa2db6 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -202,6 +202,16 @@ config ADV_SWBUTTON
To compile this driver as a module, choose M here. The module will
be called adv_swbutton.
+config MACSMC_ACPI
+ tristate "Apple SMC (System Management Controller) ACPI Backend"
+ depends on MFD_MACSMC_CORE && ACPI && !SENSORS_APPLESMC
+ help
+ This driver allows communication with the SMC (System Management Controller)
+ on Intel-Macs. It is used to control fans, control battery, and more.
+
+ To compile this driver as a module, choose M here. The module will
+ be called macsmc_acpi.
+
config APPLE_GMUX
tristate "Apple Gmux Driver"
depends on ACPI && PCI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index cab20a414..933aed4a8 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -28,6 +28,7 @@ obj-y += amd/
obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
# Apple
+obj-$(CONFIG_MACSMC_ACPI) += macsmc-acpi.o
obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
# ASUS
diff --git a/drivers/platform/x86/macsmc-acpi.c b/drivers/platform/x86/macsmc-acpi.c
new file mode 100644
index 000000000..a06ec921a
--- /dev/null
+++ b/drivers/platform/x86/macsmc-acpi.c
@@ -0,0 +1,642 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC ACPI backend
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ *
+ * PIO code taken from drivers/hwmon/applesmc.c:
+ * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
+ * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
+ */
+
+#include <linux/acpi.h>
+#include <linux/byteorder/generic.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kfifo.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/spinlock.h>
+#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+
+#define SMC_IO_DATA_PORT 0
+#define SMC_IO_CMD_PORT 4
+#define SMC_IO_NR_PORTS 32
+
+#define SMC_IO_STATUS_AWAITING_DATA BIT(0)
+#define SMC_IO_STATUS_IB_CLOSED BIT(1)
+#define SMC_IO_STATUS_BUSY BIT(2)
+
+#define SMC_MMIO_KEY_DATA 0
+#define SMC_MMIO_KEY_INTERRUPT_STATUS 0x4000
+#define SMC_MMIO_KEY_STATUS 0x4005
+#define SMC_MMIO_KEY_NAME 0x78
+#define SMC_MMIO_KEY_DATA_LEN 0x7D
+#define SMC_MMIO_KEY_SMC_ID 0x7E
+#define SMC_MMIO_KEY_CMD 0x7F
+#define SMC_MMIO_MIN_SIZE 0x4006
+
+#define SMC_TIMEOUT_MS 1000
+
+struct apple_smc_acpi {
+ struct device *dev;
+
+ u8 __iomem *iomem_base;
+ u32 iomem_base_addr, iomem_base_size;
+ bool is_mmio;
+
+ u16 port_base;
+ bool has_port;
+
+ bool has_interrupt;
+ bool interrupt_enabled;
+ struct completion cmd_done;
+
+ struct blocking_notifier_head event_handlers;
+ struct work_struct shutdown_work;
+ struct work_struct notifier_work;
+
+ DECLARE_KFIFO(event_fifo, u8, 64);
+ spinlock_t event_lock;
+};
+
+
+static int apple_smc_acpi_port_wait(struct apple_smc_acpi *smc, u8 val, u8 mask)
+{
+ int ms;
+
+ for (ms = SMC_TIMEOUT_MS; ms != 0; ms--) {
+ if ((inb(smc->port_base + SMC_IO_CMD_PORT) & mask) == val)
+ return 0;
+
+ fsleep(1000);
+ }
+ dev_dbg(smc->dev, "port/wait: timeout value:%u, mask:%u\n", val, mask);
+ return -ETIMEDOUT;
+}
+
+static int apple_smc_acpi_port_write_byte(struct apple_smc_acpi *smc, u8 buf)
+{
+ int ret;
+
+ ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_IB_CLOSED) ? :
+ apple_smc_acpi_port_wait(smc, SMC_IO_STATUS_BUSY, SMC_IO_STATUS_BUSY);
+ if (ret)
+ return ret;
+
+ outb(buf, smc->port_base + SMC_IO_DATA_PORT);
+ return 0;
+}
+
+static int apple_smc_acpi_port_write_cmd(struct apple_smc_acpi *smc, u8 buf)
+{
+ int ret;
+
+ ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_IB_CLOSED);
+ if (ret)
+ return ret;
+
+ outb(buf, smc->port_base + SMC_IO_CMD_PORT);
+ return 0;
+}
+
+static int apple_smc_acpi_port_write_key(struct apple_smc_acpi *smc, smc_key key)
+{
+ int i;
+
+ for (i = 3; i >= 0; i--) {
+ if (apple_smc_acpi_port_write_byte(smc, (u8)(key >> (i * 8))))
+ return -EIO;
+ }
+ return 0;
+}
+
+static int apple_smc_acpi_port_valid(struct apple_smc_acpi *smc)
+{
+ int ret;
+
+ ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
+ if (!ret)
+ return ret;
+
+ ret = apple_smc_acpi_port_write_cmd(smc, SMC_MSG_READ_KEY);
+ if (ret)
+ return ret;
+
+ return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
+}
+
+static int apple_smc_acpi_port_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
+ void *buffer, size_t len)
+{
+ int ret;
+
+ ret = apple_smc_acpi_port_valid(smc);
+ if (ret)
+ return ret;
+
+ if (apple_smc_acpi_port_write_cmd(smc, cmd) || apple_smc_acpi_port_write_key(smc, key)) {
+ dev_dbg(smc->dev, "port/read: read argument failed, key: 0x%X, cmd: 0x%X\n",
+ key, cmd);
+ return -EIO;
+ }
+
+ if (apple_smc_acpi_port_write_byte(smc, len)) {
+ dev_dbg(smc->dev, "port/read: read len failed, key: 0x%X, len: 0x%lX\n", key, len);
+ return -EIO;
+ }
+
+ for (ret = 0; ret < len; ret++) {
+ if (apple_smc_acpi_port_wait(smc,
+ SMC_IO_STATUS_AWAITING_DATA | SMC_IO_STATUS_BUSY,
+ SMC_IO_STATUS_AWAITING_DATA | SMC_IO_STATUS_BUSY)) {
+ dev_dbg(smc->dev, "port/read: read failed, key: 0x%X\n", key);
+ return -EIO;
+ }
+ ((u8 *)buffer)[ret] = inb(smc->port_base + SMC_IO_DATA_PORT);
+ }
+
+ return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
+}
+
+static int apple_smc_acpi_port_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
+ size_t len)
+{
+ int ret;
+
+ ret = apple_smc_acpi_port_valid(smc);
+ if (ret)
+ return ret;
+
+ if (apple_smc_acpi_port_write_cmd(smc, SMC_MSG_WRITE_KEY) ||
+ apple_smc_acpi_port_write_key(smc, key)) {
+ dev_dbg(smc->dev, "port/write: write argument failed, key: 0x%X\n", key);
+ return -EIO;
+ }
+
+ if (apple_smc_acpi_port_write_byte(smc, len)) {
+ dev_dbg(smc->dev, "port/write: write len failed, key: 0x%X, len: 0x%lX\n",
+ key, len);
+ return -EIO;
+ }
+
+ for (ret = 0; ret < len; ret++) {
+ if (apple_smc_acpi_port_write_byte(smc, ((const u8 *)buffer)[ret])) {
+ dev_dbg(smc->dev, "port/write: write failed, key: 0x%X\n", key);
+ return -EIO;
+ }
+ }
+ return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
+}
+
+static int apple_smc_acpi_port_get_key_info(struct apple_smc_acpi *smc,
+ smc_key key, struct apple_smc_key_info *info)
+{
+ u8 buf[6];
+
+ if (apple_smc_acpi_port_read(smc, SMC_MSG_GET_KEY_INFO, key, buf, 6))
+ return -EIO;
+
+ if (!info)
+ return 0;
+
+ info->size = buf[0];
+ info->type_code = get_unaligned_be32(&buf[1]);
+ info->flags = buf[5];
+
+ return 0;
+}
+
+static void apple_smc_acpi_mmio_clear(struct apple_smc_acpi *smc)
+{
+ if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS))
+ return iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_STATUS);
+}
+
+static int apple_smc_acpi_mmio_wait(struct apple_smc_acpi *smc)
+{
+ int ms = SMC_TIMEOUT_MS;
+
+ if (smc->interrupt_enabled) {
+ if (wait_for_completion_timeout(&smc->cmd_done,
+ msecs_to_jiffies(SMC_TIMEOUT_MS)) > 0)
+ return 0;
+
+ smc->interrupt_enabled = 0;
+ dev_dbg(smc->dev, "mmio/wait: interrupt missed, reverting to polling\n");
+ }
+
+ for (ms = SMC_TIMEOUT_MS; ms != 0; ms--) {
+ if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) & 0x20)
+ return 0;
+
+ fsleep(1000);
+ }
+ dev_dbg(smc->dev, "mmio/wait: timeout");
+ return -ETIMEDOUT;
+}
+
+static int apple_smc_acpi_mmio_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
+ void *buffer, size_t len)
+{
+ u8 ret;
+
+ reinit_completion(&smc->cmd_done);
+ apple_smc_acpi_mmio_clear(smc);
+ iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
+ iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
+ iowrite8(cmd, smc->iomem_base + SMC_MMIO_KEY_CMD);
+
+ if (apple_smc_acpi_mmio_wait(smc))
+ return -EIO;
+
+ ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
+ if (ret) {
+ dev_dbg(smc->dev, "mmio/read: read failed, ret: 0x%X, key: 0x%X\n", ret, key);
+ return -ret;
+ }
+
+ memcpy_fromio(buffer, smc->iomem_base + SMC_MMIO_KEY_DATA, len);
+ return 0;
+}
+
+static int apple_smc_acpi_mmio_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
+ size_t len)
+{
+ u8 ret;
+
+ reinit_completion(&smc->cmd_done);
+ apple_smc_acpi_mmio_clear(smc);
+ iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
+ memcpy_toio(smc->iomem_base + SMC_MMIO_KEY_DATA, buffer, len);
+ iowrite8(len, smc->iomem_base + SMC_MMIO_KEY_DATA_LEN);
+ iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
+ iowrite8(SMC_MSG_WRITE_KEY, smc->iomem_base + SMC_MMIO_KEY_CMD);
+
+ if (apple_smc_acpi_mmio_wait(smc))
+ return -EIO;
+
+ ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
+ if (ret) {
+ dev_dbg(smc->dev, "mmio/write: write failed, ret: 0x%X, key: 0x%X\n", ret, key);
+ return -ret;
+ }
+
+ return 0;
+}
+
+static int apple_smc_acpi_mmio_get_key_info(struct apple_smc_acpi *smc, smc_key key,
+ struct apple_smc_key_info *info)
+{
+ u8 ret;
+
+ reinit_completion(&smc->cmd_done);
+ apple_smc_acpi_mmio_clear(smc);
+ iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
+ iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
+ iowrite8(SMC_MSG_GET_KEY_INFO, smc->iomem_base + SMC_MMIO_KEY_CMD);
+
+ if (apple_smc_acpi_mmio_wait(smc))
+ return -EIO;
+
+ ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
+ if (ret) {
+ dev_dbg(smc->dev, "mmio/get_key_info failed, ret: 0x%X, key: 0x%X\n", ret, key);
+ return -ret;
+ }
+
+
+ if (!info)
+ return 0;
+ info->type_code = cpu_to_be32(ioread32(smc->iomem_base + SMC_MMIO_KEY_DATA));
+ info->size = ioread8(smc->iomem_base + 5);
+ info->flags = ioread8(smc->iomem_base + 6);
+
+ return 0;
+}
+
+static void apple_smc_acpi_shutdown_work(struct work_struct *work)
+{
+ orderly_poweroff(true);
+}
+
+static void apple_smc_acpi_notifier_work(struct work_struct *work)
+{
+ struct apple_smc_acpi *smc =
+ container_of(work, struct apple_smc_acpi, notifier_work);
+
+ unsigned long flags;
+ uint8_t event;
+
+ for (;;) {
+ spin_lock_irqsave(&smc->event_lock, flags);
+
+ if (!kfifo_get(&smc->event_fifo, &event)) {
+ spin_unlock_irqrestore(&smc->event_lock, flags);
+ break;
+ }
+ spin_unlock_irqrestore(&smc->event_lock, flags);
+
+ blocking_notifier_call_chain(&smc->event_handlers, event, NULL);
+ }
+}
+
+static irqreturn_t apple_smc_acpi_irq(int irq, void *data)
+{
+ struct apple_smc_acpi *smc = data;
+ uint8_t status = 0, int_id = 0;
+ unsigned long flags;
+
+ if (smc->is_mmio) {
+ status = ioread8(smc->iomem_base + SMC_MMIO_KEY_INTERRUPT_STATUS);
+ if ((status & 0x20) == 0 && status != 0)
+ return IRQ_HANDLED;
+ } else {
+ status = inb(smc->port_base + SMC_IO_CMD_PORT);
+ if ((status & 0x40) == 0 && status != 0)
+ return IRQ_HANDLED;
+ }
+
+ int_id = inb(smc->port_base + 0x1F);
+ switch (int_id) {
+ case 0x4B: // SMC Command done
+ complete(&smc->cmd_done);
+ break;
+ case 0x40: // Shutdown Imminet
+ schedule_work(&smc->shutdown_work);
+ break;
+ case 0x41: // bridgeOS Crash
+ dev_crit(smc->dev, "BridgeOS Crashed!!!\n");
+ break;
+ case 0x2A: // ALS Change
+ case 0x54: // Thermal Level Changed
+ case 0x55: // PlatformFuntion
+ case 0x60: // SMS Drop
+ case 0x69: // SMS Orientation
+ case 0x6F: // SMS Shock
+ case 0x80: // PLimit Changed
+ spin_lock_irqsave(&smc->event_lock, flags);
+ if (!kfifo_put(&smc->event_fifo, int_id))
+ dev_warn_ratelimited(smc->dev, "Notification lost: %u\n", int_id);
+
+ spin_unlock_irqrestore(&smc->event_lock, flags);
+
+ schedule_work(&smc->notifier_work);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static acpi_status apple_smc_acpi_walk_resources(struct acpi_resource *res,
+ void *data)
+{
+ struct apple_smc_acpi *smc = data;
+ int ret = 0;
+
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IO:
+ if (!smc->has_port) {
+ if (res->data.io.address_length < SMC_IO_NR_PORTS)
+ return AE_ERROR;
+ smc->port_base = res->data.io.minimum;
+ smc->has_port = true;
+ }
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
+ if (!smc->is_mmio) {
+ if (res->data.fixed_memory32.address_length < SMC_MMIO_MIN_SIZE) {
+ dev_dbg(smc->dev, "found mmio support but its too small: %u\n",
+ res->data.fixed_memory32.address_length);
+ return AE_OK;
+ }
+ smc->iomem_base_addr = res->data.fixed_memory32.address;
+ smc->iomem_base_size = res->data.fixed_memory32.address_length;
+ smc->is_mmio = true;
+ }
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_IRQ:
+ ret = devm_request_irq(smc->dev, res->data.irq.interrupts[0],
+ apple_smc_acpi_irq,
+ 0, "macsmc-acpi", smc);
+ if (ret < 0) {
+ dev_dbg(smc->dev, "Unable to register interrupts\n");
+ smc->has_interrupt = false;
+ return AE_OK;
+ }
+ smc->has_interrupt = true;
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ if (smc->has_port)
+ return AE_OK;
+ else
+ return AE_NOT_FOUND;
+
+ default:
+ return AE_OK;
+ }
+}
+
+static void apple_smc_acpi_try_enable_mmio(struct apple_smc_acpi *smc)
+{
+ if (!smc->is_mmio)
+ return;
+
+ u8 ldkn;
+
+ smc->iomem_base = ioremap(smc->iomem_base_addr, smc->iomem_base_size);
+ if (!smc->iomem_base)
+ goto out;
+
+ if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) == 0xFF) {
+ dev_dbg(smc->dev, "mmio enable failed: init status is 0xff\n");
+ goto out_iomem;
+ }
+
+ if (apple_smc_acpi_mmio_read(smc, SMC_MSG_READ_KEY, SMC_KEY(LDKN), &ldkn, 1)) {
+ dev_dbg(smc->dev, "mmio enable failed: ldkn read failed\n");
+ goto out_iomem;
+ }
+
+ if (ldkn < 2) {
+ dev_dbg(smc->dev, "mmio enable failed: ldkn version less than 2\n");
+ goto out_iomem;
+ }
+ return;
+
+out_iomem:
+ iounmap(smc->iomem_base);
+out:
+ smc->is_mmio = false;
+}
+
+static void apple_smc_acpi_try_enable_interrupts(struct apple_smc_acpi *smc)
+{
+ if (!smc->has_interrupt)
+ return;
+
+ const u8 enable = 1;
+ int ret;
+
+ if (smc->is_mmio)
+ ret = apple_smc_acpi_mmio_write(smc, SMC_KEY(NTOK), &enable, 1);
+ else
+ ret = apple_smc_acpi_port_write(smc, SMC_KEY(NTOK), &enable, 1);
+
+ if (ret < 0) {
+ dev_dbg(smc->dev, "Unable to enable interrupts\n");
+ smc->interrupt_enabled = 0;
+ return;
+ }
+
+ smc->interrupt_enabled = 1;
+}
+
+static int apple_smc_acpi_read(void *cookie, smc_key key, void *buf, size_t size)
+{
+ struct apple_smc_acpi *smc = cookie;
+ int ret;
+
+ if (smc->is_mmio)
+ ret = apple_smc_acpi_mmio_read(smc, SMC_MSG_READ_KEY,
+ key, buf, size);
+ else
+ ret = apple_smc_acpi_port_read(smc, SMC_MSG_READ_KEY,
+ key, buf, size);
+
+ if (ret < 0)
+ return ret;
+
+ return size;
+
+}
+
+static int apple_smc_acpi_write(void *cookie, smc_key key, const void *buf, size_t size)
+{
+ struct apple_smc_acpi *smc = cookie;
+ int ret;
+
+ if (smc->is_mmio)
+ ret = apple_smc_acpi_mmio_write(smc, key, buf, size);
+ else
+ ret = apple_smc_acpi_port_write(smc, key, buf, size);
+
+ if (ret < 0)
+ return ret;
+
+ return size;
+}
+
+static int apple_smc_acpi_get_key_by_index(void *cookie, int index, smc_key *key)
+{
+ struct apple_smc_acpi *smc = cookie;
+
+ if (smc->is_mmio)
+ return apple_smc_acpi_mmio_read(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key) index,
+ key, 4);
+ else
+ return apple_smc_acpi_port_read(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key) index,
+ key, 4);
+
+}
+
+static int apple_smc_acpi_get_key_info(void *cookie, smc_key key,
+ struct apple_smc_key_info *info)
+{
+ struct apple_smc_acpi *smc = cookie;
+
+ if (smc->is_mmio)
+ return apple_smc_acpi_mmio_get_key_info(smc, key, info);
+ else
+ return apple_smc_acpi_port_get_key_info(smc, key, info);
+
+}
+
+static void apple_smc_acpi_notifications(void *cookie)
+{
+ const u8 buf = 0;
+
+ apple_smc_acpi_write(cookie, SMC_KEY(NTOK), &buf, 1);
+}
+
+static const struct apple_smc_backend_ops apple_smc_acpi_be_ops = {
+ .read = apple_smc_acpi_read,
+ .write = apple_smc_acpi_write,
+ .get_key_by_index = apple_smc_acpi_get_key_by_index,
+ .get_key_info = apple_smc_acpi_get_key_info,
+ .disable_notifications = apple_smc_acpi_notifications,
+};
+
+static int apple_smc_acpi_probe(struct platform_device *pdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct apple_smc_acpi *smc;
+ acpi_status status;
+
+ smc = devm_kzalloc(&pdev->dev, sizeof(*smc), GFP_KERNEL);
+ if (!smc)
+ return -ENOMEM;
+
+ smc->dev = &pdev->dev;
+ init_completion(&smc->cmd_done);
+ status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
+ apple_smc_acpi_walk_resources, smc);
+
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ apple_smc_acpi_try_enable_mmio(smc);
+ BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
+ INIT_WORK(&smc->shutdown_work, apple_smc_acpi_shutdown_work);
+ INIT_WORK(&smc->notifier_work, apple_smc_acpi_notifier_work);
+ spin_lock_init(&smc->event_lock);
+ INIT_KFIFO(smc->event_fifo);
+ apple_smc_acpi_try_enable_interrupts(smc);
+
+ return apple_smc_probe(smc->dev, &apple_smc_acpi_be_ops, smc, &smc->event_handlers, true);
+}
+
+static int apple_smc_acpi_resume(struct device *dev)
+{
+ struct apple_smc *smc = dev_get_drvdata(dev);
+ struct apple_smc_acpi *smc_acpi = smc->be_cookie;
+
+ /* NTOK may be set to 0 on resume */
+ if (smc_acpi->interrupt_enabled) {
+ smc_acpi->interrupt_enabled = 0;
+ apple_smc_acpi_try_enable_interrupts(smc_acpi);
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops apple_smc_acpi_ops = {
+ .resume = apple_smc_acpi_resume,
+};
+
+static const struct acpi_device_id apple_smc_acpi_ids[] = {
+ {"APP0001", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
+
+static struct platform_driver apple_smc_acpi_driver = {
+ .probe = apple_smc_acpi_probe,
+ .driver = {
+ .name = "macsmc-acpi",
+ .acpi_match_table = apple_smc_acpi_ids,
+ .pm = &apple_smc_acpi_ops,
+ },
+};
+module_platform_driver(apple_smc_acpi_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC ACPI backend");
+MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs
2026-10-29 8:51 ` [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs Atharva Tiwari
@ 2026-10-29 9:23 ` sashiko-bot
2026-10-29 12:59 ` Ilpo Järvinen
1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:23 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] platform/x86: macsmc-acpi: uncanceled work on teardown causes use-after-free
- [High] platform/x86: macsmc-acpi: premature IRQ registration before init in probe
- [High] platform/x86: macsmc-acpi: off-by-one MMIO offsets in get_key_info()
- [Medium] platform/x86: macsmc-acpi: MMIO mapping from ioremap() leaked on teardown
- [Medium] platform/x86: macsmc-acpi: broken status check in apple_smc_acpi_irq()
- [Medium] platform/x86: macsmc-acpi: unlocked SMC write in apple_smc_acpi_resume()
- [Medium] platform/x86: macsmc-acpi: return -EIO instead of negated SMC status byte
- [Medium] platform/x86: macsmc-acpi: broken SENSORS_APPLESMC exclusion and I/O claim
- [Low] platform/x86: macsmc-acpi: duplicate MODULE_DEVICE_TABLE() breaks module build
- [Low] platform/x86: macsmc-acpi: wrong %lX format specifier for size_t len
--
commit b68788a4e706e4d383f7127b7089a3bd694321c8
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
platform/x86: add backend for macsmc in intel macs
This commit adds an ACPI backend driver (macsmc-acpi) for the Apple System
Management Controller on Intel Macs, supporting both I/O port and MMIO
access as well as interrupt-driven command completion and event handling.
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 9ce384e0475ea..ce21277407fca 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -202,6 +202,16 @@ config ADV_SWBUTTON
> To compile this driver as a module, choose M here. The module will
> be called adv_swbutton.
>
> +config MACSMC_ACPI
> + tristate "Apple SMC (System Management Controller) ACPI Backend"
> + depends on MFD_MACSMC_CORE && ACPI && !SENSORS_APPLESMC
[Severity: Medium]
Because SENSORS_APPLESMC is a tristate symbol in drivers/hwmon/Kconfig,
!SENSORS_APPLESMC evaluates to m when CONFIG_SENSORS_APPLESMC=m, which
allows both CONFIG_SENSORS_APPLESMC=m and CONFIG_MACSMC_ACPI=m to be built
in the same configuration.
When both modules are present on an Intel Mac, udev can auto-load both
applesmc (via DMI modalias) and macsmc-acpi (via the APP0001 ACPI
modalias). Because apple_smc_acpi_walk_resources() and
apple_smc_acpi_probe() in drivers/platform/x86/macsmc-acpi.c record
smc->port_base without reserving the I/O port range via
devm_request_region(), applesmc_init()'s request_region(0x300, 32,
"applesmc") will not detect a conflict, allowing both drivers to access the
SMC ports concurrently without shared locking.
Should this dependency be SENSORS_APPLESMC=n, and should macsmc-acpi also
reserve its I/O port and MMIO regions via devm_request_region() and
devm_request_mem_region()?
[ ... ]
> diff --git a/drivers/platform/x86/macsmc-acpi.c b/drivers/platform/x86/macsmc-acpi.c
> new file mode 100644
> index 0000000000000..a06ec921ac320
> --- /dev/null
> +++ b/drivers/platform/x86/macsmc-acpi.c
[ ... ]
> +static int apple_smc_acpi_port_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
> + void *buffer, size_t len)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_valid(smc);
> + if (ret)
> + return ret;
> +
> + if (apple_smc_acpi_port_write_cmd(smc, cmd) || apple_smc_acpi_port_write_key(smc, key)) {
> + dev_dbg(smc->dev, "port/read: read argument failed, key: 0x%X, cmd: 0x%X\n",
> + key, cmd);
> + return -EIO;
> + }
> +
> + if (apple_smc_acpi_port_write_byte(smc, len)) {
> + dev_dbg(smc->dev, "port/read: read len failed, key: 0x%X, len: 0x%lX\n", key, len);
> + return -EIO;
> + }
[ ... ]
> +static int apple_smc_acpi_port_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
> + size_t len)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_valid(smc);
> + if (ret)
> + return ret;
> +
> + if (apple_smc_acpi_port_write_cmd(smc, SMC_MSG_WRITE_KEY) ||
> + apple_smc_acpi_port_write_key(smc, key)) {
> + dev_dbg(smc->dev, "port/write: write argument failed, key: 0x%X\n", key);
> + return -EIO;
> + }
> +
> + if (apple_smc_acpi_port_write_byte(smc, len)) {
> + dev_dbg(smc->dev, "port/write: write len failed, key: 0x%X, len: 0x%lX\n",
> + key, len);
> + return -EIO;
> + }
[Severity: Low]
In both apple_smc_acpi_port_read() and apple_smc_acpi_port_write(), len has
type size_t, which is unsigned int on 32-bit x86 (CONFIG_X86_32). Will
formatting len with 0x%lX instead of 0x%zX trigger a -Wformat warning on
32-bit builds?
[ ... ]
> +static int apple_smc_acpi_mmio_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
> + void *buffer, size_t len)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(cmd, smc->iomem_base + SMC_MMIO_KEY_CMD);
> +
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/read: read failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
> +
> + memcpy_fromio(buffer, smc->iomem_base + SMC_MMIO_KEY_DATA, len);
> + return 0;
> +}
> +
> +static int apple_smc_acpi_mmio_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
> + size_t len)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + memcpy_toio(smc->iomem_base + SMC_MMIO_KEY_DATA, buffer, len);
> + iowrite8(len, smc->iomem_base + SMC_MMIO_KEY_DATA_LEN);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(SMC_MSG_WRITE_KEY, smc->iomem_base + SMC_MMIO_KEY_CMD);
> +
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/write: write failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
> +
> + return 0;
> +}
> +
> +static int apple_smc_acpi_mmio_get_key_info(struct apple_smc_acpi *smc, smc_key key,
> + struct apple_smc_key_info *info)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(SMC_MSG_GET_KEY_INFO, smc->iomem_base + SMC_MMIO_KEY_CMD);
> +
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/get_key_info failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
[Severity: Medium]
In apple_smc_acpi_mmio_read(), apple_smc_acpi_mmio_write(), and
apple_smc_acpi_mmio_get_key_info(), ret is the 8-bit hardware SMC status
code read from SMC_MMIO_KEY_CMD (for example, 0x84 for key not found).
Returning -ret propagates a negated hardware status code rather than a
standard Linux errno value to callers of apple_smc_read(),
apple_smc_write(), apple_smc_get_key_by_index(), and
apple_smc_get_key_info(). Should these paths return -EIO on non-zero SMC
status, matching the PIO helpers and apple_smc_rtkit_cmd_locked()?
> +
> +
> + if (!info)
> + return 0;
> + info->type_code = cpu_to_be32(ioread32(smc->iomem_base + SMC_MMIO_KEY_DATA));
> + info->size = ioread8(smc->iomem_base + 5);
> + info->flags = ioread8(smc->iomem_base + 6);
> +
> + return 0;
> +}
[Severity: High]
In apple_smc_acpi_mmio_get_key_info(), ioread32(smc->iomem_base +
SMC_MMIO_KEY_DATA) reads the 4-byte type_code from offsets 0 through 3.
Doesn't reading info->size from smc->iomem_base + 5 and info->flags from
smc->iomem_base + 6 skip offset 4, placing the flags byte into info->size
and an uninitialized byte from offset 6 into info->flags? Shouldn't these
reads use offsets 4 and 5?
[ ... ]
> +static irqreturn_t apple_smc_acpi_irq(int irq, void *data)
> +{
> + struct apple_smc_acpi *smc = data;
> + uint8_t status = 0, int_id = 0;
> + unsigned long flags;
> +
> + if (smc->is_mmio) {
> + status = ioread8(smc->iomem_base + SMC_MMIO_KEY_INTERRUPT_STATUS);
> + if ((status & 0x20) == 0 && status != 0)
> + return IRQ_HANDLED;
> + } else {
> + status = inb(smc->port_base + SMC_IO_CMD_PORT);
> + if ((status & 0x40) == 0 && status != 0)
> + return IRQ_HANDLED;
> + }
> +
> + int_id = inb(smc->port_base + 0x1F);
[Severity: Medium]
In apple_smc_acpi_irq(), if status reads as 0 (no interrupt pending), the
condition ((status & 0x20) == 0 && status != 0) evaluates to false and
falls through to read inb(smc->port_base + 0x1F). Also, when the interrupt
status bit is not set, returning IRQ_HANDLED reports an unhandled interrupt
as handled, and in PIO mode checking (status & 0x40) == 0 can drop command
completion interrupts where bit 0x40 is not set.
Should apple_smc_acpi_irq() return IRQ_NONE when no SMC interrupt is
pending?
[ ... ]
> +static acpi_status apple_smc_acpi_walk_resources(struct acpi_resource *res,
> + void *data)
> +{
> + struct apple_smc_acpi *smc = data;
> + int ret = 0;
> +
> + switch (res->type) {
[ ... ]
> + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
> + if (!smc->is_mmio) {
> + if (res->data.fixed_memory32.address_length < SMC_MMIO_MIN_SIZE) {
> + dev_dbg(smc->dev, "found mmio support but its too small: %u\n",
> + res->data.fixed_memory32.address_length);
> + return AE_OK;
> + }
> + smc->iomem_base_addr = res->data.fixed_memory32.address;
> + smc->iomem_base_size = res->data.fixed_memory32.address_length;
> + smc->is_mmio = true;
> + }
> + return AE_OK;
> +
> + case ACPI_RESOURCE_TYPE_IRQ:
> + ret = devm_request_irq(smc->dev, res->data.irq.interrupts[0],
> + apple_smc_acpi_irq,
> + 0, "macsmc-acpi", smc);
> + if (ret < 0) {
> + dev_dbg(smc->dev, "Unable to register interrupts\n");
> + smc->has_interrupt = false;
> + return AE_OK;
> + }
> + smc->has_interrupt = true;
> + return AE_OK;
[Severity: High]
During apple_smc_acpi_probe() -> acpi_walk_resources() ->
apple_smc_acpi_walk_resources(), ACPI_RESOURCE_TYPE_FIXED_MEMORY32 sets
smc->is_mmio = true while smc->iomem_base is still NULL, and
ACPI_RESOURCE_TYPE_IRQ immediately registers and enables
apple_smc_acpi_irq() via devm_request_irq().
At that point in apple_smc_acpi_probe(), apple_smc_acpi_try_enable_mmio()
has not yet mapped smc->iomem_base, and smc->event_handlers,
smc->shutdown_work, smc->notifier_work, smc->event_lock, and
smc->event_fifo have not been initialized yet:
apple_smc_acpi_probe()
acpi_walk_resources(..., apple_smc_acpi_walk_resources, smc)
smc->is_mmio = true
devm_request_irq(..., apple_smc_acpi_irq, ...)
apple_smc_acpi_try_enable_mmio(smc)
BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers)
INIT_WORK(&smc->shutdown_work, ...)
INIT_WORK(&smc->notifier_work, ...)
spin_lock_init(&smc->event_lock)
INIT_KFIFO(smc->event_fifo)
Similarly, if MMIO validation fails in apple_smc_acpi_try_enable_mmio(),
iounmap(smc->iomem_base) is called before smc->is_mmio is set to false.
If an interrupt fires during this window (for example, if NTOK was left
enabled across a warm reboot/module reload, or during the LDKN read in
apple_smc_acpi_try_enable_mmio()), won't apple_smc_acpi_irq() dereference a
NULL or unmapped smc->iomem_base and access uninitialized spinlock, kfifo,
and work_struct fields?
[ ... ]
> +static void apple_smc_acpi_try_enable_mmio(struct apple_smc_acpi *smc)
> +{
> + if (!smc->is_mmio)
> + return;
> +
> + u8 ldkn;
> +
> + smc->iomem_base = ioremap(smc->iomem_base_addr, smc->iomem_base_size);
> + if (!smc->iomem_base)
> + goto out;
> +
> + if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) == 0xFF) {
> + dev_dbg(smc->dev, "mmio enable failed: init status is 0xff\n");
> + goto out_iomem;
> + }
> +
> + if (apple_smc_acpi_mmio_read(smc, SMC_MSG_READ_KEY, SMC_KEY(LDKN), &ldkn, 1)) {
> + dev_dbg(smc->dev, "mmio enable failed: ldkn read failed\n");
> + goto out_iomem;
> + }
> +
> + if (ldkn < 2) {
> + dev_dbg(smc->dev, "mmio enable failed: ldkn version less than 2\n");
> + goto out_iomem;
> + }
> + return;
> +
> +out_iomem:
> + iounmap(smc->iomem_base);
> +out:
> + smc->is_mmio = false;
> +}
[Severity: Medium]
When apple_smc_acpi_try_enable_mmio() succeeds, smc->iomem_base remains
mapped with unmanaged ioremap(), and nothing calls iounmap() if
apple_smc_probe() subsequently fails or when the driver is unbound.
Does this leak the smc->iomem_base MMIO mapping on teardown, and should
devm_ioremap() (with devm_iounmap() on fallback) be used instead?
[ ... ]
> +static int apple_smc_acpi_probe(struct platform_device *pdev)
> +{
> + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
> + struct apple_smc_acpi *smc;
> + acpi_status status;
> +
> + smc = devm_kzalloc(&pdev->dev, sizeof(*smc), GFP_KERNEL);
> + if (!smc)
> + return -ENOMEM;
> +
> + smc->dev = &pdev->dev;
> + init_completion(&smc->cmd_done);
> + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
> + apple_smc_acpi_walk_resources, smc);
> +
> + if (ACPI_FAILURE(status))
> + return -ENODEV;
> +
> + apple_smc_acpi_try_enable_mmio(smc);
> + BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
> + INIT_WORK(&smc->shutdown_work, apple_smc_acpi_shutdown_work);
> + INIT_WORK(&smc->notifier_work, apple_smc_acpi_notifier_work);
> + spin_lock_init(&smc->event_lock);
> + INIT_KFIFO(smc->event_fifo);
> + apple_smc_acpi_try_enable_interrupts(smc);
> +
> + return apple_smc_probe(smc->dev, &apple_smc_acpi_be_ops, smc, &smc->event_handlers, true);
> +}
[Severity: Critical]
In apple_smc_acpi_probe(), smc->shutdown_work and smc->notifier_work are
initialized with INIT_WORK(), and apple_smc_acpi_irq() schedules them via
schedule_work(). However, neither work item is ever cancelled if
apple_smc_probe() fails or when the device is unbound (and if
apple_smc_probe() fails before registering apple_smc_disable_notifications,
NTOK remains enabled).
Once devres frees struct apple_smc_acpi on teardown, can a pending or
running apple_smc_acpi_notifier_work() access freed memory via
smc->event_lock, smc->event_fifo, and smc->event_handlers? Could
devm_work_autocancel() be used here, and should interrupts be disabled and
synchronized before cancelling work on teardown?
> +
> +static int apple_smc_acpi_resume(struct device *dev)
> +{
> + struct apple_smc *smc = dev_get_drvdata(dev);
> + struct apple_smc_acpi *smc_acpi = smc->be_cookie;
> +
> + /* NTOK may be set to 0 on resume */
> + if (smc_acpi->interrupt_enabled) {
> + smc_acpi->interrupt_enabled = 0;
> + apple_smc_acpi_try_enable_interrupts(smc_acpi);
> + }
> + return 0;
> +}
> +
> +static const struct dev_pm_ops apple_smc_acpi_ops = {
> + .resume = apple_smc_acpi_resume,
> +};
[Severity: Medium]
In apple_smc_acpi_resume(), apple_smc_acpi_try_enable_interrupts() writes
the NTOK key to the SMC hardware and updates smc_acpi->interrupt_enabled
without holding smc->mutex.
If a child device or worker performs an SMC transaction concurrently during
resume, can these unsynchronized accesses race on the MMIO/PIO registers
and smc_acpi->cmd_done? Also, should apple_smc_acpi_ops use
DEFINE_SIMPLE_DEV_PM_OPS() with pm_sleep_ptr() so hibernation restore and
CONFIG_PM_SLEEP=n builds are handled properly?
> +
> +static const struct acpi_device_id apple_smc_acpi_ids[] = {
> + {"APP0001", 0},
> + {"", 0},
> +};
> +MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
> +
> +static struct platform_driver apple_smc_acpi_driver = {
> + .probe = apple_smc_acpi_probe,
> + .driver = {
> + .name = "macsmc-acpi",
> + .acpi_match_table = apple_smc_acpi_ids,
> + .pm = &apple_smc_acpi_ops,
> + },
> +};
> +module_platform_driver(apple_smc_acpi_driver);
> +
> +MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Apple SMC ACPI backend");
> +MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
[Severity: Low]
MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids) is invoked twice in
drivers/platform/x86/macsmc-acpi.c (and linux/module.h is not explicitly
included). When building with CONFIG_MACSMC_ACPI=m, doesn't the duplicate
MODULE_DEVICE_TABLE() macro fail to compile due to redefining
__mod_device_table__kmod_macsmc_acpi__acpi__apple_smc_acpi_ids?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=2
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs
2026-10-29 8:51 ` [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs Atharva Tiwari
2026-10-29 9:23 ` sashiko-bot
@ 2026-10-29 12:59 ` Ilpo Järvinen
1 sibling, 0 replies; 34+ messages in thread
From: Ilpo Järvinen @ 2026-10-29 12:59 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Sebastian Reichel,
Alexandre Belloni, Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Paul Moore,
James Morris, Serge E. Hallyn, LKML, linux-pci, linux-acpi,
linux-gpio, asahi, linux-arm-kernel, linux-hwmon, linux-iio,
linux-leds, mfd, platform-driver-x86, linux-pm, linux-rtc,
linux-watchdog, linux-integrity, keyrings, linux-security-module
On Thu, 29 Oct 2026, Atharva Tiwari wrote:
> Add a backend that talks to the SMC on Intel Macs over ACPI.
> It supports the old I/O port interface, and also MMIO and
> interrupt-driven completion, which applesmc doesn't have.
> If an interrupt is missed it falls back to polling.
>
> This is the base that lets the macsmc drivers replace
> applesmc.
You have 72-75 chars. Don't split intentionally at . unless you really
start another paragraph.
>
> Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
> ---
> drivers/platform/x86/Kconfig | 10 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/macsmc-acpi.c | 642 +++++++++++++++++++++++++++++
> 3 files changed, 653 insertions(+)
> create mode 100644 drivers/platform/x86/macsmc-acpi.c
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 4851bf451..448aa2db6 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -202,6 +202,16 @@ config ADV_SWBUTTON
> To compile this driver as a module, choose M here. The module will
> be called adv_swbutton.
>
> +config MACSMC_ACPI
> + tristate "Apple SMC (System Management Controller) ACPI Backend"
> + depends on MFD_MACSMC_CORE && ACPI && !SENSORS_APPLESMC
> + help
> + This driver allows communication with the SMC (System Management Controller)
> + on Intel-Macs. It is used to control fans, control battery, and more.
> +
> + To compile this driver as a module, choose M here. The module will
> + be called macsmc_acpi.
> +
> config APPLE_GMUX
> tristate "Apple Gmux Driver"
> depends on ACPI && PCI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index cab20a414..933aed4a8 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -28,6 +28,7 @@ obj-y += amd/
> obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
>
> # Apple
> +obj-$(CONFIG_MACSMC_ACPI) += macsmc-acpi.o
> obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
>
> # ASUS
> diff --git a/drivers/platform/x86/macsmc-acpi.c b/drivers/platform/x86/macsmc-acpi.c
> new file mode 100644
> index 000000000..a06ec921a
> --- /dev/null
> +++ b/drivers/platform/x86/macsmc-acpi.c
> @@ -0,0 +1,642 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Apple SMC ACPI backend
> + * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
> + *
> + * PIO code taken from drivers/hwmon/applesmc.c:
> + * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
> + * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/byteorder/generic.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/kfifo.h>
> +#include <linux/mfd/macsmc.h>
> +#include <linux/platform_device.h>
> +#include <linux/reboot.h>
> +#include <linux/spinlock.h>
> +#include <linux/unaligned.h>
> +#include <linux/workqueue.h>
> +
> +#define SMC_IO_DATA_PORT 0
> +#define SMC_IO_CMD_PORT 4
> +#define SMC_IO_NR_PORTS 32
> +
> +#define SMC_IO_STATUS_AWAITING_DATA BIT(0)
> +#define SMC_IO_STATUS_IB_CLOSED BIT(1)
> +#define SMC_IO_STATUS_BUSY BIT(2)
Add include.
> +
> +#define SMC_MMIO_KEY_DATA 0
> +#define SMC_MMIO_KEY_INTERRUPT_STATUS 0x4000
> +#define SMC_MMIO_KEY_STATUS 0x4005
> +#define SMC_MMIO_KEY_NAME 0x78
> +#define SMC_MMIO_KEY_DATA_LEN 0x7D
> +#define SMC_MMIO_KEY_SMC_ID 0x7E
> +#define SMC_MMIO_KEY_CMD 0x7F
> +#define SMC_MMIO_MIN_SIZE 0x4006
> +
> +#define SMC_TIMEOUT_MS 1000
> +
> +struct apple_smc_acpi {
> + struct device *dev;
> +
> + u8 __iomem *iomem_base;
> + u32 iomem_base_addr, iomem_base_size;
> + bool is_mmio;
> +
> + u16 port_base;
> + bool has_port;
> +
> + bool has_interrupt;
> + bool interrupt_enabled;
> + struct completion cmd_done;
> +
> + struct blocking_notifier_head event_handlers;
> + struct work_struct shutdown_work;
> + struct work_struct notifier_work;
> +
> + DECLARE_KFIFO(event_fifo, u8, 64);
> + spinlock_t event_lock;
You must document what the lock protects.
> +};
> +
> +
> +static int apple_smc_acpi_port_wait(struct apple_smc_acpi *smc, u8 val, u8 mask)
> +{
> + int ms;
> +
> + for (ms = SMC_TIMEOUT_MS; ms != 0; ms--) {
> + if ((inb(smc->port_base + SMC_IO_CMD_PORT) & mask) == val)
> + return 0;
> +
> + fsleep(1000);
> + }
Something from iopoll.h should suffice for this.
> + dev_dbg(smc->dev, "port/wait: timeout value:%u, mask:%u\n", val, mask);
Add include.
> + return -ETIMEDOUT;
> +}
> +
> +static int apple_smc_acpi_port_write_byte(struct apple_smc_acpi *smc, u8 buf)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_IB_CLOSED) ? :
> + apple_smc_acpi_port_wait(smc, SMC_IO_STATUS_BUSY, SMC_IO_STATUS_BUSY);
Split this please.
> + if (ret)
> + return ret;
> +
> + outb(buf, smc->port_base + SMC_IO_DATA_PORT);
> + return 0;
> +}
> +
> +static int apple_smc_acpi_port_write_cmd(struct apple_smc_acpi *smc, u8 buf)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_IB_CLOSED);
> + if (ret)
> + return ret;
> +
> + outb(buf, smc->port_base + SMC_IO_CMD_PORT);
> + return 0;
> +}
> +
> +static int apple_smc_acpi_port_write_key(struct apple_smc_acpi *smc, smc_key key)
> +{
> + int i;
> +
> + for (i = 3; i >= 0; i--) {
> + if (apple_smc_acpi_port_write_byte(smc, (u8)(key >> (i * 8))))
I'd prefer ANDing with 0xff instead of relying on the cast.
> + return -EIO;
> + }
> + return 0;
> +}
> +
> +static int apple_smc_acpi_port_valid(struct apple_smc_acpi *smc)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
> + if (!ret)
> + return ret;
> +
> + ret = apple_smc_acpi_port_write_cmd(smc, SMC_MSG_READ_KEY);
> + if (ret)
> + return ret;
> +
> + return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
> +}
> +
> +static int apple_smc_acpi_port_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
> + void *buffer, size_t len)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_valid(smc);
> + if (ret)
> + return ret;
> +
> + if (apple_smc_acpi_port_write_cmd(smc, cmd) || apple_smc_acpi_port_write_key(smc, key)) {
> + dev_dbg(smc->dev, "port/read: read argument failed, key: 0x%X, cmd: 0x%X\n",
> + key, cmd);
> + return -EIO;
> + }
> +
> + if (apple_smc_acpi_port_write_byte(smc, len)) {
> + dev_dbg(smc->dev, "port/read: read len failed, key: 0x%X, len: 0x%lX\n", key, len);
> + return -EIO;
> + }
> +
> + for (ret = 0; ret < len; ret++) {
> + if (apple_smc_acpi_port_wait(smc,
> + SMC_IO_STATUS_AWAITING_DATA | SMC_IO_STATUS_BUSY,
> + SMC_IO_STATUS_AWAITING_DATA | SMC_IO_STATUS_BUSY)) {
> + dev_dbg(smc->dev, "port/read: read failed, key: 0x%X\n", key);
> + return -EIO;
> + }
> + ((u8 *)buffer)[ret] = inb(smc->port_base + SMC_IO_DATA_PORT);
Why is buffer void * and not u8 *?
> + }
> +
> + return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
> +}
> +
> +static int apple_smc_acpi_port_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
> + size_t len)
> +{
> + int ret;
> +
> + ret = apple_smc_acpi_port_valid(smc);
> + if (ret)
> + return ret;
> +
> + if (apple_smc_acpi_port_write_cmd(smc, SMC_MSG_WRITE_KEY) ||
> + apple_smc_acpi_port_write_key(smc, key)) {
> + dev_dbg(smc->dev, "port/write: write argument failed, key: 0x%X\n", key);
> + return -EIO;
> + }
> +
> + if (apple_smc_acpi_port_write_byte(smc, len)) {
> + dev_dbg(smc->dev, "port/write: write len failed, key: 0x%X, len: 0x%lX\n",
> + key, len);
> + return -EIO;
> + }
> +
> + for (ret = 0; ret < len; ret++) {
> + if (apple_smc_acpi_port_write_byte(smc, ((const u8 *)buffer)[ret])) {
Why is the buffer void *?
> + dev_dbg(smc->dev, "port/write: write failed, key: 0x%X\n", key);
> + return -EIO;
> + }
> + }
> + return apple_smc_acpi_port_wait(smc, 0, SMC_IO_STATUS_BUSY);
> +}
> +
> +static int apple_smc_acpi_port_get_key_info(struct apple_smc_acpi *smc,
> + smc_key key, struct apple_smc_key_info *info)
> +{
> + u8 buf[6];
> +
> + if (apple_smc_acpi_port_read(smc, SMC_MSG_GET_KEY_INFO, key, buf, 6))
sizeof(buf)
> + return -EIO;
> +
> + if (!info)
> + return 0;
> +
> + info->size = buf[0];
> + info->type_code = get_unaligned_be32(&buf[1]);
Please use a proper endianness type. It would be preferrable to make a
(__packed) struct out of this so you can properly name and type the
members. It would also get rid if magic number offsets.
Don't forget add include if you use __packed.
> + info->flags = buf[5];
> +
> + return 0;
> +}
> +
> +static void apple_smc_acpi_mmio_clear(struct apple_smc_acpi *smc)
> +{
> + if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS))
> + return iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_STATUS);
> +}
> +
> +static int apple_smc_acpi_mmio_wait(struct apple_smc_acpi *smc)
> +{
> + int ms = SMC_TIMEOUT_MS;
> +
> + if (smc->interrupt_enabled) {
> + if (wait_for_completion_timeout(&smc->cmd_done,
> + msecs_to_jiffies(SMC_TIMEOUT_MS)) > 0)
> + return 0;
> +
> + smc->interrupt_enabled = 0;
> + dev_dbg(smc->dev, "mmio/wait: interrupt missed, reverting to polling\n");
> + }
> +
> + for (ms = SMC_TIMEOUT_MS; ms != 0; ms--) {
> + if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) & 0x20)
Name the literal with a define.
> + return 0;
> +
> + fsleep(1000);
> + }
> + dev_dbg(smc->dev, "mmio/wait: timeout");
> + return -ETIMEDOUT;
> +}
> +
> +static int apple_smc_acpi_mmio_read(struct apple_smc_acpi *smc, u8 cmd, smc_key key,
> + void *buffer, size_t len)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(cmd, smc->iomem_base + SMC_MMIO_KEY_CMD);
> +
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/read: read failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
> +
> + memcpy_fromio(buffer, smc->iomem_base + SMC_MMIO_KEY_DATA, len);
> + return 0;
> +}
> +
> +static int apple_smc_acpi_mmio_write(struct apple_smc_acpi *smc, smc_key key, const void *buffer,
> + size_t len)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + memcpy_toio(smc->iomem_base + SMC_MMIO_KEY_DATA, buffer, len);
> + iowrite8(len, smc->iomem_base + SMC_MMIO_KEY_DATA_LEN);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(SMC_MSG_WRITE_KEY, smc->iomem_base + SMC_MMIO_KEY_CMD);
> +
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/write: write failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
> +
> + return 0;
> +}
> +
> +static int apple_smc_acpi_mmio_get_key_info(struct apple_smc_acpi *smc, smc_key key,
> + struct apple_smc_key_info *info)
> +{
> + u8 ret;
> +
> + reinit_completion(&smc->cmd_done);
> + apple_smc_acpi_mmio_clear(smc);
> + iowrite32be(key, smc->iomem_base + SMC_MMIO_KEY_NAME);
> + iowrite8(0, smc->iomem_base + SMC_MMIO_KEY_SMC_ID);
> + iowrite8(SMC_MSG_GET_KEY_INFO, smc->iomem_base + SMC_MMIO_KEY_CMD);
> + if (apple_smc_acpi_mmio_wait(smc))
> + return -EIO;
> +
> + ret = ioread8(smc->iomem_base + SMC_MMIO_KEY_CMD);
Don't name non-return-value variables "ret".
> + if (ret) {
> + dev_dbg(smc->dev, "mmio/get_key_info failed, ret: 0x%X, key: 0x%X\n", ret, key);
> + return -ret;
> + }
Largely identical to another function above? Add a helper?
> + if (!info)
> + return 0;
> + info->type_code = cpu_to_be32(ioread32(smc->iomem_base + SMC_MMIO_KEY_DATA));
> + info->size = ioread8(smc->iomem_base + 5);
> + info->flags = ioread8(smc->iomem_base + 6);
Name the literal indexes with defines.
> +
> + return 0;
> +}
> +
> +static void apple_smc_acpi_shutdown_work(struct work_struct *work)
> +{
> + orderly_poweroff(true);
> +}
> +
> +static void apple_smc_acpi_notifier_work(struct work_struct *work)
> +{
> + struct apple_smc_acpi *smc =
> + container_of(work, struct apple_smc_acpi, notifier_work);
> +
> + unsigned long flags;
> + uint8_t event;
Kernel uses u8 for internal variables.
> +
> + for (;;) {
> + spin_lock_irqsave(&smc->event_lock, flags);
> +
> + if (!kfifo_get(&smc->event_fifo, &event)) {
> + spin_unlock_irqrestore(&smc->event_lock, flags);
Please use scoped_guard() to avoid having to split unlock paths.
> + break;
> + }
> + spin_unlock_irqrestore(&smc->event_lock, flags);
> +
> + blocking_notifier_call_chain(&smc->event_handlers, event, NULL);
> + }
> +}
> +
> +static irqreturn_t apple_smc_acpi_irq(int irq, void *data)
> +{
> + struct apple_smc_acpi *smc = data;
> + uint8_t status = 0, int_id = 0;
> + unsigned long flags;
> +
> + if (smc->is_mmio) {
> + status = ioread8(smc->iomem_base + SMC_MMIO_KEY_INTERRUPT_STATUS);
> + if ((status & 0x20) == 0 && status != 0)
> + return IRQ_HANDLED;
> + } else {
> + status = inb(smc->port_base + SMC_IO_CMD_PORT);
> + if ((status & 0x40) == 0 && status != 0)
Name the literals with a define x2
(...) == 0 => !(...)
> + return IRQ_HANDLED;
> + }
> +
> + int_id = inb(smc->port_base + 0x1F);
> + switch (int_id) {
> + case 0x4B: // SMC Command done
Don't add comments like this at all but make all those named defines,
you clearly seem to know about they are so name them accordingly.
> + complete(&smc->cmd_done);
> + break;
> + case 0x40: // Shutdown Imminet
> + schedule_work(&smc->shutdown_work);
> + break;
> + case 0x41: // bridgeOS Crash
> + dev_crit(smc->dev, "BridgeOS Crashed!!!\n");
> + break;
> + case 0x2A: // ALS Change
> + case 0x54: // Thermal Level Changed
> + case 0x55: // PlatformFuntion
> + case 0x60: // SMS Drop
> + case 0x69: // SMS Orientation
> + case 0x6F: // SMS Shock
> + case 0x80: // PLimit Changed
> + spin_lock_irqsave(&smc->event_lock, flags);
> + if (!kfifo_put(&smc->event_fifo, int_id))
> + dev_warn_ratelimited(smc->dev, "Notification lost: %u\n", int_id);
> +
> + spin_unlock_irqrestore(&smc->event_lock, flags);
> +
> + schedule_work(&smc->notifier_work);
> + break;
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static acpi_status apple_smc_acpi_walk_resources(struct acpi_resource *res,
> + void *data)
Please align to ( or just use one line (you have long lines below anyway
so I don't know why you split this one).
> +{
> + struct apple_smc_acpi *smc = data;
> + int ret = 0;
Why you need to init this?
> +
> + switch (res->type) {
> + case ACPI_RESOURCE_TYPE_IO:
> + if (!smc->has_port) {
> + if (res->data.io.address_length < SMC_IO_NR_PORTS)
> + return AE_ERROR;
> + smc->port_base = res->data.io.minimum;
> + smc->has_port = true;
> + }
> + return AE_OK;
> +
> + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
> + if (!smc->is_mmio) {
> + if (res->data.fixed_memory32.address_length < SMC_MMIO_MIN_SIZE) {
> + dev_dbg(smc->dev, "found mmio support but its too small: %u\n",
> + res->data.fixed_memory32.address_length);
> + return AE_OK;
> + }
> + smc->iomem_base_addr = res->data.fixed_memory32.address;
> + smc->iomem_base_size = res->data.fixed_memory32.address_length;
> + smc->is_mmio = true;
> + }
> + return AE_OK;
> +
> + case ACPI_RESOURCE_TYPE_IRQ:
> + ret = devm_request_irq(smc->dev, res->data.irq.interrupts[0],
> + apple_smc_acpi_irq,
> + 0, "macsmc-acpi", smc);
Align to (
> + if (ret < 0) {
> + dev_dbg(smc->dev, "Unable to register interrupts\n");
> + smc->has_interrupt = false;
> + return AE_OK;
> + }
> + smc->has_interrupt = true;
> + return AE_OK;
> +
> + case ACPI_RESOURCE_TYPE_END_TAG:
> + if (smc->has_port)
> + return AE_OK;
> + else
> + return AE_NOT_FOUND;
Preferrably handle error first by reversing the logic.
Using else in such a construct unnecessary and should be dropped.
> +
> + default:
> + return AE_OK;
> + }
> +}
> +
> +static void apple_smc_acpi_try_enable_mmio(struct apple_smc_acpi *smc)
> +{
> + if (!smc->is_mmio)
> + return;
> +
> + u8 ldkn;
Wrong place.
> +
> + smc->iomem_base = ioremap(smc->iomem_base_addr, smc->iomem_base_size);
> + if (!smc->iomem_base)
> + goto out;
> +
> + if (ioread8(smc->iomem_base + SMC_MMIO_KEY_STATUS) == 0xFF) {
> + dev_dbg(smc->dev, "mmio enable failed: init status is 0xff\n");
> + goto out_iomem;
> + }
> +
> + if (apple_smc_acpi_mmio_read(smc, SMC_MSG_READ_KEY, SMC_KEY(LDKN), &ldkn, 1)) {
sizeof(ldkn) ?
> + dev_dbg(smc->dev, "mmio enable failed: ldkn read failed\n");
> + goto out_iomem;
> + }
> +
> + if (ldkn < 2) {
> + dev_dbg(smc->dev, "mmio enable failed: ldkn version less than 2\n");
> + goto out_iomem;
> + }
> + return;
> +
> +out_iomem:
> + iounmap(smc->iomem_base);
> +out:
> + smc->is_mmio = false;
> +}
> +
> +static void apple_smc_acpi_try_enable_interrupts(struct apple_smc_acpi *smc)
> +{
> + if (!smc->has_interrupt)
> + return;
> +
> + const u8 enable = 1;
> + int ret;
These are in wrong place.
> +
> + if (smc->is_mmio)
> + ret = apple_smc_acpi_mmio_write(smc, SMC_KEY(NTOK), &enable, 1);
> + else
> + ret = apple_smc_acpi_port_write(smc, SMC_KEY(NTOK), &enable, 1);
> +
> + if (ret < 0) {
> + dev_dbg(smc->dev, "Unable to enable interrupts\n");
> + smc->interrupt_enabled = 0;
> + return;
> + }
> +
> + smc->interrupt_enabled = 1;
> +}
> +
> +static int apple_smc_acpi_read(void *cookie, smc_key key, void *buf, size_t size)
> +{
> + struct apple_smc_acpi *smc = cookie;
> + int ret;
> +
> + if (smc->is_mmio)
> + ret = apple_smc_acpi_mmio_read(smc, SMC_MSG_READ_KEY,
> + key, buf, size);
> + else
> + ret = apple_smc_acpi_port_read(smc, SMC_MSG_READ_KEY,
> + key, buf, size);
> +
> + if (ret < 0)
> + return ret;
> +
> + return size;
> +
> +}
> +
> +static int apple_smc_acpi_write(void *cookie, smc_key key, const void *buf, size_t size)
> +{
> + struct apple_smc_acpi *smc = cookie;
> + int ret;
> +
> + if (smc->is_mmio)
> + ret = apple_smc_acpi_mmio_write(smc, key, buf, size);
> + else
> + ret = apple_smc_acpi_port_write(smc, key, buf, size);
> +
> + if (ret < 0)
> + return ret;
> +
> + return size;
> +}
> +
> +static int apple_smc_acpi_get_key_by_index(void *cookie, int index, smc_key *key)
> +{
> + struct apple_smc_acpi *smc = cookie;
> +
> + if (smc->is_mmio)
> + return apple_smc_acpi_mmio_read(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key) index,
> + key, 4);
> + else
> + return apple_smc_acpi_port_read(smc, SMC_MSG_GET_KEY_BY_INDEX, (smc_key) index,
> + key, 4);
> +
> +}
> +
> +static int apple_smc_acpi_get_key_info(void *cookie, smc_key key,
> + struct apple_smc_key_info *info)
> +{
> + struct apple_smc_acpi *smc = cookie;
> +
> + if (smc->is_mmio)
> + return apple_smc_acpi_mmio_get_key_info(smc, key, info);
> + else
> + return apple_smc_acpi_port_get_key_info(smc, key, info);
> +
> +}
> +
> +static void apple_smc_acpi_notifications(void *cookie)
> +{
> + const u8 buf = 0;
> +
> + apple_smc_acpi_write(cookie, SMC_KEY(NTOK), &buf, 1);
sizeof()
> +}
> +
> +static const struct apple_smc_backend_ops apple_smc_acpi_be_ops = {
> + .read = apple_smc_acpi_read,
> + .write = apple_smc_acpi_write,
> + .get_key_by_index = apple_smc_acpi_get_key_by_index,
> + .get_key_info = apple_smc_acpi_get_key_info,
> + .disable_notifications = apple_smc_acpi_notifications,
> +};
> +
> +static int apple_smc_acpi_probe(struct platform_device *pdev)
> +{
> + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
> + struct apple_smc_acpi *smc;
> + acpi_status status;
> +
> + smc = devm_kzalloc(&pdev->dev, sizeof(*smc), GFP_KERNEL);
> + if (!smc)
> + return -ENOMEM;
> +
> + smc->dev = &pdev->dev;
> + init_completion(&smc->cmd_done);
> + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
> + apple_smc_acpi_walk_resources, smc);
> +
> + if (ACPI_FAILURE(status))
> + return -ENODEV;
> +
> + apple_smc_acpi_try_enable_mmio(smc);
> + BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
> + INIT_WORK(&smc->shutdown_work, apple_smc_acpi_shutdown_work);
> + INIT_WORK(&smc->notifier_work, apple_smc_acpi_notifier_work);
Don't these need cancelling on remove?
> + spin_lock_init(&smc->event_lock);
> + INIT_KFIFO(smc->event_fifo);
> + apple_smc_acpi_try_enable_interrupts(smc);
> +
> + return apple_smc_probe(smc->dev, &apple_smc_acpi_be_ops, smc, &smc->event_handlers, true);
> +}
> +
> +static int apple_smc_acpi_resume(struct device *dev)
> +{
> + struct apple_smc *smc = dev_get_drvdata(dev);
> + struct apple_smc_acpi *smc_acpi = smc->be_cookie;
> +
> + /* NTOK may be set to 0 on resume */
> + if (smc_acpi->interrupt_enabled) {
> + smc_acpi->interrupt_enabled = 0;
> + apple_smc_acpi_try_enable_interrupts(smc_acpi);
> + }
> + return 0;
> +}
> +
> +static const struct dev_pm_ops apple_smc_acpi_ops = {
> + .resume = apple_smc_acpi_resume,
> +};
> +
> +static const struct acpi_device_id apple_smc_acpi_ids[] = {
> + {"APP0001", 0},
> + {"", 0},
{}
No comma on the terminator.
> +};
> +MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
> +
> +static struct platform_driver apple_smc_acpi_driver = {
> + .probe = apple_smc_acpi_probe,
> + .driver = {
> + .name = "macsmc-acpi",
> + .acpi_match_table = apple_smc_acpi_ids,
> + .pm = &apple_smc_acpi_ops,
> + },
> +};
> +module_platform_driver(apple_smc_acpi_driver);
> +
> +MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
> +MODULE_LICENSE("Dual MIT/GPL");
> +MODULE_DESCRIPTION("Apple SMC ACPI backend");
> +MODULE_DEVICE_TABLE(acpi, apple_smc_acpi_ids);
>
--
i.
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 03/14] watchdog: add driver for apple smc
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
2026-10-29 8:51 ` [RFC PATCH 01/14] treewide: Add backend for macsmc Atharva Tiwari
2026-10-29 8:51 ` [RFC PATCH 02/14] platform/x86: add backend for macsmc in intel macs Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:17 ` sashiko-bot
2026-10-29 13:30 ` Andy Shevchenko
2026-10-29 8:52 ` [RFC PATCH 04/14] iio/light: add new driver for macsmc Atharva Tiwari
` (10 subsequent siblings)
13 siblings, 2 replies; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a watchdog driver for the SMC on Intel Macs. It can
reset the machine if the system stops responding. It works
on both newer and older SMC versions.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/mfd/macsmc-core.c | 11 +++
drivers/watchdog/Kconfig | 11 +++
drivers/watchdog/Makefile | 2 +-
drivers/watchdog/macsmc_wdt.c | 140 ++++++++++++++++++++++++++++++++++
4 files changed, 163 insertions(+), 1 deletion(-)
create mode 100644 drivers/watchdog/macsmc_wdt.c
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index b8905c834..1c1469828 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -33,6 +33,10 @@ static const struct mfd_cell apple_smc_acpi_rtc[] = {
MFD_CELL_NAME("macsmc-rtc"),
};
+static const struct mfd_cell apple_smc_acpi_wdt[] = {
+ MFD_CELL_NAME("macsmc-wdt"),
+};
+
int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
{
guard(mutex)(&smc->mutex);
@@ -147,6 +151,13 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
return ret;
}
+ if (apple_smc_key_exists(smc, SMC_KEY(NATJ))) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_wdt,
+ ARRAY_SIZE(apple_smc_acpi_wdt), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
} else
ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f7d0a0c2c..854ab772f 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1810,6 +1810,17 @@ config SIEMENS_SIMATIC_IPC_WDT
To compile this driver as a module, choose M here: the module will be
called simatic-ipc-wdt.
+config MACSMC_WDT
+ tristate "Apple SMC Watchdog"
+ depends on X86 && MACSMC_ACPI
+ select WATCHDOG_CORE
+ help
+ This driver adds support for Watchdogs in Intel macs Using
+ Apple SMC.
+
+ To compile this driver as a module, choose M here: the module will be
+ called macsmc_wdt.
+
# M68K Architecture
config M54xx_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 9ac406677..f142a5d8d 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -158,7 +158,7 @@ obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o
obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_WDT) += simatic-ipc-wdt.o
-
+obj-$(CONFIG_MACSMC_WDT) += macsmc_wdt.o
# M68K Architecture
obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o
diff --git a/drivers/watchdog/macsmc_wdt.c b/drivers/watchdog/macsmc_wdt.c
new file mode 100644
index 000000000..38f68d0d8
--- /dev/null
+++ b/drivers/watchdog/macsmc_wdt.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Apple SMC Watchdog Driver
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ */
+
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int timeout = WATCHDOG_TIMEOUT; /* in secounds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+struct macsmc_wdt {
+ struct apple_smc *smc;
+ bool has_oswd; /* Newer version of watchdogs in macsmc */
+ struct watchdog_device wdd;
+};
+
+static int macsmc_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ struct macsmc_wdt *drv = watchdog_get_drvdata(wdd);
+ int ret;
+
+ if (timeout)
+ wdd->timeout = timeout;
+
+ if (drv->has_oswd) {
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(OSWD), (u16)(timeout << 8));
+ if (ret < 0)
+ return ret;
+ } else {
+ /*
+ * NATJ = 2, makes the watchdog do force restart if NATi == 0
+ * NATJ = 0, makes the watchdog do nothing if NATi == 0
+ */
+ if (timeout == 0) {
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATJ), 0);
+ if (ret < 0)
+ return ret;
+ }
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATi), (u16)(timeout << 8));
+ if (ret < 0)
+ return ret;
+
+ if (timeout != 0) {
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATJ), 2);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+static int macsmc_wdt_start(struct watchdog_device *wdd)
+{
+ return macsmc_wdt_set_timeout(wdd, wdd->timeout);
+}
+static int macsmc_wdt_stop(struct watchdog_device *wdd)
+{
+ return macsmc_wdt_set_timeout(wdd, 0);
+}
+
+static int macsmc_wdt_ping(struct watchdog_device *wdd)
+{
+ struct macsmc_wdt *drv = watchdog_get_drvdata(wdd);
+ int ret;
+
+ if (drv->has_oswd)
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(OSWD), (u16)(wdd->timeout << 8));
+ else
+ ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATi), (u16)(wdd->timeout << 8));
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct watchdog_ops macsmc_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = macsmc_wdt_start,
+ .stop = macsmc_wdt_stop,
+ .set_timeout = macsmc_wdt_set_timeout,
+ .ping = macsmc_wdt_ping,
+};
+
+static const struct watchdog_info macsmc_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "Macsmc Watchdog timer",
+};
+
+static int macsmc_wdt_probe(struct platform_device *pdev)
+{
+ struct macsmc_wdt *drv;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(struct macsmc_wdt), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ drv->smc = dev_get_drvdata(pdev->dev.parent);
+ if (apple_smc_key_exists(drv->smc, SMC_KEY(OSWD)))
+ drv->has_oswd = true;
+ else
+ drv->has_oswd = false;
+
+ drv->wdd.ops = &macsmc_wdt_ops;
+ drv->wdd.info = &macsmc_wdt_info;
+ drv->wdd.min_timeout = 1;
+ drv->wdd.max_timeout = 255;
+ watchdog_init_timeout(&drv->wdd, timeout, &pdev->dev);
+ watchdog_set_nowayout(&drv->wdd, nowayout);
+ watchdog_set_drvdata(&drv->wdd, drv);
+
+ return devm_watchdog_register_device(&pdev->dev, &drv->wdd);
+}
+
+static struct platform_driver macsmc_wdt_driver = {
+ .probe = macsmc_wdt_probe,
+ .driver = {
+ .name = "macsmc-wdt"
+ },
+};
+module_platform_driver(macsmc_wdt_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC Watchdog Driver");
+MODULE_ALIAS("platform:macsmc-wdt");
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 03/14] watchdog: add driver for apple smc
2026-10-29 8:52 ` [RFC PATCH 03/14] watchdog: add driver for apple smc Atharva Tiwari
@ 2026-10-29 9:17 ` sashiko-bot
2026-10-29 13:30 ` Andy Shevchenko
1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:17 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] watchdog: macsmc_wdt: u16 write to 8-bit NATJ key in macsmc_wdt_set_timeout()
- [High] watchdog: macsmc_wdt: macsmc_wdt_set_timeout() arms stopped watchdog
- [High] watchdog: macsmc_wdt: missing WDIOF_MAGICCLOSE, parent, and stop hooks
- [Medium] watchdog: macsmc_wdt: uninitialized default timeout in macsmc_wdt_probe()
- [Medium] mfd: macsmc: apple_smc_probe() only checks NATJ key for macsmc-wdt
- [Low] watchdog: macsmc_wdt: undocumented nowayout and timeout module parameters
--
commit 4d753d432c6f9bfd023f7ab788e1134dd8d67d8e
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
watchdog: add driver for apple smc
This patch adds a watchdog driver for the Apple SMC on Intel Macs and
registers the macsmc-wdt MFD device when the NATJ SMC key is present.
> diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
> index b8905c834c448..1c14698285456 100644
> --- a/drivers/mfd/macsmc-core.c
> +++ b/drivers/mfd/macsmc-core.c
> @@ -33,6 +33,10 @@ static const struct mfd_cell apple_smc_acpi_rtc[] = {
> MFD_CELL_NAME("macsmc-rtc"),
> };
>
> +static const struct mfd_cell apple_smc_acpi_wdt[] = {
> + MFD_CELL_NAME("macsmc-wdt"),
> +};
> +
> int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
> {
> guard(mutex)(&smc->mutex);
> @@ -147,6 +151,13 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
> return ret;
> }
>
> + if (apple_smc_key_exists(smc, SMC_KEY(NATJ))) {
> + ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_wdt,
> + ARRAY_SIZE(apple_smc_acpi_wdt), NULL, 0, NULL);
> + if (ret < 0)
> + return ret;
> + }
[Severity: Medium]
In drivers/mfd/macsmc-core.c, apple_smc_probe() only checks for SMC_KEY(NATJ)
before registering apple_smc_acpi_wdt, whereas macsmc_wdt_probe() in
drivers/watchdog/macsmc_wdt.c checks for SMC_KEY(OSWD) on newer SMCs and
falls back to using both SMC_KEY(NATJ) and SMC_KEY(NATi) on older SMCs.
If a newer SMC provides SMC_KEY(OSWD) without SMC_KEY(NATJ), or if
SMC_KEY(NATJ) is present without SMC_KEY(NATi) and SMC_KEY(OSWD), will
device enumeration mismatch the keys required by macsmc_wdt? Should
apple_smc_probe() check for SMC_KEY(OSWD) || (SMC_KEY(NATJ) && SMC_KEY(NATi))
instead?
[ ... ]
> diff --git a/drivers/watchdog/macsmc_wdt.c b/drivers/watchdog/macsmc_wdt.c
> new file mode 100644
> index 0000000000000..38f68d0d87c10
> --- /dev/null
> +++ b/drivers/watchdog/macsmc_wdt.c
> @@ -0,0 +1,140 @@
> +// SPDX-License-Identifier: GPL-2.0 OR MIT
> +/*
> + * Apple SMC Watchdog Driver
> + *
> + * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
> + */
> +
> +#include <linux/mfd/macsmc.h>
> +#include <linux/platform_device.h>
> +#include <linux/watchdog.h>
> +
> +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default="
> + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static int timeout = WATCHDOG_TIMEOUT; /* in secounds */
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout,
> + "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
> + __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
[Severity: Low]
Since nowayout and timeout are exposed as kernel command-line and module
parameters via module_param() in drivers/watchdog/macsmc_wdt.c, should they
be documented in Documentation/admin-guide/kernel-parameters.txt (and
Documentation/watchdog/watchdog-parameters.rst)? Also, MODULE_PARM_DESC for
nowayout is missing an opening parenthesis before default=.
> +
> +struct macsmc_wdt {
> + struct apple_smc *smc;
> + bool has_oswd; /* Newer version of watchdogs in macsmc */
> + struct watchdog_device wdd;
> +};
> +
> +static int macsmc_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
> +{
> + struct macsmc_wdt *drv = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + if (timeout)
> + wdd->timeout = timeout;
[Severity: High]
When userspace stops the watchdog via ioctl(WDIOC_SETOPTIONS,
WDIOS_DISABLECARD) and then configures a new timeout via
ioctl(WDIOC_SETTIMEOUT), watchdog_set_timeout() in
drivers/watchdog/watchdog_dev.c invokes wdd->ops->set_timeout(wdd, timeout)
while WDOG_ACTIVE and WDOG_HW_RUNNING are cleared.
Because macsmc_wdt_set_timeout() unconditionally writes the non-zero timeout
to SMC_KEY(OSWD) or writes to SMC_KEY(NATi) and SMC_KEY(NATJ) without
checking watchdog_active(wdd), doesn't this immediately arm the hardware
countdown in the SMC while the watchdog core still considers the device
stopped?
In that state, watchdog_ping() sees !watchdog_hw_running(wdd) and returns 0
without feeding the hardware, and closing /dev/watchdog in watchdog_release()
sees !watchdog_active(wdd) and skips watchdog_stop(), leading to an
unstoppable system reset when the timer expires. Also, updating wdd->timeout
before the SMC writes complete leaves wdd->timeout out of sync if
apple_smc_write_u16() fails.
Should macsmc_wdt_set_timeout() only write to the SMC when
watchdog_active(wdd) is true (or when called from start/stop), and update
wdd->timeout only after any hardware writes succeed?
> +
> + if (drv->has_oswd) {
> + ret = apple_smc_write_u16(drv->smc, SMC_KEY(OSWD), (u16)(timeout << 8));
> + if (ret < 0)
> + return ret;
> + } else {
> + /*
> + * NATJ = 2, makes the watchdog do force restart if NATi == 0
> + * NATJ = 0, makes the watchdog do nothing if NATi == 0
> + */
> + if (timeout == 0) {
> + ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATJ), 0);
> + if (ret < 0)
> + return ret;
> + }
> + ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATi), (u16)(timeout << 8));
> + if (ret < 0)
> + return ret;
> +
> + if (timeout != 0) {
> + ret = apple_smc_write_u16(drv->smc, SMC_KEY(NATJ), 2);
> + if (ret < 0)
> + return ret;
> + }
> + }
[Severity: High]
On Apple SMC hardware, NATi and OSWD are 2-byte keys (ui16), whereas NATJ is
a 1-byte key (ui8). In macsmc_wdt_set_timeout(), calling
apple_smc_write_u16(drv->smc, SMC_KEY(NATJ), ...) passes a length of 2 bytes
to the SMC backend in drivers/platform/x86/macsmc-acpi.c:
drivers/platform/x86/macsmc-acpi.c:apple_smc_acpi_port_write() {
...
if (apple_smc_acpi_port_write_byte(smc, len)) {
dev_dbg(smc->dev, "port/write: write len failed, key: 0x%X, len: 0x%lX\n",
key, len);
return -EIO;
}
...
}
Won't writing 2 bytes to the 1-byte NATJ key cause apple_smc_acpi_port_write()
and apple_smc_acpi_mmio_write() to fail with -EIO when starting or stopping
the watchdog on machines without OSWD? (Additionally, even if NATJ were 16-bit,
passing unshifted 2 on little-endian x86 sends 0x0200 in big-endian.) Should
the NATJ writes use apple_smc_write_u8() instead of apple_smc_write_u16()?
[ ... ]
> +static const struct watchdog_info macsmc_wdt_info = {
> + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> + .identity = "Macsmc Watchdog timer",
> +};
> +
> +static int macsmc_wdt_probe(struct platform_device *pdev)
> +{
> + struct macsmc_wdt *drv;
> +
> + drv = devm_kzalloc(&pdev->dev, sizeof(struct macsmc_wdt), GFP_KERNEL);
> + if (!drv)
> + return -ENOMEM;
> +
> + drv->smc = dev_get_drvdata(pdev->dev.parent);
> + if (apple_smc_key_exists(drv->smc, SMC_KEY(OSWD)))
> + drv->has_oswd = true;
> + else
> + drv->has_oswd = false;
> +
> + drv->wdd.ops = &macsmc_wdt_ops;
> + drv->wdd.info = &macsmc_wdt_info;
> + drv->wdd.min_timeout = 1;
> + drv->wdd.max_timeout = 255;
> + watchdog_init_timeout(&drv->wdd, timeout, &pdev->dev);
[Severity: Medium]
In macsmc_wdt_probe(), drv is allocated with devm_kzalloc(), so
drv->wdd.timeout starts at 0 before calling watchdog_init_timeout(&drv->wdd,
timeout, &pdev->dev).
If a user passes timeout=0 (to request the default timeout) or an
out-of-range value (such as a negative value or > 255) to the module
parameter, watchdog_init_timeout() in drivers/watchdog/watchdog_core.c will
leave drv->wdd.timeout at 0. When /dev/watchdog is then opened,
macsmc_wdt_start() calls macsmc_wdt_set_timeout(wdd, 0), which runs the stop
sequence and leaves the hardware timer disabled while the kernel marks it
active.
Should drv->wdd.timeout be initialized to WATCHDOG_TIMEOUT before calling
watchdog_init_timeout(), and should the timeout module parameter be unsigned
int (uint)?
> + watchdog_set_nowayout(&drv->wdd, nowayout);
> + watchdog_set_drvdata(&drv->wdd, drv);
> +
> + return devm_watchdog_register_device(&pdev->dev, &drv->wdd);
> +}
> +
> +static struct platform_driver macsmc_wdt_driver = {
> + .probe = macsmc_wdt_probe,
> + .driver = {
> + .name = "macsmc-wdt"
> + },
> +};
[Severity: High]
There are a few lifecycle details in macsmc_wdt_info, macsmc_wdt_probe(), and
macsmc_wdt_driver that affect how the watchdog behaves on daemon crash,
reboot, unbind, and suspend:
1. macsmc_wdt_info.options omits WDIOF_MAGICCLOSE. In watchdog_release() in
drivers/watchdog/watchdog_dev.c, if WDIOF_MAGICCLOSE is not set and nowayout
is disabled, closing /dev/watchdog without writing the magic 'V' character
still stops the watchdog. If a userspace watchdog daemon crashes, won't the
kernel disarm the SMC watchdog instead of letting it reset the machine?
2. Because the Apple SMC is an independent always-on microcontroller, won't
an active watchdog keep counting down and forcibly reset the machine during
system reboot/shutdown, driver unbind, or system suspend unless
watchdog_stop_on_reboot(&drv->wdd), watchdog_stop_on_unregister(&drv->wdd),
and PM suspend/resume callbacks are provided?
3. Should macsmc_wdt_probe() also set drv->wdd.parent = &pdev->dev before
calling devm_watchdog_register_device()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=3
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [RFC PATCH 03/14] watchdog: add driver for apple smc
2026-10-29 8:52 ` [RFC PATCH 03/14] watchdog: add driver for apple smc Atharva Tiwari
2026-10-29 9:17 ` sashiko-bot
@ 2026-10-29 13:30 ` Andy Shevchenko
1 sibling, 0 replies; 34+ messages in thread
From: Andy Shevchenko @ 2026-10-29 13:30 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-kernel, linux-pci, linux-acpi, linux-gpio, asahi,
linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
On Thu, Oct 29, 2026 at 04:52:00AM -0400, Atharva Tiwari wrote:
> Add a watchdog driver for the SMC on Intel Macs. It can
> reset the machine if the system stops responding. It works
> on both newer and older SMC versions.
...
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -1810,6 +1810,17 @@ config SIEMENS_SIMATIC_IPC_WDT
> To compile this driver as a module, choose M here: the module will be
> called simatic-ipc-wdt.
>
> +config MACSMC_WDT
> + tristate "Apple SMC Watchdog"
> + depends on X86 && MACSMC_ACPI
> + select WATCHDOG_CORE
> + help
> + This driver adds support for Watchdogs in Intel macs Using
> + Apple SMC.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called macsmc_wdt.
> +
> # M68K Architecture
>
> config M54xx_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 9ac406677..f142a5d8d 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -158,7 +158,7 @@ obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o
> obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
> obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o
> obj-$(CONFIG_SIEMENS_SIMATIC_IPC_WDT) += simatic-ipc-wdt.o
> -
> +obj-$(CONFIG_MACSMC_WDT) += macsmc_wdt.o
> # M68K Architecture
> obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o
In moth files the entries seems misplaced to me. Also, why did you touch
the blank line?
...
> +struct macsmc_wdt {
> + struct apple_smc *smc;
> + bool has_oswd; /* Newer version of watchdogs in macsmc */
> + struct watchdog_device wdd;
> +};
Try with `pahole`.
...
> + .probe = macsmc_wdt_probe,
> + .driver = {
> + .name = "macsmc-wdt"
Always add a comma to non-terminator entries in the initializers
(yes, there are some exceptions, but it's not the case here).
> + },
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 04/14] iio/light: add new driver for macsmc
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (2 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 03/14] watchdog: add driver for apple smc Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:17 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 05/14] iio/accel: add driver for apple SMS Atharva Tiwari
` (9 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a driver for the ambient light sensors, exposed through
the standard IIO interface with left and right channels.
This replaces the light sensor support in applesmc.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/iio/light/Kconfig | 11 ++
drivers/iio/light/Makefile | 1 +
drivers/iio/light/macsmc-light.c | 189 +++++++++++++++++++++++++++++++
drivers/mfd/macsmc-core.c | 11 ++
4 files changed, 212 insertions(+)
create mode 100644 drivers/iio/light/macsmc-light.c
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 462fff1dd..f84707edc 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -457,6 +457,17 @@ config LV0104CS
To compile this driver as a module, choose M here:
the module will be called lv0104cs.
+config MACSMC_LIGHT
+ tristate "Apple SMC Light Sensor"
+ depends on MACSMC_ACPI
+ help
+ Say Y here if you have a Intel-based Mac. and you
+ want support for light sensors.
+
+ To compile this driver as a module, choose M here:
+ the module will be called macsmc_light
+
+
config MAX44000
tristate "MAX44000 Ambient and Infrared Proximity Sensor"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 6672ed01a..2ef4c054d 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_LTR390) += ltr390.o
obj-$(CONFIG_LTR501) += ltr501.o
obj-$(CONFIG_LTRF216A) += ltrf216a.o
obj-$(CONFIG_LV0104CS) += lv0104cs.o
+obj-$(CONFIG_MACSMC_LIGHT) += macsmc-light.o
obj-$(CONFIG_MAX44000) += max44000.o
obj-$(CONFIG_MAX44009) += max44009.o
obj-$(CONFIG_NOA1305) += noa1305.o
diff --git a/drivers/iio/light/macsmc-light.c b/drivers/iio/light/macsmc-light.c
new file mode 100644
index 000000000..9d4532b34
--- /dev/null
+++ b/drivers/iio/light/macsmc-light.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Apple SMC Light Sensor Driver
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ *
+ * Based on drivers/hwmom/applesmc.c:
+ * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
+ * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
+ */
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/byteorder/generic.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+#include <linux/unaligned.h>
+
+enum {
+ LEFT_LIGHT = 0,
+ RIGHT_LIGHT
+};
+
+struct macsmc_light {
+ struct apple_smc *smc;
+ struct notifier_block nb;
+ struct iio_dev *indio_dev;
+ u8 size;
+};
+
+static int macsmc_light_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+
+{
+ struct macsmc_light *drv = iio_priv(indio_dev);
+ u8 buffer[10];
+ int ret;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ switch (chan->channel) {
+ case LEFT_LIGHT:
+ ret = apple_smc_read(drv->smc, SMC_KEY(ALV0), buffer, drv->size);
+ if (ret < 0)
+ return ret;
+
+ if (drv->size == 10)
+ *val = be16_to_cpu(get_unaligned_be16(buffer + 6)) >> 10;
+ else
+ *val = buffer[2];
+
+ break;
+ case RIGHT_LIGHT:
+ if (drv->size == 10) {
+ *val = 0;
+ break;
+ }
+ ret = apple_smc_read(drv->smc, SMC_KEY(ALV1), buffer, drv->size);
+ if (ret < 0)
+ return ret;
+
+ *val = buffer[2];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static int macsmc_light_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_light *drv = container_of(nb, struct macsmc_light, nb);
+ s64 timestamp = iio_get_time_ns(drv->indio_dev);
+
+ /* check if event is for us */
+ if (event != 0x2A)
+ return NOTIFY_DONE;
+
+ iio_push_event(drv->indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ LEFT_LIGHT,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_EITHER),
+ timestamp);
+
+ if (drv->size != 10) {
+ iio_push_event(drv->indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ RIGHT_LIGHT,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_EITHER),
+ timestamp);
+ }
+
+ return NOTIFY_OK;
+}
+
+static void macsmc_light_unregister_notifier(void *data)
+{
+ struct macsmc_light *drv = data;
+
+ blocking_notifier_chain_unregister(drv->smc->event_handlers,
+ &drv->nb);
+}
+
+static const struct iio_event_spec macsmc_light_events[] = {
+ {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec macsmc_light_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .channel = LEFT_LIGHT,
+ .extend_name = "left",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .event_spec = macsmc_light_events,
+ .num_event_specs = ARRAY_SIZE(macsmc_light_events),
+ },
+ {
+ .type = IIO_LIGHT,
+ .channel = RIGHT_LIGHT,
+ .extend_name = "right",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .event_spec = macsmc_light_events,
+ .num_event_specs = ARRAY_SIZE(macsmc_light_events),
+ },
+};
+
+static const struct iio_info macsmc_light_info = {
+ .read_raw = macsmc_light_read_raw,
+};
+
+static int macsmc_light_probe(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev;
+ struct macsmc_light *drv;
+ struct apple_smc_key_info info;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*drv));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ drv = iio_priv(indio_dev);
+ drv->smc = dev_get_drvdata(pdev->dev.parent);
+
+ ret = apple_smc_get_key_info(drv->smc, SMC_KEY(ALV0), &info);
+ if (ret < 0)
+ return -ENODEV;
+
+ drv->size = info.size;
+ drv->indio_dev = indio_dev;
+ drv->nb.notifier_call = macsmc_light_event;
+ ret = blocking_notifier_chain_register(drv->smc->event_handlers, &drv->nb);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, macsmc_light_unregister_notifier, drv);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "macsmc-light";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &macsmc_light_info;
+ indio_dev->channels = macsmc_light_channels;
+ indio_dev->num_channels = ARRAY_SIZE(macsmc_light_channels);
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver macsmc_light_driver = {
+ .probe = macsmc_light_probe,
+ .driver = {
+ .name = "macsmc-light",
+ },
+};
+module_platform_driver(macsmc_light_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC Light Sensor Driver");
+MODULE_ALIAS("platform:macsmc-light");
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index 1c1469828..6014df4fe 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -29,6 +29,10 @@ static const struct mfd_cell apple_smc_acpi_common[] = {
MFD_CELL_NAME("macsmc-power"),
};
+static const struct mfd_cell apple_smc_acpi_light[] = {
+ MFD_CELL_NAME("macsmc-light"),
+};
+
static const struct mfd_cell apple_smc_acpi_rtc[] = {
MFD_CELL_NAME("macsmc-rtc"),
};
@@ -158,6 +162,13 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
return ret;
}
+ if (apple_smc_key_exists(smc, SMC_KEY(ALV0))) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_light,
+ ARRAY_SIZE(apple_smc_acpi_light), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
} else
ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 04/14] iio/light: add new driver for macsmc
2026-10-29 8:52 ` [RFC PATCH 04/14] iio/light: add new driver for macsmc Atharva Tiwari
@ 2026-10-29 9:17 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:17 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] iio: light: unvalidated drv->size in macsmc_light_read_raw() overflows buffer
- [Medium] iio: light: double byte-swap and wrong shift in macsmc_light_read_raw()
- [Medium] iio: light: missing event config callbacks in macsmc_light_info
- [Medium] iio: light: macsmc_light_probe() unconditionally registers RIGHT_LIGHT channel
- [Medium] iio: light: macsmc_light_channels omits indexed flag and uses extend_name
--
commit 03cf99563b174ff5a5c22b832e325621b25e1bec
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
iio/light: add new driver for macsmc
This commit adds an IIO ambient light sensor driver for Apple SMC devices
with left and right illuminance channels and change event support, and
registers the corresponding MFD cell in macsmc-core.
> diff --git a/drivers/iio/light/macsmc-light.c b/drivers/iio/light/macsmc-light.c
> new file mode 100644
> index 0000000000000..9d4532b34ef05
> --- /dev/null
> +++ b/drivers/iio/light/macsmc-light.c
> @@ -0,0 +1,189 @@
[ ... ]
> +static int macsmc_light_read_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2, long mask)
> +
> +{
> + struct macsmc_light *drv = iio_priv(indio_dev);
> + u8 buffer[10];
> + int ret;
> +
> + if (mask != IIO_CHAN_INFO_RAW)
> + return -EINVAL;
> +
> + switch (chan->channel) {
> + case LEFT_LIGHT:
> + ret = apple_smc_read(drv->smc, SMC_KEY(ALV0), buffer, drv->size);
> + if (ret < 0)
> + return ret;
> +
> + if (drv->size == 10)
> + *val = be16_to_cpu(get_unaligned_be16(buffer + 6)) >> 10;
[Severity: Medium]
Does this double-swap the endianness and shift away the sensor reading when
drv->size == 10?
In include/linux/unaligned.h, get_unaligned_be16() already converts the
big-endian value to CPU byte order:
static inline u16 get_unaligned_be16(const void *p)
{
return be16_to_cpu(__get_unaligned_t(__be16, p));
}
Passing its return value to be16_to_cpu() swaps the bytes a second time on
little-endian systems (and triggers a Sparse endianness warning).
In addition, in drivers/hwmon/applesmc.c, applesmc_light_show() handled the
10-byte ALV0 payload as a 10-bit big-endian value at buffer + 6 and shifted
it right by 2 only to fit into an 8-bit variable:
/* newer macbooks report a single 10-bit bigendian value */
if (data_length == 10) {
left = be16_to_cpu(*(__be16 *)(buffer + 6)) >> 2;
goto out;
}
Shifting a 10-bit value right by 10 bits discards the entire reading. Should
this just be get_unaligned_be16(buffer + 6) (or shifted right by 2 if 8-bit
scaling is intended)?
> + else
> + *val = buffer[2];
> +
> + break;
> + case RIGHT_LIGHT:
> + if (drv->size == 10) {
> + *val = 0;
> + break;
> + }
> + ret = apple_smc_read(drv->smc, SMC_KEY(ALV1), buffer, drv->size);
> + if (ret < 0)
> + return ret;
> +
> + *val = buffer[2];
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return IIO_VAL_INT;
> +}
[ ... ]
> +static const struct iio_event_spec macsmc_light_events[] = {
> + {
> + .type = IIO_EV_TYPE_CHANGE,
> + .dir = IIO_EV_DIR_EITHER,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + },
> +};
> +
> +static const struct iio_chan_spec macsmc_light_channels[] = {
> + {
> + .type = IIO_LIGHT,
> + .channel = LEFT_LIGHT,
> + .extend_name = "left",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + .event_spec = macsmc_light_events,
> + .num_event_specs = ARRAY_SIZE(macsmc_light_events),
> + },
> + {
> + .type = IIO_LIGHT,
> + .channel = RIGHT_LIGHT,
> + .extend_name = "right",
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + .event_spec = macsmc_light_events,
> + .num_event_specs = ARRAY_SIZE(macsmc_light_events),
> + },
> +};
[Severity: Medium]
Should macsmc_light_channels set .indexed = 1 (and use .read_label instead of
the deprecated .extend_name field)?
In drivers/iio/industrialio-core.c, __iio_chan_prefix_emit() only includes
chan->channel in the sysfs attribute name when chan->indexed is set:
if (chan->indexed)
seq_buf_printf(&s, "%d", chan->channel);
Without .indexed = 1, the channel numbers are omitted from the sysfs
attribute names, whereas macsmc_light_event() pushes events using
IIO_UNMOD_EVENT_CODE(IIO_LIGHT, LEFT_LIGHT, ...) and
IIO_UNMOD_EVENT_CODE(IIO_LIGHT, RIGHT_LIGHT, ...) with channel indices 0
and 1.
> +
> +static const struct iio_info macsmc_light_info = {
> + .read_raw = macsmc_light_read_raw,
> +};
[Severity: Medium]
Does macsmc_light_info need .read_event_config and .write_event_config
callbacks?
Because macsmc_light_events sets .mask_separate = BIT(IIO_EV_INFO_ENABLE),
the IIO core creates event enable sysfs attributes wired to
iio_ev_state_show() and iio_ev_state_store() in
drivers/iio/industrialio-event.c:
if (!indio_dev->info->write_event_config)
return -EINVAL;
and:
if (!indio_dev->info->read_event_config)
return -EINVAL;
With both callbacks left NULL in macsmc_light_info, reading or writing the
event enable attributes in sysfs will always fail with -EINVAL, and
macsmc_light_event() pushes events unconditionally regardless of whether
events are enabled.
> +
> +static int macsmc_light_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev;
> + struct macsmc_light *drv;
> + struct apple_smc_key_info info;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*drv));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + drv = iio_priv(indio_dev);
> + drv->smc = dev_get_drvdata(pdev->dev.parent);
> +
> + ret = apple_smc_get_key_info(drv->smc, SMC_KEY(ALV0), &info);
> + if (ret < 0)
> + return -ENODEV;
> +
> + drv->size = info.size;
[Severity: High]
Can an unexpected info.size from the SMC overflow buffer[] or leak
uninitialized stack memory in macsmc_light_read_raw()?
In macsmc_light_read_raw(), buffer is a 10-byte uninitialized stack array:
u8 buffer[10];
...
ret = apple_smc_read(drv->smc, SMC_KEY(ALV0), buffer, drv->size);
In drivers/hwmon/applesmc.c, applesmc_light_show() checked that the key
length did not exceed 10 before reading into buffer. Here drv->size is taken
directly from info.size without bounds checking. If info.size > 10,
apple_smc_read() will write past the end of buffer[] on the stack; if
info.size < 3, reading buffer[2] will return uninitialized stack bytes to
userspace (and ALV1 is also read using drv->size without checking ALV1's
own key size).
> + drv->indio_dev = indio_dev;
> + drv->nb.notifier_call = macsmc_light_event;
> + ret = blocking_notifier_chain_register(drv->smc->event_handlers, &drv->nb);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(&pdev->dev, macsmc_light_unregister_notifier, drv);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = "macsmc-light";
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->info = &macsmc_light_info;
> + indio_dev->channels = macsmc_light_channels;
> + indio_dev->num_channels = ARRAY_SIZE(macsmc_light_channels);
[Severity: Medium]
Should RIGHT_LIGHT only be registered when a right sensor is actually
present?
macsmc_light_probe() unconditionally registers both LEFT_LIGHT and
RIGHT_LIGHT channels. On systems where drv->size == 10, there is only a
single sensor in ALV0, so the right channel always returns a hardcoded 0 in
macsmc_light_read_raw() and never receives events in macsmc_light_event().
On systems where drv->size != 10 but SMC_KEY(ALV1) is absent, reading
RIGHT_LIGHT in macsmc_light_read_raw() will fail when querying ALV1, while
macsmc_light_event() will still push change events for RIGHT_LIGHT.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=4
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 05/14] iio/accel: add driver for apple SMS
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (3 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 04/14] iio/light: add new driver for macsmc Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:20 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 06/14] leds: add support for macsmc kbl Atharva Tiwari
` (8 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a driver for the Sudden Motion Sensor accelerometer,
exposed through IIO. It reports the three axes and also
drop, orientation and shock events.
This replaces the motion sensor support in applesmc.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/iio/accel/Kconfig | 10 ++
drivers/iio/accel/Makefile | 1 +
drivers/iio/accel/macsmc-accel.c | 248 +++++++++++++++++++++++++++++++
drivers/mfd/macsmc-core.c | 10 ++
4 files changed, 269 insertions(+)
create mode 100644 drivers/iio/accel/macsmc-accel.c
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 141ac9842..50bc127a4 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -532,6 +532,16 @@ config KXCJK1013
To compile this driver as a module, choose M here: the module will
be called kxcjk-1013.
+config MACSMC_ACCEL
+ tristate "Apple SMC Motion Sensor Driver"
+ depends on MACSMC_ACPI
+ help
+ Say yes here if you want support for Apple Motion Sensors, that uses
+ the System Management Controller.
+
+ To compile this driver as a module, choose M here: the module will
+ be called macsmc-accel.
+
config MC3230
tristate "mCube MC3230 Digital Accelerometer Driver"
depends on I2C
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile
index fa440a859..52fe661de 100644
--- a/drivers/iio/accel/Makefile
+++ b/drivers/iio/accel/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_KXSD9) += kxsd9.o
obj-$(CONFIG_KXSD9_SPI) += kxsd9-spi.o
obj-$(CONFIG_KXSD9_I2C) += kxsd9-i2c.o
obj-$(CONFIG_MC3230) += mc3230.o
+obj-$(CONFIG_MACSMC_ACCEL) += macsmc-accel.o
obj-$(CONFIG_MMA7455) += mma7455_core.o
obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o
diff --git a/drivers/iio/accel/macsmc-accel.c b/drivers/iio/accel/macsmc-accel.c
new file mode 100644
index 000000000..0abf81e7b
--- /dev/null
+++ b/drivers/iio/accel/macsmc-accel.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC sudden motion sensor (SMS) driver
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ *
+ * Based on drivers/hwmon/applesmc.c:
+ * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
+ * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
+ */
+
+#include <linux/delay.h>
+#include <linux/byteorder/generic.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+
+struct macsmc_accel {
+ struct apple_smc *smc;
+ struct iio_dev *indio_dev;
+ struct notifier_block nb;
+};
+
+enum {
+ SMS_DROP = 0,
+ SMS_ORIENTATION,
+ SMS_SHOCK,
+};
+
+static int macsmc_accel_read(struct macsmc_accel *drv, smc_key key, s16 *p)
+{
+ int ret;
+
+ ret = apple_smc_read_s16(drv->smc, key, p);
+ if (ret < 0)
+ return ret;
+
+ *p = (s16)be16_to_cpu(*p);
+ return 0;
+}
+
+static int macsmc_accel_dev_init(struct macsmc_accel *drv)
+{
+ int ret, total;
+ u16 buf;
+
+ for (total = 0; total < 100; total++) {
+ ret = apple_smc_read_u16(drv->smc, SMC_KEY(MOCN), &buf);
+ if (!ret && buf)
+ return 0;
+
+ buf = 0xE0;
+ apple_smc_write_u16(drv->smc, SMC_KEY(MOCN), buf);
+ msleep(50);
+ }
+
+ return -ENODEV;
+}
+
+static int macsmc_accel_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct macsmc_accel *drv = iio_priv(indio_dev);
+ int ret;
+ s16 buf;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ ret = macsmc_accel_read(drv, SMC_KEY(MO_X), &buf);
+ if (ret < 0)
+ return ret;
+
+ *val = buf;
+ return IIO_VAL_INT;
+ case IIO_MOD_Y:
+ ret = macsmc_accel_read(drv, SMC_KEY(MO_Y), &buf);
+ if (ret < 0)
+ return ret;
+
+ *val = buf;
+ return IIO_VAL_INT;
+ case IIO_MOD_Z:
+ ret = macsmc_accel_read(drv, SMC_KEY(MO_Z), &buf);
+ if (ret < 0)
+ return ret;
+
+ *val = buf;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int macsmc_accel_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_accel *drv = container_of(nb, struct macsmc_accel, nb);
+ u64 code;
+
+ /* check if event is for us */
+ if (event != 0x60 && event != 0x69 && event != 0x6F)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case 0x60:
+ code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_DROP,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE);
+ break;
+ case 0x69:
+ code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_ORIENTATION,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE);
+ break;
+ case 0x6F:
+ code = IIO_UNMOD_EVENT_CODE(IIO_ACCEL, SMS_SHOCK,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE);
+ break;
+ }
+
+ iio_push_event(drv->indio_dev, code, iio_get_time_ns(drv->indio_dev));
+ return NOTIFY_OK;
+}
+
+static void macsmc_accel_unregister_notifier(void *data)
+{
+ struct macsmc_accel *drv = data;
+
+ blocking_notifier_chain_unregister(drv->smc->event_handlers,
+ &drv->nb);
+}
+
+static const struct iio_event_spec macsmc_accel_event_spec = {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+};
+
+static const struct iio_chan_spec macsmc_accel_channels[] = {
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_ACCEL,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_ACCEL,
+ .channel = SMS_DROP,
+ .event_spec = &macsmc_accel_event_spec,
+ .num_event_specs = 1,
+ },
+ {
+ .type = IIO_ACCEL,
+ .channel = SMS_ORIENTATION,
+ .event_spec = &macsmc_accel_event_spec,
+ .num_event_specs = 1,
+ },
+ {
+ .type = IIO_ACCEL,
+ .channel = SMS_SHOCK,
+ .event_spec = &macsmc_accel_event_spec,
+ .num_event_specs = 1,
+ },
+};
+
+static const struct iio_info macsmc_accel_info = {
+ .read_raw = macsmc_accel_read_raw
+};
+
+static int macsmc_accel_restore(struct device *dev)
+{
+ struct macsmc_accel *drv = dev_get_drvdata(dev);
+
+ return macsmc_accel_dev_init(drv);
+}
+
+static const struct dev_pm_ops macsmc_accel_pm_ops = {
+ .restore = macsmc_accel_restore,
+};
+
+static int macsmc_accel_probe(struct platform_device *pdev)
+{
+ struct macsmc_accel *drv;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*drv));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ drv = iio_priv(indio_dev);
+ drv->smc = dev_get_drvdata(pdev->dev.parent);
+
+ ret = macsmc_accel_dev_init(drv);
+ if (ret)
+ return ret;
+
+ drv->indio_dev = indio_dev;
+ drv->nb.notifier_call = macsmc_accel_event;
+ ret = blocking_notifier_chain_register(drv->smc->event_handlers, &drv->nb);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, macsmc_accel_unregister_notifier, drv);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, drv);
+
+ indio_dev->name = "macsmc-accel";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &macsmc_accel_info;
+ indio_dev->channels = macsmc_accel_channels;
+ indio_dev->num_channels = ARRAY_SIZE(macsmc_accel_channels);
+
+ return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver macsmc_accel_driver = {
+ .probe = macsmc_accel_probe,
+ .driver = {
+ .name = "macsmc-accel",
+ .pm = &macsmc_accel_pm_ops,
+ },
+};
+module_platform_driver(macsmc_accel_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_DESCRIPTION("Apple SMC Motion Sensor Driver");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:macsmc-accel");
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index 6014df4fe..6567d2f1f 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -24,6 +24,10 @@ static const struct mfd_cell apple_smc_rtkit_devs[] = {
* There isn't device tree on acpi backend, so we need to use
* apple_smc_key_exists to register mfd devs
*/
+static const struct mfd_cell apple_smc_acpi_accel[] = {
+ MFD_CELL_NAME("macsmc-accel"),
+};
+
static const struct mfd_cell apple_smc_acpi_common[] = {
MFD_CELL_NAME("macsmc-hwmon"),
MFD_CELL_NAME("macsmc-power"),
@@ -169,6 +173,12 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
return ret;
}
+ if (apple_smc_key_exists(smc, SMC_KEY(MOCN))) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_accel,
+ ARRAY_SIZE(apple_smc_acpi_accel), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
} else
ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 05/14] iio/accel: add driver for apple SMS
2026-10-29 8:52 ` [RFC PATCH 05/14] iio/accel: add driver for apple SMS Atharva Tiwari
@ 2026-10-29 9:20 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:20 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] iio: accel: macsmc-accel: unindexed event channels cause -EBUSY probe failure
- [Medium] iio: accel: macsmc-accel: missing event config callbacks in macsmc_accel_info
- [Low] iio: accel: macsmc-accel: missing pm_sleep_ptr() wrapper on macsmc_accel_pm_ops
--
commit ee4b509c282b1a026627accf04e0b08185034ca5
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
iio/accel: add driver for apple SMS
This commit adds an IIO accelerometer driver for the Apple Sudden Motion
Sensor (SMS) to report X, Y, and Z axes along with drop, orientation, and
shock events, and registers the macsmc-accel MFD cell in macsmc-core when the
MOCN key is present.
> diff --git a/drivers/iio/accel/macsmc-accel.c b/drivers/iio/accel/macsmc-accel.c
> new file mode 100644
> index 0000000000000..0abf81e7bd334
> --- /dev/null
> +++ b/drivers/iio/accel/macsmc-accel.c
[ ... ]
> +static const struct iio_event_spec macsmc_accel_event_spec = {
> + .type = IIO_EV_TYPE_CHANGE,
> + .dir = IIO_EV_DIR_NONE,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> +};
> +
> +static const struct iio_chan_spec macsmc_accel_channels[] = {
> + {
> + .type = IIO_ACCEL,
> + .modified = 1,
> + .channel2 = IIO_MOD_X,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + },
> + {
> + .type = IIO_ACCEL,
> + .modified = 1,
> + .channel2 = IIO_MOD_Y,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + },
> + {
> + .type = IIO_ACCEL,
> + .modified = 1,
> + .channel2 = IIO_MOD_Z,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + },
> + {
> + .type = IIO_ACCEL,
> + .channel = SMS_DROP,
> + .event_spec = &macsmc_accel_event_spec,
> + .num_event_specs = 1,
> + },
> + {
> + .type = IIO_ACCEL,
> + .channel = SMS_ORIENTATION,
> + .event_spec = &macsmc_accel_event_spec,
> + .num_event_specs = 1,
> + },
> + {
> + .type = IIO_ACCEL,
> + .channel = SMS_SHOCK,
> + .event_spec = &macsmc_accel_event_spec,
> + .num_event_specs = 1,
> + },
> +};
[Severity: High]
Will macsmc_accel_probe() always fail with -EBUSY when registering these three
event channels?
In macsmc_accel_channels[], the SMS_DROP, SMS_ORIENTATION, and SMS_SHOCK
channels set .channel and .event_spec = &macsmc_accel_event_spec (which sets
.mask_separate = BIT(IIO_EV_INFO_ENABLE)), but they leave .indexed as 0.
When devm_iio_device_register() registers the event attributes:
macsmc_accel_probe()
-> devm_iio_device_register()
-> __iio_device_register()
-> iio_device_register_eventset()
-> __iio_add_event_config_attrs()
-> iio_device_add_event_sysfs()
-> iio_device_add_event()
-> __iio_add_chan_devattr()
-> __iio_device_attr_init()
-> __iio_chan_prefix_emit()
drivers/iio/industrialio-core.c:__iio_chan_prefix_emit() only includes
chan->channel in the sysfs attribute name when chan->indexed is set:
seq_buf_printf(&s, "%s_%s", dir, type);
if (chan->indexed)
seq_buf_printf(&s, "%d", chan->channel);
Because chan->indexed is 0 on all three event channels, each one generates the
same separate sysfs attribute name "in_accel_change_en". When the second event
channel (SMS_ORIENTATION) is processed in __iio_add_chan_devattr(), it detects
the duplicate IIO_SEPARATE attribute name in ev_int->dev_attr_list and returns
-EBUSY:
list_for_each_entry(t, attr_list, l)
if (strcmp(t->dev_attr.attr.name,
iio_attr->dev_attr.attr.name) == 0) {
if (shared_by == IIO_SEPARATE)
dev_err(dev, "tried to double register : %s\n",
t->dev_attr.attr.name);
ret = -EBUSY;
goto error_device_attr_deinit;
}
Should these three event channels set .indexed = 1 (and should
macsmc_accel_event() use IIO_EVENT_CODE() or otherwise match the channel
configuration) so that their sysfs attribute names do not collide?
> +
> +static const struct iio_info macsmc_accel_info = {
> + .read_raw = macsmc_accel_read_raw
> +};
[Severity: Medium]
Should macsmc_accel_info also provide .read_event_config and
.write_event_config callbacks, or should BIT(IIO_EV_INFO_ENABLE) be omitted
from macsmc_accel_event_spec?
Because macsmc_accel_event_spec sets .mask_separate = BIT(IIO_EV_INFO_ENABLE),
iio_device_add_event() creates sysfs event enable attributes backed by
iio_ev_state_show() and iio_ev_state_store() in
drivers/iio/industrialio-event.c:
if (!indio_dev->info->read_event_config)
return -EINVAL;
and:
if (!indio_dev->info->write_event_config)
return -EINVAL;
With both callbacks left NULL in macsmc_accel_info, any userspace read or
write on the exported *_change_en sysfs attributes will fail with -EINVAL.
> +
> +static int macsmc_accel_restore(struct device *dev)
> +{
> + struct macsmc_accel *drv = dev_get_drvdata(dev);
> +
> + return macsmc_accel_dev_init(drv);
> +}
> +
> +static const struct dev_pm_ops macsmc_accel_pm_ops = {
> + .restore = macsmc_accel_restore,
> +};
[ ... ]
> +static struct platform_driver macsmc_accel_driver = {
> + .probe = macsmc_accel_probe,
> + .driver = {
> + .name = "macsmc-accel",
> + .pm = &macsmc_accel_pm_ops,
> + },
> +};
[Severity: Low]
Could macsmc_accel_pm_ops wrap macsmc_accel_restore with pm_sleep_ptr(), and
macsmc_accel_driver wrap &macsmc_accel_pm_ops with pm_sleep_ptr(), so the PM
callback and ops structure are compiled out when CONFIG_PM_SLEEP is disabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=5
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 06/14] leds: add support for macsmc kbl
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (4 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 05/14] iio/accel: add driver for apple SMS Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:16 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 07/14] rtc: add support for x86 macs in macsmc-rtc Atharva Tiwari
` (7 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a LED class driver to control the keyboard backlight.
This replaces the keyboard backlight support in applesmc.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/leds/Kconfig | 11 ++++++
drivers/leds/Makefile | 1 +
drivers/leds/leds-macsmc.c | 69 ++++++++++++++++++++++++++++++++++++++
drivers/mfd/macsmc-core.c | 11 ++++++
4 files changed, 92 insertions(+)
create mode 100644 drivers/leds/leds-macsmc.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7ec2c8d78..30cd64d41 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -729,6 +729,17 @@ config LEDS_ADP5520
To compile this driver as a module, choose M here: the module will
be called leds-adp5520.
+config LEDS_MACSMC
+ tristate "Keyboard Backlight support for Intel-Macs"
+ depends on LEDS_CLASS
+ depends on MACSMC_ACPI
+ help
+ This option enables support for the Keyboard backlight in
+ Intel-Macs using macsmc.
+
+ To compile this driver as a module, choose M here: the module will
+ be called leds-macsmc.
+
config LEDS_MAX5970
tristate "LED Support for Maxim 5970"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4d4b08915..7c38dbef4 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
obj-$(CONFIG_LEDS_ACER_A500) += leds-acer-a500.o
obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
+obj-$(CONFIG_LEDS_MACSMC) += leds-macsmc.o
obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
diff --git a/drivers/leds/leds-macsmc.c b/drivers/leds/leds-macsmc.c
new file mode 100644
index 000000000..f38ef2113
--- /dev/null
+++ b/drivers/leds/leds-macsmc.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Apple SMC Keyboard backlight driver
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ *
+ * Based on drivers/hwmon/applesmc.c
+ * Copyright (C) 2007 Nicolas Boichat <nicolas@boichat.ch>
+ * Copyright (C) 2010 Henrik Rydberg <rydberg@euromail.se>
+ *
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/platform_device.h>
+
+struct macsmc_led {
+ struct led_classdev cdev;
+ struct apple_smc *smc;
+ u8 brightness;
+};
+
+static enum led_brightness macsmc_led_get_brightness(struct led_classdev *cdev)
+{
+ struct macsmc_led *drv = container_of(cdev, struct macsmc_led, cdev);
+
+ return drv->brightness;
+}
+
+static void macsmc_led_set_brightness(struct led_classdev *cdev, enum led_brightness value)
+{
+ struct macsmc_led *drv = container_of(cdev, struct macsmc_led, cdev);
+
+ drv->brightness = (u8) value;
+
+ apple_smc_write_u16(drv->smc, SMC_KEY(LKSB), drv->brightness);
+}
+
+static int macsmc_led_probe(struct platform_device *pdev)
+{
+ struct macsmc_led *drv;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ drv->smc = dev_get_drvdata(pdev->dev.parent);
+
+ drv->cdev.name = "macsmc-led";
+ drv->cdev.max_brightness = 255;
+ drv->cdev.brightness_get = macsmc_led_get_brightness;
+ drv->cdev.brightness_set = macsmc_led_set_brightness;
+ drv->cdev.dev = &pdev->dev;
+
+ return devm_led_classdev_register(&pdev->dev, &drv->cdev);
+}
+
+static struct platform_driver macsmc_led_driver = {
+ .probe = macsmc_led_probe,
+ .driver = {
+ .name = "macsmc-led",
+ },
+};
+module_platform_driver(macsmc_led_driver);
+
+MODULE_AUTHOR("Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>");
+MODULE_DESCRIPTION("LED keyboard backlight driver for Intel-macs using macsmc");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:macsmc-led");
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index 6567d2f1f..bc4200f57 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -33,6 +33,10 @@ static const struct mfd_cell apple_smc_acpi_common[] = {
MFD_CELL_NAME("macsmc-power"),
};
+static const struct mfd_cell apple_smc_acpi_led[] = {
+ MFD_CELL_NAME("macsmc-led"),
+};
+
static const struct mfd_cell apple_smc_acpi_light[] = {
MFD_CELL_NAME("macsmc-light"),
};
@@ -179,6 +183,13 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
if (ret < 0)
return ret;
}
+
+ if (apple_smc_key_exists(smc, SMC_KEY(LKSB))) {
+ ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_led,
+ ARRAY_SIZE(apple_smc_acpi_led), NULL, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
} else
ret = devm_mfd_add_devices(dev, -1, apple_smc_rtkit_devs,
ARRAY_SIZE(apple_smc_rtkit_devs), NULL, 0, NULL);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 06/14] leds: add support for macsmc kbl
2026-10-29 8:52 ` [RFC PATCH 06/14] leds: add support for macsmc kbl Atharva Tiwari
@ 2026-10-29 9:16 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:16 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] leds: macsmc: sleeping SMC write registered as non-blocking brightness_set
- [Medium] leds: macsmc: non-standard LED name breaks kbd_backlight discovery
- [Medium] leds: macsmc: missing suspend/resume handling loses backlight state
- [Low] leds: macsmc: commit prefix, drv naming, and Makefile sort order violations
--
commit 62f13fe6413b6947c13026e13a39c022cd83dde2
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
leds: add support for macsmc kbl
This commit adds a LED class driver for the Apple SMC keyboard backlight on
Intel Macs and registers the macsmc-led MFD cell when the LKSB key is present,
intended to replace the keyboard backlight support in applesmc.
> leds: add support for macsmc kbl
>
> Add a LED class driver to control the keyboard backlight.
>
> This replaces the keyboard backlight support in applesmc.
>
> Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
[Severity: Low]
This isn't a bug, but should the commit subject follow the LED subsystem
convention of including the driver prefix and capitalizing the description,
such as "leds: macsmc: Add support for keyboard backlight"?
[ ... ]
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 4d4b089156e24..7c38dbef42390 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o
> obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
> obj-$(CONFIG_LEDS_ACER_A500) += leds-acer-a500.o
> obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
> +obj-$(CONFIG_LEDS_MACSMC) += leds-macsmc.o
> obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
[Severity: Low]
This isn't a bug, but the platform drivers section in drivers/leds/Makefile
is kept sorted alphabetically. Should leds-macsmc.o be placed in alphabetical
order right before leds-max5970.o?
> diff --git a/drivers/leds/leds-macsmc.c b/drivers/leds/leds-macsmc.c
> new file mode 100644
> index 0000000000000..f38ef2113aced
> --- /dev/null
> +++ b/drivers/leds/leds-macsmc.c
[ ... ]
> +static enum led_brightness macsmc_led_get_brightness(struct led_classdev *cdev)
> +{
> + struct macsmc_led *drv = container_of(cdev, struct macsmc_led, cdev);
> +
> + return drv->brightness;
> +}
> +
> +static void macsmc_led_set_brightness(struct led_classdev *cdev, enum led_brightness value)
> +{
> + struct macsmc_led *drv = container_of(cdev, struct macsmc_led, cdev);
> +
> + drv->brightness = (u8) value;
> +
> + apple_smc_write_u16(drv->smc, SMC_KEY(LKSB), drv->brightness);
> +}
> +
> +static int macsmc_led_probe(struct platform_device *pdev)
> +{
> + struct macsmc_led *drv;
[Severity: Low]
This isn't a bug, but could the private data pointer be named ddata instead
of drv in macsmc_led_get_brightness(), macsmc_led_set_brightness(), and
macsmc_led_probe() to match the LED subsystem naming convention?
> +
> + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
> + if (!drv)
> + return -ENOMEM;
> +
> + drv->smc = dev_get_drvdata(pdev->dev.parent);
> +
> + drv->cdev.name = "macsmc-led";
[Severity: Medium]
Will setting drv->cdev.name to "macsmc-led" in macsmc_led_probe() cause a
regression for userspace tools discovering the keyboard backlight?
In drivers/hwmon/applesmc.c, which this driver replaces, the LED class device
is registered with the name "smc::kbd_backlight":
static struct led_classdev applesmc_backlight = {
.name = "smc::kbd_backlight",
.default_trigger = "nand-disk",
.brightness_set = applesmc_brightness_set,
};
Documentation/leds/leds-class.rst also specifies that keyboard backlight LED
devices should use kbd_backlight as the function suffix so that userspace
consumers (such as upower and systemd-backlight) can identify them under
/sys/class/leds/. Should this keep "smc::kbd_backlight" to preserve UAPI
compatibility?
> + drv->cdev.max_brightness = 255;
> + drv->cdev.brightness_get = macsmc_led_get_brightness;
> + drv->cdev.brightness_set = macsmc_led_set_brightness;
[Severity: High]
Can registering macsmc_led_set_brightness() on drv->cdev.brightness_set in
macsmc_led_probe() sleep in atomic context when an atomic LED trigger or
software blink timer fires?
When software blinking or an atomic trigger updates the LED, the call chain
led_timer_function() or led_trigger_event() -> led_set_brightness_nosleep() ->
led_set_brightness_nopm() -> __led_set_brightness() invokes the
brightness_set callback synchronously without deferring to
set_brightness_work:
/* Use brightness_set op if available, it is guaranteed not to sleep */
if (!__led_set_brightness(led_cdev, value))
return;
However, macsmc_led_set_brightness() calls apple_smc_write_u16() ->
apple_smc_write():
int apple_smc_write(struct apple_smc *smc, smc_key key, const void *buf, size_t size)
{
guard(mutex)(&smc->mutex);
return smc->be->write(smc->be_cookie, key, buf, size);
}
This acquires a sleeping mutex and calls apple_smc_acpi_write(), which sleeps
via wait_for_completion_timeout() and fsleep(). Unlike applesmc, which queued
a work item on a workqueue in applesmc_brightness_set(), this sleeps directly
in the caller's context and also discards any error returned by
apple_smc_write_u16().
Should macsmc_led_set_brightness() be registered via
drv->cdev.brightness_set_blocking instead, and return the result of
apple_smc_write_u16()?
> + drv->cdev.dev = &pdev->dev;
> +
> + return devm_led_classdev_register(&pdev->dev, &drv->cdev);
> +}
[Severity: Medium]
Does omitting suspend and resume handling here cause the keyboard backlight
state to be lost across system suspend or hibernation?
In drivers/hwmon/applesmc.c, applesmc_pm_resume() and applesmc_pm_restore()
restore the saved brightness to the hardware key on resume:
static int applesmc_pm_resume(struct device *dev)
{
if (smcreg.has_key_backlight)
applesmc_write_key(BACKLIGHT_KEY, backlight_state, 2);
return 0;
}
Because macsmc_led_probe() does not set LED_CORE_SUSPENDRESUME in
drv->cdev.flags (and macsmc_led_driver does not define PM callbacks), the
keyboard backlight is not turned off on suspend or restored on resume. Note
also that if LED_CORE_SUSPENDRESUME is enabled, the custom brightness_get
callback returning drv->brightness would return 0 during resume after
led_classdev_suspend() sets the brightness to 0, unless the LED core's
cached brightness is used instead of a custom brightness_get callback.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=6
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 07/14] rtc: add support for x86 macs in macsmc-rtc
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (5 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 06/14] leds: add support for macsmc kbl Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:15 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 08/14] treewide: Add a flag to detect the Apple T2 chip Atharva Tiwari
` (6 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Make macsmc-rtc work on Intel Macs. The ACPI RTC doesn't
work correctly on T2 Macs, so the clock from the SMC is
used instead.
On these machines the time offset is kept in the SMC instead
of NVMEM, and the clock rate is read from the SMC too.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/rtc/rtc-macsmc.c | 126 ++++++++++++++++++++++++++++++++-------
1 file changed, 106 insertions(+), 20 deletions(-)
diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c
index 8fe883066..37395c64c 100644
--- a/drivers/rtc/rtc-macsmc.c
+++ b/drivers/rtc/rtc-macsmc.c
@@ -2,9 +2,12 @@
/*
* Apple SMC RTC driver
* Copyright The Asahi Linux Contributors
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
*/
#include <linux/bitops.h>
+#include <linux/delay.h>
#include <linux/mfd/macsmc.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
@@ -20,13 +23,54 @@
/* 32768 Hz clock */
#define RTC_SEC_SHIFT 15
+static bool force_clkm;
+module_param(force_clkm, bool, 0644);
+MODULE_PARM_DESC(force_clkm, "Force CLKM Key, instead of trying to use CLKL");
+
struct macsmc_rtc {
struct device *dev;
struct apple_smc *smc;
struct rtc_device *rtc_dev;
struct nvmem_cell *rtc_offset;
+ uint8_t shift;
+ bool async_rtc;
};
+static int macsmc_rtc_read_clkl(struct macsmc_rtc *rtc, u64 *ctr)
+{
+ u64 request = 0xffffffffffffULL;
+ u64 response = 0;
+ int i, j, ret;
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 40; j++) {
+ ret = apple_smc_write(rtc->smc, SMC_KEY(CLKL), &request, RTC_BYTES);
+ if (ret == RTC_BYTES)
+ break;
+ }
+ if (ret != RTC_BYTES)
+ continue;
+
+ msleep(20);
+
+ for (j = 0; j < 20; j++) {
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKL), &response, RTC_BYTES);
+ if (ret == RTC_BYTES) {
+ if ((response & 0x7fff) == 5 || (response & 0x7fff) == 6)
+ return -EAGAIN;
+
+ if (response != 0xffffffffffffULL) {
+ *ctr = response;
+ return RTC_BYTES;
+ }
+ }
+ fsleep(1000);
+ }
+ }
+
+ return -EIO;
+}
+
static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm)
{
struct macsmc_rtc *rtc = dev_get_drvdata(dev);
@@ -36,25 +80,36 @@ static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm)
size_t len;
int ret;
- ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+ if (rtc->async_rtc && !force_clkm)
+ ret = macsmc_rtc_read_clkl(rtc, &ctr);
+ else
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+
if (ret < 0)
return ret;
if (ret != RTC_BYTES)
return -EIO;
- p_off = nvmem_cell_read(rtc->rtc_offset, &len);
- if (IS_ERR(p_off))
- return PTR_ERR(p_off);
- if (len < RTC_BYTES) {
+ if (rtc->smc->is_acpi) {
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKO), &off, RTC_BYTES);
+ if (ret < 0)
+ return ret;
+ if (ret != RTC_BYTES)
+ return -EIO;
+ } else {
+ p_off = nvmem_cell_read(rtc->rtc_offset, &len);
+ if (IS_ERR(p_off))
+ return PTR_ERR(p_off);
+ if (len < RTC_BYTES) {
+ kfree(p_off);
+ return -EIO;
+ }
+
+ memcpy(&off, p_off, RTC_BYTES);
kfree(p_off);
- return -EIO;
}
-
- memcpy(&off, p_off, RTC_BYTES);
- kfree(p_off);
-
/* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */
- now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT;
+ now = sign_extend64(ctr + off, RTC_BITS - 1) >> rtc->shift;
rtc_time64_to_tm(now, tm);
return ret;
@@ -66,14 +121,20 @@ static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
u64 ctr = 0, off = 0;
int ret;
- ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+ if (rtc->async_rtc && !force_clkm)
+ ret = macsmc_rtc_read_clkl(rtc, &ctr);
+ else
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
if (ret < 0)
return ret;
if (ret != RTC_BYTES)
return -EIO;
/* This sets the offset such that the set second begins now */
- off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr;
+ off = (rtc_tm_to_time64(tm) << rtc->shift) - ctr;
+ if (rtc->smc->is_acpi)
+ return (apple_smc_write(rtc->smc, SMC_KEY(CLKO), &off, RTC_BYTES) != RTC_BYTES);
+
return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES);
}
@@ -86,13 +147,15 @@ static int macsmc_rtc_probe(struct platform_device *pdev)
{
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
struct macsmc_rtc *rtc;
+ int ret;
+ uint32_t clkr = 0xFFFFFFFF;
/*
* MFD will probe this device even without a node in the device tree,
* thus bail out early if the SMC on the current machines does not
* support RTC and has no node in the device tree.
*/
- if (!pdev->dev.of_node)
+ if (!pdev->dev.of_node && !smc->is_acpi)
return -ENODEV;
rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
@@ -102,18 +165,40 @@ static int macsmc_rtc_probe(struct platform_device *pdev)
rtc->dev = &pdev->dev;
rtc->smc = smc;
- rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset");
- if (IS_ERR(rtc->rtc_offset))
- return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset),
- "Failed to get rtc_offset NVMEM cell\n");
+ if (!rtc->smc->is_acpi) {
+ rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset");
+ if (IS_ERR(rtc->rtc_offset))
+ return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset),
+ "Failed to get rtc_offset NVMEM cell\n");
+ }
+
+ if (rtc->smc->is_acpi) {
+ ret = apple_smc_read_u32(rtc->smc, SMC_KEY(CLKR), &clkr);
+ if (ret < 0 || clkr == 0xFFFFFFFF)
+ rtc->shift = RTC_SEC_SHIFT;
+ else
+ rtc->shift = ffs(clkr) - 1;
+
+ if (apple_smc_key_exists(rtc->smc, SMC_KEY(CLKL))) {
+ u64 clkl;
+
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKL), &clkl, RTC_BYTES);
+ if (ret == RTC_BYTES && clkl != 0xFFFFFFFFFFFFFFFFULL)
+ rtc->async_rtc = true;
+ }
+ } else
+ rtc->shift = RTC_SEC_SHIFT;
+
+ dev_info(&pdev->dev, "%u clock, using %s key\n", (clkr != 0xFFFFFFFF) ? clkr : 32768,
+ rtc->async_rtc ? "CLKL" : "CLKM");
rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
if (IS_ERR(rtc->rtc_dev))
return PTR_ERR(rtc->rtc_dev);
rtc->rtc_dev->ops = &macsmc_rtc_ops;
- rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
- rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
+ rtc->rtc_dev->range_min = S64_MIN >> (rtc->shift + (64 - RTC_BITS));
+ rtc->rtc_dev->range_max = S64_MAX >> (rtc->shift + (64 - RTC_BITS));
platform_set_drvdata(pdev, rtc);
@@ -138,3 +223,4 @@ module_platform_driver(macsmc_rtc_driver);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("Apple SMC RTC driver");
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-rtc");
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 07/14] rtc: add support for x86 macs in macsmc-rtc
2026-10-29 8:52 ` [RFC PATCH 07/14] rtc: add support for x86 macs in macsmc-rtc Atharva Tiwari
@ 2026-10-29 9:15 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:15 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] rtc: macsmc: uninitialized u64 clkl and wrong mask in macsmc_rtc_probe()
- [High] rtc: macsmc: positive return on ACPI write failure in macsmc_rtc_set_time()
- [Medium] rtc: macsmc: shift underflow when clkr is zero in macsmc_rtc_probe()
- [Low] rtc: macsmc: missing kernel-parameters.txt entry for force_clkm
--
commit a291b39ce81e3a636ffdc1d72c951eb114ad04ea
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
rtc: add support for x86 macs in macsmc-rtc
This commit adds support for Intel T2 Macs to the macsmc-rtc driver. On these
machines, the driver reads the clock rate and RTC offset from the SMC rather
than NVMEM, and optionally reads the clock via the asynchronous CLKL SMC key.
> diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c
> index 8fe8830669569..37395c64cbb5e 100644
> --- a/drivers/rtc/rtc-macsmc.c
> +++ b/drivers/rtc/rtc-macsmc.c
[ ... ]
> @@ -20,13 +23,54 @@
> /* 32768 Hz clock */
> #define RTC_SEC_SHIFT 15
>
> +static bool force_clkm;
> +module_param(force_clkm, bool, 0644);
> +MODULE_PARM_DESC(force_clkm, "Force CLKM Key, instead of trying to use CLKL");
[Severity: Low]
Should the new rtc_macsmc.force_clkm module parameter also be documented in
Documentation/admin-guide/kernel-parameters.txt?
[ ... ]
> @@ -66,14 +121,20 @@ static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
> u64 ctr = 0, off = 0;
> int ret;
>
> - ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
> + if (rtc->async_rtc && !force_clkm)
> + ret = macsmc_rtc_read_clkl(rtc, &ctr);
> + else
> + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
> if (ret < 0)
> return ret;
> if (ret != RTC_BYTES)
> return -EIO;
>
> /* This sets the offset such that the set second begins now */
> - off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr;
> + off = (rtc_tm_to_time64(tm) << rtc->shift) - ctr;
> + if (rtc->smc->is_acpi)
> + return (apple_smc_write(rtc->smc, SMC_KEY(CLKO), &off, RTC_BYTES) != RTC_BYTES);
[Severity: High]
What happens when apple_smc_write() fails in macsmc_rtc_set_time()?
Because struct rtc_class_ops.set_time is expected to return 0 on success and
a negative error code on failure, returning the boolean comparison result
evaluates to 1 when apple_smc_write() returns a negative errno or a short
write.
In the rtc_dev_ioctl() -> rtc_set_time() -> macsmc_rtc_set_time() call chain:
drivers/rtc/interface.c:rtc_set_time() {
...
else if (rtc->ops->set_time)
err = rtc->ops->set_time(rtc->dev.parent, tm);
...
return err;
}
a return value of 1 is either overwritten by 0 if update IRQs are enabled or
returned to userspace from the RTC_SET_TIME ioctl as a non-negative value,
causing a failed hardware clock update to be reported as success. Should this
propagate negative error codes from apple_smc_write() and return -EIO on a
size mismatch?
[ ... ]
> @@ -102,18 +165,40 @@ static int macsmc_rtc_probe(struct platform_device *pdev)
[ ... ]
> + if (rtc->smc->is_acpi) {
> + ret = apple_smc_read_u32(rtc->smc, SMC_KEY(CLKR), &clkr);
> + if (ret < 0 || clkr == 0xFFFFFFFF)
> + rtc->shift = RTC_SEC_SHIFT;
> + else
> + rtc->shift = ffs(clkr) - 1;
[Severity: Medium]
Can clkr be 0 if the SMC returns a zero value for the CLKR key in
macsmc_rtc_probe()?
If apple_smc_read_u32() succeeds with clkr == 0, ffs(0) returns 0 and
ffs(clkr) - 1 underflows the uint8_t field rtc->shift to 255. That causes
subsequent shifts by rtc->shift in macsmc_rtc_probe(), macsmc_rtc_get_time(),
and macsmc_rtc_set_time() to shift 64-bit integers by 255 or 271 bits:
rtc->rtc_dev->range_min = S64_MIN >> (rtc->shift + (64 - RTC_BITS));
rtc->rtc_dev->range_max = S64_MAX >> (rtc->shift + (64 - RTC_BITS));
Also, if apple_smc_read_u32() fails after partially modifying clkr, the
dev_info() call below only checks clkr != 0xFFFFFFFF and will log the
corrupted clkr value instead of 32768 even though rtc->shift fell back to
RTC_SEC_SHIFT. Would it make sense to guard against !clkr as well?
> +
> + if (apple_smc_key_exists(rtc->smc, SMC_KEY(CLKL))) {
> + u64 clkl;
> +
> + ret = apple_smc_read(rtc->smc, SMC_KEY(CLKL), &clkl, RTC_BYTES);
> + if (ret == RTC_BYTES && clkl != 0xFFFFFFFFFFFFFFFFULL)
> + rtc->async_rtc = true;
> + }
[Severity: High]
Does this read uninitialized stack memory and check against the wrong sentinel
mask in macsmc_rtc_probe()?
Here clkl is an uninitialized 8-byte u64 on the stack, but apple_smc_read()
only writes RTC_BYTES (6 bytes) into &clkl, leaving the upper 2 bytes
uninitialized when evaluating clkl != 0xFFFFFFFFFFFFFFFFULL.
In addition, because CLKL is a 48-bit (6-byte) value, its all-ones invalid
sentinel is 0xffffffffffffULL, as checked in macsmc_rtc_read_clkl():
static int macsmc_rtc_read_clkl(struct macsmc_rtc *rtc, u64 *ctr)
{
u64 request = 0xffffffffffffULL;
u64 response = 0;
...
if (response != 0xffffffffffffULL) {
*ctr = response;
return RTC_BYTES;
}
Even if clkl is zero-initialized to 0, reading 6 bytes of 0xff into it
produces 0x0000ffffffffffffULL, which will never equal 0xFFFFFFFFFFFFFFFFULL
and will erroneously set rtc->async_rtc = true. Should clkl be initialized to
0 and compared against 0xffffffffffffULL?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=7
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 08/14] treewide: Add a flag to detect the Apple T2 chip
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (6 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 07/14] rtc: add support for x86 macs in macsmc-rtc Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:11 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 09/14] treewide: disable other rtc's in t2 macs Atharva Tiwari
` (5 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a flag to detect Apple T2 chips on Intel Macs.
Cache the result to avoid repeated checks. That will
be used in upcoming patches.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
arch/x86/kernel/quirks.c | 37 ++++++++++++++++++
include/linux/platform_data/x86/apple.h | 5 +++
.../platform_certs/keyring_handler.h | 8 ----
security/integrity/platform_certs/load_uefi.c | 38 ++++---------------
4 files changed, 50 insertions(+), 38 deletions(-)
diff --git a/arch/x86/kernel/quirks.c b/arch/x86/kernel/quirks.c
index a92f18db9..6651e5d4d 100644
--- a/arch/x86/kernel/quirks.c
+++ b/arch/x86/kernel/quirks.c
@@ -664,8 +664,45 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2083, quirk_intel_purley_xeon_ras
bool x86_apple_machine;
EXPORT_SYMBOL(x86_apple_machine);
+bool has_apple_t2_chip;
+EXPORT_SYMBOL(has_apple_t2_chip);
+
+static const char * const __initconst apple_t2_models[] = {
+ "MacBookPro15,1",
+ "MacBookPro15,2",
+ "MacBookPro15,3",
+ "MacBookPro15,4",
+ "MacBookPro16,1",
+ "MacBookPro16,2",
+ "MacBookPro16,3",
+ "MacBookPro16,4",
+ "MacBookAir8,1",
+ "MacBookAir8,2",
+ "MacBookAir9,1",
+ "Macmini8,1",
+ "MacPro7,1",
+ "iMac20,1",
+ "iMac20,2",
+ "iMacPro1,1",
+};
+
void __init early_platform_quirks(void)
{
x86_apple_machine = dmi_match(DMI_SYS_VENDOR, "Apple Inc.") ||
dmi_match(DMI_SYS_VENDOR, "Apple Computer, Inc.");
+
+ if (x86_apple_machine) {
+ const char *product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
+ int i;
+
+ if (!product_name)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(apple_t2_models); i++) {
+ if (!strcmp(product_name, apple_t2_models[i])) {
+ has_apple_t2_chip = true;
+ break;
+ }
+ }
+ }
}
diff --git a/include/linux/platform_data/x86/apple.h b/include/linux/platform_data/x86/apple.h
index 079e816c3..47b27dcea 100644
--- a/include/linux/platform_data/x86/apple.h
+++ b/include/linux/platform_data/x86/apple.h
@@ -6,8 +6,13 @@
* x86_apple_machine - whether the machine is an x86 Apple Macintosh
*/
extern bool x86_apple_machine;
+/**
+ * has_apple_t2_chip - whether the machine has the Apple T2 chip
+ */
+extern bool has_apple_t2_chip;
#else
#define x86_apple_machine false
+#define has_apple_t2_chip false
#endif
#endif
diff --git a/security/integrity/platform_certs/keyring_handler.h b/security/integrity/platform_certs/keyring_handler.h
index f92895cc5..a355f4400 100644
--- a/security/integrity/platform_certs/keyring_handler.h
+++ b/security/integrity/platform_certs/keyring_handler.h
@@ -45,11 +45,3 @@ efi_element_handler_t get_handler_for_code_signing_keys(const efi_guid_t *sig_ty
efi_element_handler_t get_handler_for_dbx(const efi_guid_t *sig_type);
#endif
-
-#ifndef UEFI_QUIRK_SKIP_CERT
-#define UEFI_QUIRK_SKIP_CERT(vendor, product) \
- .matches = { \
- DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
- DMI_MATCH(DMI_PRODUCT_NAME, product), \
- },
-#endif
diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c
index c0d694844..b4096d4c8 100644
--- a/security/integrity/platform_certs/load_uefi.c
+++ b/security/integrity/platform_certs/load_uefi.c
@@ -3,42 +3,16 @@
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cred.h>
-#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/efi.h>
#include <linux/slab.h>
#include <linux/ima.h>
+#include <linux/platform_data/x86/apple.h>
#include <keys/asymmetric-type.h>
#include <keys/system_keyring.h>
#include "../integrity.h"
#include "keyring_handler.h"
-/*
- * On T2 Macs reading the db and dbx efi variables to load UEFI Secure Boot
- * certificates causes occurrence of a page fault in Apple's firmware and
- * a crash disabling EFI runtime services. The following quirk skips reading
- * these variables.
- */
-static const struct dmi_system_id uefi_skip_cert[] = {
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,2") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,3") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro15,4") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,2") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,3") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookPro16,4") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir8,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir8,2") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacBookAir9,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "Macmini8,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "MacPro7,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,1") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMac20,2") },
- { UEFI_QUIRK_SKIP_CERT("Apple Inc.", "iMacPro1,1") },
- { }
-};
-
/*
* Look to see if a UEFI variable called MokIgnoreDB exists and return true if
* it does.
@@ -165,10 +139,14 @@ static int __init load_uefi_certs(void)
unsigned long dbsize = 0, dbxsize = 0, mokxsize = 0;
efi_status_t status;
int rc = 0;
- const struct dmi_system_id *dmi_id;
- dmi_id = dmi_first_match(uefi_skip_cert);
- if (dmi_id) {
+ /*
+ * On T2 Macs reading the db and dbx efi variables to load UEFI Secure Boot
+ * certificates causes occurrence of a page fault in Apple's firmware and
+ * a crash disabling EFI runtime services. The following quirk skips reading
+ * these variables.
+ */
+ if (has_apple_t2_chip) {
pr_err("Reading UEFI Secure Boot Certs is not supported on T2 Macs.\n");
return false;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [RFC PATCH 09/14] treewide: disable other rtc's in t2 macs
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (7 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 08/14] treewide: Add a flag to detect the Apple T2 chip Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:16 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 10/14] power/supply: add support for x86 macs in macsmc-power Atharva Tiwari
` (4 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
The CMOS RTC and the ACPI TAD clock don't work correctly on
T2 Macs. When macsmc-rtc is enabled, turn them off on these
machines so macsmc-rtc is the only clock.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/acpi/acpi_tad.c | 4 ++++
drivers/rtc/rtc-cmos.c | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c
index 45459a4ca..a137a39e5 100644
--- a/drivers/acpi/acpi_tad.c
+++ b/drivers/acpi/acpi_tad.c
@@ -28,6 +28,7 @@
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/platform_data/x86/apple.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/rtc.h>
@@ -845,6 +846,9 @@ static int acpi_tad_probe(struct platform_device *pdev)
return -ENODEV;
}
+ if (IS_ENABLED(CONFIG_RTC_DRV_MACSMC) && has_apple_t2_chip)
+ caps &= ~ACPI_TAD_RT;
+
if (!acpi_has_method(handle, "_PRW")) {
dev_info(dev, "Missing _PRW\n");
caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE);
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index fa04ece15..bdb436e24 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -33,6 +33,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
+#include <linux/platform_data/x86/apple.h>
#include <linux/platform_device.h>
#include <linux/log2.h>
#include <linux/pm.h>
@@ -944,6 +945,9 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
.priv = &cmos_rtc,
};
+ if (IS_ENABLED(CONFIG_RTC_DRV_MACSMC) && has_apple_t2_chip)
+ return -ENODEV;
+
/* there can be only one ... */
if (cmos_rtc.dev)
return -EBUSY;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 09/14] treewide: disable other rtc's in t2 macs
2026-10-29 8:52 ` [RFC PATCH 09/14] treewide: disable other rtc's in t2 macs Atharva Tiwari
@ 2026-10-29 9:16 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:16 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] rtc: acpi_tad_probe() and cmos_do_probe() miss CONFIG_MACSMC_ACPI check
- [High] rtc: cmos_do_probe() leaves x86 CMOS wallclock callbacks active on T2 Macs
--
commit 8f32ab6b5995f6b8ccbfee083b09d400a56632a7
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
treewide: disable other rtc's in t2 macs
This commit disables the ACPI TAD real-time clock capability and aborts
probing the CMOS RTC driver on Apple T2 Macs when CONFIG_RTC_DRV_MACSMC is
enabled, leaving macsmc-rtc as the active RTC.
> diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c
> index 45459a4cafae3..a137a39e5e67f 100644
> --- a/drivers/acpi/acpi_tad.c
> +++ b/drivers/acpi/acpi_tad.c
[ ... ]
> @@ -845,6 +846,9 @@ static int acpi_tad_probe(struct platform_device *pdev)
> return -ENODEV;
> }
>
> + if (IS_ENABLED(CONFIG_RTC_DRV_MACSMC) && has_apple_t2_chip)
> + caps &= ~ACPI_TAD_RT;
[Severity: High]
Could checking only IS_ENABLED(CONFIG_RTC_DRV_MACSMC) in acpi_tad_probe()
and cmos_do_probe() leave T2 Macs without any registered RTC device when
CONFIG_MACSMC_ACPI is disabled?
In drivers/rtc/Kconfig, RTC_DRV_MACSMC only depends on MFD_MACSMC_CORE:
config RTC_DRV_MACSMC
tristate "Apple Mac System Management Controller RTC"
depends on MFD_MACSMC_CORE
However, on x86 T2 Macs, the macsmc-rtc MFD child device is only registered
via apple_smc_acpi_probe() -> apple_smc_probe(), which requires
CONFIG_MACSMC_ACPI in drivers/platform/x86/Kconfig:
config MACSMC_ACPI
tristate "Apple SMC (System Management Controller) ACPI Backend"
depends on MFD_MACSMC_CORE && ACPI && !SENSORS_APPLESMC
When CONFIG_RTC_DRV_MACSMC is enabled alongside CONFIG_SENSORS_APPLESMC (or
when CONFIG_MACSMC_ACPI is otherwise disabled), macsmc-rtc is never
instantiated, yet acpi_tad_probe() and cmos_do_probe() will still disable
both the ACPI TAD RTC and the CMOS RTC.
Should both checks also verify IS_ENABLED(CONFIG_MACSMC_ACPI)?
> diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
> index fa04ece151b8b..bdb436e24bb82 100644
> --- a/drivers/rtc/rtc-cmos.c
> +++ b/drivers/rtc/rtc-cmos.c
[ ... ]
> @@ -944,6 +945,9 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
> .priv = &cmos_rtc,
> };
>
> + if (IS_ENABLED(CONFIG_RTC_DRV_MACSMC) && has_apple_t2_chip)
> + return -ENODEV;
[Severity: High]
When cmos_do_probe() returns -ENODEV here, what happens to the x86
architectural wallclock callbacks in x86_platform?
In arch/x86/kernel/x86_init.c, x86_platform defaults to the legacy CMOS
routines:
.get_wallclock = mach_get_cmos_time,
.set_wallclock = mach_set_cmos_time,
These are invoked by read_persistent_clock64() and
update_persistent_clock64() in arch/x86/kernel/rtc.c, which call
mc146818_get_time() and mc146818_set_time() directly on CMOS I/O ports
0x70/0x71 regardless of whether the rtc-cmos driver probed.
During NTP synchronization in kernel/time/ntp.c, sync_hw_clock() tries the
persistent clock before falling back to the RTC class device:
/* Try the legacy RTC first. */
res = update_persistent_clock64(to_set);
if (res != -ENODEV)
goto rearm;
/* Try the RTC class */
res = update_rtc(&to_set, &offset_nsec);
Because mach_set_cmos_time() -> mc146818_set_time() writes to the CMOS ports
and returns 0 (or -EINVAL, never -ENODEV), sync_hw_clock() will take the
goto rearm path and never call update_rtc() to update macsmc-rtc.
Similarly, timekeeping_init(), timekeeping_suspend(), and
timekeeping_resume() call read_persistent_clock64(), which will still read
the non-working CMOS clock via mach_get_cmos_time(). If that returns a
non-zero timestamp, persistent_clock_exists is set to true and
timekeeping_rtc_skipsuspend() causes rtc_suspend() to skip macsmc-rtc across
suspend and resume.
Should x86_platform.get_wallclock and x86_platform.set_wallclock also be
redirected to x86_init_noop / x86_wallclock_noop on T2 Macs when macsmc-rtc
is used?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=9
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 10/14] power/supply: add support for x86 macs in macsmc-power
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (8 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 09/14] treewide: disable other rtc's in t2 macs Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:19 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 11/14] acpi: disable battery and adapter for x86 apple machines Atharva Tiwari
` (3 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Make macsmc-power work on Intel Macs, as a replacement for
acpi-sbs. It exposes a lot more sensors than acpi-sbs, such
as voltage, current, power, temperature, charge and health
info, and also the adapter state.
It also adds a charge limit, which acpi-sbs doesn't offer.
And now we have power stats on non-mobile macs as well (iMacs,
MacPros,etc), using macsmc-ac.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/power/supply/macsmc-power.c | 751 ++++++++++++++++++++++------
1 file changed, 606 insertions(+), 145 deletions(-)
diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c
index ced37a701..1d64d8185 100644
--- a/drivers/power/supply/macsmc-power.c
+++ b/drivers/power/supply/macsmc-power.c
@@ -7,8 +7,11 @@
* on Apple Silicon systems.
*
* Copyright The Asahi Linux Contributors
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
*/
+#include <linux/acpi.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/devm-helpers.h>
@@ -80,12 +83,16 @@ struct macsmc_power {
char serial_number[MAX_STRING_LENGTH];
char mfg_date[MAX_STRING_LENGTH];
+ bool has_battery;
+
/* Supported feature flags based on SMC key presence */
bool has_chwa; /* Charge limit (Modern firmware) */
- bool has_chls; /* Charge limit (Older firmware) */
+ bool has_chls; /* Charge limit (Older firmware And New firmware on Intel macs) */
bool has_ch0i; /* Force discharge (Older firmware) */
bool has_ch0c; /* Inhibit charge (Older firmware) */
bool has_chte; /* Inhibit charge (Modern firmware) */
+ bool has_pd0r; /* AC Adapter current power in watts (Old Intel macs) */
+
/*
* Battery critical key is 1 byte and charge key is little endian
* (Modern firmware)
@@ -94,7 +101,9 @@ struct macsmc_power {
u8 num_cells;
int nominal_voltage_mv;
+ unsigned long long adapter_status;
+ struct acpi_device *adapter;
struct notifier_block nb;
struct work_struct critical_work;
bool emergency_shutdown_triggered;
@@ -195,6 +204,37 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
return POWER_SUPPLY_STATUS_CHARGING;
}
+static int macsmc_battery_acpi_get_status(struct macsmc_power *power)
+{
+ int ret;
+ s16 vs16;
+ u16 vu16;
+
+ ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
+ if (ret < 0)
+ return ret;
+
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
+ if (ret < 0)
+ return ret;
+
+ vu16 = swab16(vu16);
+ vs16 = (s16)swab16(vs16);
+
+ if (power->adapter_status) {
+ if (vu16 == 100 && vs16 <= 0)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ if (vs16 > 0)
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+
static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
{
int ret;
@@ -322,6 +362,198 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
}
+static int macsmc_battery_acpi_get_capacity_level(struct macsmc_power *power)
+{
+ int ret;
+ u16 vu16;
+
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
+ if (ret < 0)
+ return ret;
+
+ vu16 = swab16(vu16);
+
+ if (vu16 <= 5)
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ if (vu16 <= 20)
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ if (vu16 <= 80)
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ if (vu16 < 100)
+ return POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+}
+
+static int macsmc_battery_acpi_read_f32_scaled(struct apple_smc *smc, smc_key key,
+ int *p, int scale)
+{
+ u32 fval;
+ u64 val;
+ int ret, exp;
+
+ ret = apple_smc_read_u32(smc, key, &fval);
+ if (ret < 0)
+ return ret;
+
+ val = (u64)((fval & GENMASK(22, 0)) | BIT(23));
+ exp = ((fval >> 23) & 0xff) - 127 - 23;
+
+ val *= (u64)scale;
+
+ if (exp > 63)
+ val = U64_MAX;
+ else if (exp < -63)
+ val = 0;
+ else if (exp < 0)
+ val >>= -exp;
+ else if (exp != 0 &&
+ (val & ~((1ULL << (64 - exp)) - 1)))
+ val = U64_MAX;
+ else
+ val <<= exp;
+
+ if (fval & BIT(31)) {
+ if (val > (u64)INT_MAX + 1)
+ *p = INT_MIN;
+ else
+ *p = -(int)val;
+ } else {
+ if (val > INT_MAX)
+ *p = INT_MAX;
+ else
+ *p = (int)val;
+ }
+
+ return 0;
+}
+
+static int macsmc_battery_acpi_read(struct apple_smc *smc, smc_key key, int *val,
+ int scale)
+{
+ int ret;
+ struct apple_smc_key_info info;
+
+ ret = apple_smc_get_key_info(smc, key, &info);
+ if (ret)
+ return ret;
+
+ switch (info.type_code) {
+ /* 32-bit IEEE 754 float */
+ case __SMC_KEY('f', 'l', 't', ' '): {
+ int flt_ = 0;
+
+ ret = macsmc_battery_acpi_read_f32_scaled(smc, key, &flt_, scale);
+ if (ret)
+ return ret;
+
+ *val = flt_;
+ break;
+ }
+
+ /* 3.13 fixed point decimal */
+ case __SMC_KEY('f', 'p', '3', 'd'): {
+ u16 fp3d;
+
+ ret = apple_smc_read_u16(smc, key, &fp3d);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp3d), scale, BIT(13));
+ break;
+ }
+
+ /* 5.11 fixed point decimal */
+ case __SMC_KEY('f', 'p', '5', 'b'): {
+ u16 fp5b;
+
+ ret = apple_smc_read_u16(smc, key, &fp5b);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp5b), scale, BIT(11));
+ break;
+ }
+
+ /* 8.8 fixed point decimal */
+ case __SMC_KEY('f', 'p', '8', '8'): {
+ u16 fp88;
+
+ ret = apple_smc_read_u16(smc, key, &fp88);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp88), scale, BIT(8));
+ break;
+ }
+
+ /* 3.12 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '3', 'c'): {
+ s16 sp3c;
+
+ ret = apple_smc_read_s16(smc, key, &sp3c);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp3c), scale, BIT(12));
+ break;
+ }
+
+ /* 4.11 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '4', 'b'): {
+ s16 sp4b;
+
+ ret = apple_smc_read_s16(smc, key, &sp4b);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp4b), scale, BIT(11));
+ break;
+ }
+
+ /* 5.10 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '5', 'a'): {
+ s16 sp5a;
+
+ ret = apple_smc_read_s16(smc, key, &sp5a);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp5a), scale, BIT(10));
+ break;
+ }
+
+ /* 6.9 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '6', '9'): {
+ s16 sp69;
+
+ ret = apple_smc_read_s16(smc, key, &sp69);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp69), scale, BIT(9));
+ break;
+ }
+
+ /* 7.8 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '7', '8'): {
+ s16 sp78;
+
+ ret = apple_smc_read_s16(smc, key, &sp78);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp78), scale, BIT(8));
+ break;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
static s16 macsmc_swap_b0rm(struct macsmc_power *power, s16 b0rm)
{
/* B0RM was Big Endian, likely pass through from TI gas gauge */
@@ -343,7 +575,10 @@ static int macsmc_battery_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
- val->intval = macsmc_battery_get_status(power);
+ if (power->smc->is_acpi)
+ val->intval = macsmc_battery_acpi_get_status(power);
+ else
+ val->intval = macsmc_battery_get_status(power);
ret = val->intval < 0 ? val->intval : 0;
break;
case POWER_SUPPLY_PROP_PRESENT:
@@ -359,36 +594,71 @@ static int macsmc_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
- val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+ if (power->smc->is_acpi)
+ val->intval = vu16 == 0xffff ? 0 : swab16(vu16) * 60;
+ else
+ val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
break;
case POWER_SUPPLY_PROP_CAPACITY:
- ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
- val->intval = vu8;
+ if (power->smc->is_acpi) {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
+ val->intval = swab16(vu16);
+ } else {
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
+ val->intval = vu8;
+ }
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- val->intval = macsmc_battery_get_capacity_level(power);
+ if (power->smc->is_acpi)
+ val->intval = macsmc_battery_acpi_get_capacity_level(power);
+ else
+ val->intval = macsmc_battery_get_capacity_level(power);
ret = val->intval < 0 ? val->intval : 0;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi)
+ val->intval = swab16(vu16) * 1000;
+ else
+ val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
- val->intval = vs16 * 1000;
+ if (power->smc->is_acpi)
+ val->intval = (s16)swab16(vs16) * 1000;
+ else
+ val->intval = vs16 * 1000;
break;
case POWER_SUPPLY_PROP_POWER_NOW:
- ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
- val->intval = vs32 * 1000;
+ if (power->smc->is_acpi) {
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(B0AP),
+ &val->intval, 1000000);
+ if (ret < 0)
+ return ret;
+
+ val->intval = -val->intval;
+ }
+ else {
+ ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
+ val->intval = vs32 * 1000;
+ }
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi)
+ val->intval = swab16(vu16) * 1000;
+ else
+ val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
/* Calculate total max design voltage from per-cell maximum voltage */
- ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
- val->intval = vu16 * 1000 * power->num_cells;
+ if (power->smc->is_acpi) {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BCMV), &vu16);
+ val->intval = swab16(vu16) * 1000 * power->num_cells;
+ } else {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
+ val->intval = vu16 * 1000 * power->num_cells;
+ }
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
/* Lifetime min */
@@ -400,17 +670,36 @@ static int macsmc_battery_get_property(struct power_supply *psy,
ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16);
val->intval = vs16 * 1000;
break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (power->has_chls) {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
+ val->intval = vu16 & 0xFF;
+ } else {
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(BCLM), &vu8);
+ val->intval = vu8;
+ }
+ break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
- ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi) {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(CHBI), &vu16);
+ val->intval = swab16(vu16) * 1000;
+ } else {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
+ val->intval = vu16 * 1000;
+ }
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
- ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi) {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(CHBV), &vu16);
+ val->intval = swab16(vu16) * 1000;
+ } else {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
+ val->intval = vu16 * 1000;
+ }
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
@@ -418,11 +707,17 @@ static int macsmc_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi)
+ val->intval = swab16(vu16) * 1000;
+ else
+ val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
- val->intval = macsmc_swap_b0rm(power, vu16) * 1000;
+ if (power->smc->is_acpi)
+ val->intval = swab16(vu16) * 1000;
+ else
+ val->intval = macsmc_swap_b0rm(power, vu16) * 1000;
break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
@@ -430,15 +725,37 @@ static int macsmc_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_ENERGY_FULL:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
- val->intval = vu16 * power->nominal_voltage_mv;
+ if (power->smc->is_acpi) {
+ if (ret < 0)
+ return ret;
+
+ u16 voltage;
+
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &voltage);
+ val->intval = swab16(vu16) * swab16(voltage);
+ } else
+ val->intval = vu16 * power->nominal_voltage_mv;
break;
case POWER_SUPPLY_PROP_ENERGY_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
- val->intval = macsmc_swap_b0rm(power, vu16) * power->nominal_voltage_mv;
+ if (power->smc->is_acpi) {
+ if (ret < 0)
+ return ret;
+
+ u16 voltage;
+
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &voltage);
+ val->intval = swab16(vu16) * swab16(voltage);
+ } else
+ val->intval = macsmc_swap_b0rm(power, vu16) * power->nominal_voltage_mv;
break;
case POWER_SUPPLY_PROP_TEMP:
- ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
- val->intval = vu16 - 2732; /* Kelvin x10 to Celsius x10 */
+ if (power->smc->is_acpi)
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(TB0T), &val->intval, 10);
+ else {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
+ val->intval = vu16 - 2732; /* Kelvin x10 to Celsius x10 */
+ }
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
@@ -446,7 +763,10 @@ static int macsmc_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
- val->intval = vu16;
+ if (power->smc->is_acpi)
+ val->intval = swab16(vu16);
+ else
+ val->intval = vu16;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
@@ -489,6 +809,15 @@ static int macsmc_battery_set_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
return macsmc_battery_set_charge_behaviour(power, val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval > 100 || val->intval < 1)
+ return -EINVAL;
+ if (power->has_chls) {
+ u16 vu16 = val->intval & 0xFF;
+
+ return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), vu16);
+ }
+ return apple_smc_write_u8(power->smc, SMC_KEY(BCLM), (u8) val->intval);
default:
return -EINVAL;
}
@@ -500,6 +829,8 @@ static int macsmc_battery_property_is_writeable(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
return true;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return true;
default:
return false;
}
@@ -524,12 +855,47 @@ static int macsmc_ac_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
- ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
- val->intval = !!vu32;
+ if (power->smc->is_acpi) {
+ if (power->has_battery)
+ val->intval = power->adapter_status;
+ else
+ val->intval = 1;
+ } else {
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
+ val->intval = !!vu32;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (power->has_battery)
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(ID0R),
+ &val->intval, 1000000);
+ else
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(ID2R),
+ &val->intval, 1000000);
+ break;
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ if (power->has_pd0r)
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(PD0R),
+ &val->intval, 1000000);
+ else if (power->has_battery)
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(PDTR),
+ &val->intval, 1000000);
+ else
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(PD2R),
+ &val->intval, 1000000);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
- val->intval = vu16 * 1000;
+ if (power->smc->is_acpi) {
+ if (power->has_battery)
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(VD0R),
+ &val->intval, 1000000);
+ else
+ ret = macsmc_battery_acpi_read(power->smc, SMC_KEY(VD2R),
+ &val->intval, 1000000);
+ } else {
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
+ val->intval = vu16 * 1000;
+ }
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
@@ -537,7 +903,10 @@ static int macsmc_ac_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
- val->intval = vu32 * 1000;
+ if (power->smc->is_acpi)
+ val->intval = swab32(vu32) * 1000;
+ else
+ val->intval = vu32 * 1000;
break;
default:
return -EINVAL;
@@ -634,6 +1003,23 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
return NOTIFY_DONE;
}
+static void macsmc_power_acpi_notify(acpi_handle handle, u32 event, void *context)
+{
+ struct macsmc_power *power = context;
+
+ if (event == 0x80) {
+ acpi_status status = acpi_evaluate_integer(power->adapter->handle, "_PSR", NULL,
+ &power->adapter_status);
+ if (ACPI_FAILURE(status))
+ return;
+
+ power->adapter_status = !!power->adapter_status;
+ power_supply_changed(power->batt);
+ power_supply_changed(power->ac);
+ }
+}
+
+
static int macsmc_power_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -670,15 +1056,19 @@ static int macsmc_power_probe(struct platform_device *pdev)
* Check for battery presence.
* B0AV is a fundamental key.
*/
+ if (smc->is_acpi && apple_smc_read_flag(power->smc, SMC_KEY(BBIN), &flag) == 0 && flag)
+ has_battery = true;
+
if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 &&
macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN)
has_battery = true;
+ power->has_battery = has_battery;
/*
* Check for AC adapter presence.
* CHIS is a fundamental key.
*/
- if (apple_smc_key_exists(smc, SMC_KEY(CHIS)))
+ if (apple_smc_key_exists(smc, SMC_KEY(CHIS)) || smc->is_acpi)
has_ac_adapter = true;
if (!has_battery && !has_ac_adapter)
@@ -694,118 +1084,156 @@ static int macsmc_power_probe(struct platform_device *pdev)
nprops = 0;
- /* Fundamental properties */
- props[nprops++] = POWER_SUPPLY_PROP_STATUS;
- props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
- props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
- props[nprops++] = POWER_SUPPLY_PROP_TEMP;
- props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
- props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
- props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
- props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME;
- props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER;
- props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR;
- props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH;
- props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY;
-
- /* Extended properties usually present */
- props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
- props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
- props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
- props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
- props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER;
-
- /* Detect features based on key availability */
- if (apple_smc_key_exists(smc, SMC_KEY(CHTE)))
- power->has_chte = true;
- if (apple_smc_key_exists(smc, SMC_KEY(CH0C)))
- power->has_ch0c = true;
- if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
- power->has_ch0i = true;
-
- ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info);
- if (ret) {
- dev_err(&pdev->dev, "Failed to determine BCF0 key size\n");
- return ret;
- }
- if (info.size == 1)
- power->fw_ge_27 = true;
- else if (info.size == 4)
- power->fw_ge_27 = false;
- else {
- dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size);
- return -EIO;
- }
-
- /* Reset "Optimised Battery Charging" flags to default state */
- if (power->has_chte)
- apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
- else if (power->has_ch0c)
- apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);
-
- if (power->has_ch0i)
- apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);
-
- apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
- apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
-
- /* Configure charge behaviour if supported */
- if (power->has_ch0i || power->has_ch0c || power->has_chte) {
- props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
-
- power->batt_desc.charge_behaviours =
- BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
+ if (smc->is_acpi) {
+ props[nprops++] = POWER_SUPPLY_PROP_STATUS;
+ props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
+ props[nprops++] = POWER_SUPPLY_PROP_TEMP;
+ props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
+ props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
+ props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
+
+ if (apple_smc_key_exists(smc, SMC_KEY(CHLS)))
+ power->has_chls = true;
+ if (power->has_chls || apple_smc_key_exists(smc, SMC_KEY(BCLM)))
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
+ if (apple_smc_key_exists(smc, SMC_KEY(B0AP)))
+ props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
+ if (apple_smc_key_exists(smc, SMC_KEY(B0TE)))
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
+ if (apple_smc_key_exists(smc, SMC_KEY(BITV)))
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ if (apple_smc_key_exists(smc, SMC_KEY(BCMV)))
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+
+ if (apple_smc_key_exists(smc, SMC_KEY(BC3V)))
+ power->num_cells = 3;
+ else
+ power->num_cells = 2;
+ } else {
+ /* Fundamental properties */
+ props[nprops++] = POWER_SUPPLY_PROP_STATUS;
+ props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
+ props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
+ props[nprops++] = POWER_SUPPLY_PROP_TEMP;
+ props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
+ props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
+ props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
+ props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME;
+ props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH;
+ props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY;
+
+ /* Extended properties usually present */
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
+ props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
+ props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER;
+
+ /* Detect features based on key availability */
+ if (apple_smc_key_exists(smc, SMC_KEY(CHTE)))
+ power->has_chte = true;
+ if (apple_smc_key_exists(smc, SMC_KEY(CH0C)))
+ power->has_ch0c = true;
+ if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
+ power->has_ch0i = true;
+
+ ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to determine BCF0 key size\n");
+ return ret;
+ }
+ if (info.size == 1)
+ power->fw_ge_27 = true;
+ else if (info.size == 4)
+ power->fw_ge_27 = false;
+ else {
+ dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size);
+ return -EIO;
+ }
+
+ /* Reset "Optimised Battery Charging" flags to default state */
+ if (power->has_chte)
+ apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
+ else if (power->has_ch0c)
+ apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);
if (power->has_ch0i)
- power->batt_desc.charge_behaviours |=
- BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
-
- if (power->has_chte || power->has_ch0c)
- power->batt_desc.charge_behaviours |=
- BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
+ apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);
+
+ apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
+ apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
+
+ /* Configure charge behaviour if supported */
+ if (power->has_ch0i || power->has_ch0c || power->has_chte) {
+ props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
+
+ power->batt_desc.charge_behaviours =
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
+
+ if (power->has_ch0i)
+ power->batt_desc.charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
+
+ if (power->has_chte || power->has_ch0c)
+ power->batt_desc.charge_behaviours |=
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
+ }
+
+ /* Detect charge limit method (CHWA vs CHLS) */
+ if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
+ power->has_chwa = true;
+ else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0)
+ power->has_chls = true;
+
+ /* Fetch identity strings */
+ apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
+ sizeof(power->model_name) - 1);
+ apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
+ sizeof(power->serial_number) - 1);
+ apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
+ sizeof(power->mfg_date) - 1);
+
+ apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
+ power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV *
+ power->num_cells;
+
+ /* Enable critical shutdown notifications by reading status once */
+ macsmc_battery_read_bcf0(power, &val32);
}
-
- /* Detect charge limit method (CHWA vs CHLS) */
- if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
- power->has_chwa = true;
- else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0)
- power->has_chls = true;
-
if (nprops > MACSMC_MAX_BATT_PROPS)
return -ENOMEM;
power->batt_desc.properties = props;
power->batt_desc.num_properties = nprops;
- /* Fetch identity strings */
- apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
- sizeof(power->model_name) - 1);
- apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
- sizeof(power->serial_number) - 1);
- apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
- sizeof(power->mfg_date) - 1);
-
- apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
- power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
-
- /* Enable critical shutdown notifications by reading status once */
- macsmc_battery_read_bcf0(power, &val32);
-
psy_cfg.drv_data = power;
power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg);
if (IS_ERR(power->batt)) {
@@ -829,16 +1257,27 @@ static int macsmc_power_probe(struct platform_device *pdev)
/* Online status is fundamental */
props[nprops++] = POWER_SUPPLY_PROP_ONLINE;
- /* Input power limits are usually available */
- if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
- props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
-
- /* macOS 15.4+ firmware dropped legacy AC keys (AC-n, AC-i) */
- if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) {
- props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
- props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
+ if (smc->is_acpi) {
+ props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
+ if (apple_smc_key_exists(power->smc, SMC_KEY(PD0R)))
+ power->has_pd0r = true;
+ /* MacBookPro5,x and 7,x dosent have VD0R key or any equivalent */
+ if (!power->has_battery || apple_smc_key_exists(power->smc, SMC_KEY(VD0R)))
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
+ props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
+ } else {
+ /* Input power limits are usually available */
+ if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
+ props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
+
+ /* macOS 15.4+ firmware dropped legacy AC keys (AC-n, AC-i) */
+ if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) {
+ props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
+ }
}
-
if (nprops > MACSMC_MAX_AC_PROPS)
return -ENOMEM;
@@ -858,9 +1297,26 @@ static int macsmc_power_probe(struct platform_device *pdev)
if (!power->batt && !power->ac)
return -ENODEV;
- power->nb.notifier_call = macsmc_power_event;
- blocking_notifier_chain_register(smc->event_handlers, &power->nb);
+ if (!smc->is_acpi) {
+ power->nb.notifier_call = macsmc_power_event;
+ blocking_notifier_chain_register(smc->event_handlers, &power->nb);
+ } else if (power->has_battery) {
+ power->adapter = acpi_dev_get_first_match_dev("ACPI0003", NULL, -1);
+ if (!power->adapter)
+ return -ENODEV;
+ ret = acpi_dev_install_notify_handler(power->adapter, ACPI_DEVICE_NOTIFY,
+ macsmc_power_acpi_notify, power);
+ if (ret)
+ return ret;
+
+ acpi_status status = acpi_evaluate_integer(power->adapter->handle, "_PSR", NULL,
+ &power->adapter_status);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ power->adapter_status = !!power->adapter_status;
+ }
return 0;
}
@@ -868,7 +1324,11 @@ static void macsmc_power_remove(struct platform_device *pdev)
{
struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
- blocking_notifier_chain_unregister(power->smc->event_handlers, &power->nb);
+ if (!power->smc->is_acpi)
+ blocking_notifier_chain_unregister(power->smc->event_handlers, &power->nb);
+ else
+ acpi_dev_remove_notify_handler(power->adapter, ACPI_DEVICE_NOTIFY,
+ macsmc_power_acpi_notify);
}
static const struct platform_device_id macsmc_power_id[] = {
@@ -891,3 +1351,4 @@ MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("Apple SMC battery and power management driver");
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
+MODULE_ALIAS("platform:macsmc-power");
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 10/14] power/supply: add support for x86 macs in macsmc-power
2026-10-29 8:52 ` [RFC PATCH 10/14] power/supply: add support for x86 macs in macsmc-power Atharva Tiwari
@ 2026-10-29 9:19 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:19 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] power: supply: macsmc: NULL deref in macsmc_power_remove() without battery
- [High] power: supply: macsmc: notify handler UAF and ref leak in macsmc_power_probe()
- [High] power: supply: macsmc: unchecked power supplies in macsmc_power_acpi_notify()
- [Medium] power: supply: macsmc: uninitialized adapter_status in macsmc_power_probe()
- [Medium] power: supply: macsmc: missing swab16() for B0TE on ACPI Macs
- [Medium] power: supply: macsmc: missing else in macsmc_power_probe() battery check
- [Medium] power: supply: macsmc: 32-bit overflow in macsmc_battery_acpi_read()
- [Low] power: supply: macsmc: build failure when CONFIG_ACPI is disabled
--
commit 14a9bd57ae09c9ca5072f486dd3745277286ab3b
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
power/supply: add support for x86 macs in macsmc-power
This commit adds support for Intel Macs to the macsmc-power driver,
exposing battery and AC adapter sensors as well as charge limit controls
on ACPI-based Apple systems.
> diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c
> index ced37a7019981..1d64d8185ed7b 100644
> --- a/drivers/power/supply/macsmc-power.c
> +++ b/drivers/power/supply/macsmc-power.c
[ ... ]
> @@ -322,6 +362,198 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
[ ... ]
> +static int macsmc_battery_acpi_read(struct apple_smc *smc, smc_key key, int *val,
> + int scale)
> +{
> + int ret;
> + struct apple_smc_key_info info;
> +
> + ret = apple_smc_get_key_info(smc, key, &info);
> + if (ret)
> + return ret;
> +
> + switch (info.type_code) {
> + /* 32-bit IEEE 754 float */
> + case __SMC_KEY('f', 'l', 't', ' '): {
> + int flt_ = 0;
> +
> + ret = macsmc_battery_acpi_read_f32_scaled(smc, key, &flt_, scale);
> + if (ret)
> + return ret;
> +
> + *val = flt_;
> + break;
> + }
> +
> + /* 3.13 fixed point decimal */
> + case __SMC_KEY('f', 'p', '3', 'd'): {
> + u16 fp3d;
> +
> + ret = apple_smc_read_u16(smc, key, &fp3d);
> + if (ret)
> + return ret;
> +
> + *val = mult_frac(be16_to_cpu(fp3d), scale, BIT(13));
> + break;
> + }
[ ... ]
> + /* 3.12 signed fixed point decimal */
> + case __SMC_KEY('s', 'p', '3', 'c'): {
> + s16 sp3c;
> +
> + ret = apple_smc_read_s16(smc, key, &sp3c);
> + if (ret)
> + return ret;
> +
> + *val = mult_frac((s16)be16_to_cpu(sp3c), scale, BIT(12));
> + break;
> + }
[Severity: Medium]
Can the mult_frac() calls for the fp3d and sp3c types overflow a 32-bit
signed int when scale is 1000000?
Inside mult_frac(x, n, d):
typeof(x_) q = x_ / d_;
typeof(x_) r = x_ % d_;
q * n_ + r * n_ / d_;
Because x is u16 or s16 and scale is int (1000000), r * n_ is evaluated as
a 32-bit signed int multiplication before being converted to unsigned long
for division by BIT(13) (8192) or BIT(12) (4096). Whenever the fractional
remainder r is 2148 or larger, r * 1000000 exceeds INT_MAX and wraps around,
producing corrupted (and often negative) current, voltage, or power readings.
[ ... ]
> @@ -359,36 +594,71 @@ static int macsmc_battery_get_property(struct power_supply *psy,
> break;
> case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
> ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
> - val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
> + if (power->smc->is_acpi)
> + val->intval = vu16 == 0xffff ? 0 : swab16(vu16) * 60;
> + else
> + val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
> break;
[Severity: Medium]
In macsmc_battery_get_property(), POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW right
above this case reads SMC_KEY(B0TE) into vu16 without byte-swapping when
power->smc->is_acpi is true:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
break;
Since macsmc_power_probe() registers POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW on
ACPI Macs when B0TE is present, should B0TE also apply swab16(vu16) on ACPI
systems just like B0TF does here?
[ ... ]
> @@ -634,6 +1003,23 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
> return NOTIFY_DONE;
> }
>
> +static void macsmc_power_acpi_notify(acpi_handle handle, u32 event, void *context)
> +{
> + struct macsmc_power *power = context;
> +
> + if (event == 0x80) {
> + acpi_status status = acpi_evaluate_integer(power->adapter->handle, "_PSR", NULL,
> + &power->adapter_status);
> + if (ACPI_FAILURE(status))
> + return;
> +
> + power->adapter_status = !!power->adapter_status;
> + power_supply_changed(power->batt);
> + power_supply_changed(power->ac);
> + }
> +}
[Severity: High]
What happens in macsmc_power_acpi_notify() if either power->batt or
power->ac failed to register during macsmc_power_probe()?
In macsmc_power_probe(), if devm_power_supply_register() fails for one of
the supplies, the failed pointer is set to NULL and probe continues as long
as the other supply succeeded. Unlike macsmc_power_event(), which checks
if (power->batt) and if (power->ac), macsmc_power_acpi_notify() passes both
pointers unconditionally to power_supply_changed(), which immediately
dereferences the pointer.
> +
> +
> static int macsmc_power_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -670,15 +1056,19 @@ static int macsmc_power_probe(struct platform_device *pdev)
> * Check for battery presence.
> * B0AV is a fundamental key.
> */
> + if (smc->is_acpi && apple_smc_read_flag(power->smc, SMC_KEY(BBIN), &flag) == 0 && flag)
> + has_battery = true;
> +
> if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 &&
> macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN)
> has_battery = true;
[Severity: Medium]
Should the second battery check be an else if (!smc->is_acpi && ...) branch?
As written, ACPI Macs always fall through to the second check and call
macsmc_battery_get_status(power), which queries Apple Silicon SMC keys
(CH0R, CHCE, CHCC, AC-i, BSFC, CHNC, CHSC) even when smc->is_acpi is true
and BBIN indicated no battery is present.
[ ... ]
> @@ -858,9 +1297,26 @@ static int macsmc_power_probe(struct platform_device *pdev)
> if (!power->batt && !power->ac)
> return -ENODEV;
>
> - power->nb.notifier_call = macsmc_power_event;
> - blocking_notifier_chain_register(smc->event_handlers, &power->nb);
> + if (!smc->is_acpi) {
> + power->nb.notifier_call = macsmc_power_event;
> + blocking_notifier_chain_register(smc->event_handlers, &power->nb);
> + } else if (power->has_battery) {
> + power->adapter = acpi_dev_get_first_match_dev("ACPI0003", NULL, -1);
> + if (!power->adapter)
> + return -ENODEV;
>
> + ret = acpi_dev_install_notify_handler(power->adapter, ACPI_DEVICE_NOTIFY,
> + macsmc_power_acpi_notify, power);
> + if (ret)
> + return ret;
> +
> + acpi_status status = acpi_evaluate_integer(power->adapter->handle, "_PSR", NULL,
> + &power->adapter_status);
> + if (ACPI_FAILURE(status))
> + return -EIO;
> +
> + power->adapter_status = !!power->adapter_status;
> + }
> return 0;
> }
[Severity: High]
If acpi_evaluate_integer() fails here in macsmc_power_probe(), does this
leave macsmc_power_acpi_notify() installed with a freed power context
pointer?
acpi_dev_install_notify_handler() is not devm-managed, so returning -EIO
causes devres to free power while the ACPI notify handler remains registered
on power->adapter; a subsequent 0x80 notify event will then dereference
freed memory in macsmc_power_acpi_notify(). In addition,
acpi_dev_get_first_match_dev() takes a reference on power->adapter via
get_device(), which is never released with acpi_dev_put() on these error
paths or in macsmc_power_remove().
[Severity: Medium]
Also in macsmc_power_probe(), devm_power_supply_register() is called for
both power->batt and power->ac before power->adapter is looked up and before
_PSR is evaluated into power->adapter_status.
Because __power_supply_register() queries the supply properties during
registration, won't the initial reads of POWER_SUPPLY_PROP_STATUS in
macsmc_battery_acpi_get_status() and POWER_SUPPLY_PROP_ONLINE in
macsmc_ac_get_property() always observe power->adapter_status as 0 even when
AC power is connected? Furthermore, evaluating _PSR directly into
power->adapter_status and normalizing it in place afterwards allows
concurrent property reads to observe the unnormalized value.
>
> static void macsmc_power_remove(struct platform_device *pdev)
> {
> struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
>
> - blocking_notifier_chain_unregister(power->smc->event_handlers, &power->nb);
> + if (!power->smc->is_acpi)
> + blocking_notifier_chain_unregister(power->smc->event_handlers, &power->nb);
> + else
> + acpi_dev_remove_notify_handler(power->adapter, ACPI_DEVICE_NOTIFY,
> + macsmc_power_acpi_notify);
> }
[Severity: High]
On desktop Intel Macs without a battery (where power->smc->is_acpi is true
and power->has_battery is false), power->adapter is never initialized in
macsmc_power_probe() and remains NULL.
When macsmc_power_remove() runs on those machines, won't passing
power->adapter to acpi_dev_remove_notify_handler() cause a NULL pointer
dereference when it accesses adev->handle?
[Severity: Low]
Finally, does this driver still build when CONFIG_ACPI is disabled (for
example, on arm64 Apple Silicon configs with CONFIG_ACPI=n)?
In include/acpi/acpi_bus.h, the full definition of struct acpi_device as
well as acpi_evaluate_integer(), acpi_dev_install_notify_handler(), and
acpi_dev_remove_notify_handler() are only provided under #ifdef CONFIG_ACPI
without !CONFIG_ACPI stubs, so dereferencing power->adapter->handle and
calling those functions in macsmc_power_acpi_notify(), macsmc_power_probe(),
and macsmc_power_remove() will fail to compile when CONFIG_ACPI is not
enabled.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=10
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 11/14] acpi: disable battery and adapter for x86 apple machines
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (9 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 10/14] power/supply: add support for x86 macs in macsmc-power Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:13 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 12/14] hwmon/macsmc: add more types Atharva Tiwari
` (2 subsequent siblings)
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
When macsmc-power is enabled on an Intel Mac, skip acpi-sbs
and the ACPI battery and adapter drivers, since macsmc-power
now provides them. This avoids duplicate devices.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/acpi/sbs.c | 17 +++++++++--------
drivers/acpi/x86/utils.c | 4 ++++
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
index f10bbf13c..b12f2acc0 100644
--- a/drivers/acpi/sbs.c
+++ b/drivers/acpi/sbs.c
@@ -634,6 +634,9 @@ static int acpi_sbs_probe(struct platform_device *pdev)
int result = 0;
int id;
+ if (IS_ENABLED(CONFIG_MACSMC_POWER) && x86_apple_machine)
+ return -ENODEV;
+
device = ACPI_COMPANION(&pdev->dev);
if (!device)
return -ENODEV;
@@ -655,14 +658,12 @@ static int acpi_sbs_probe(struct platform_device *pdev)
result = 0;
- if (!x86_apple_machine) {
- result = acpi_manager_get_info(sbs);
- if (!result) {
- sbs->manager_present = 1;
- for (id = 0; id < MAX_SBS_BAT; ++id)
- if ((sbs->batteries_supported & (1 << id)))
- acpi_battery_add(sbs, id);
- }
+ result = acpi_manager_get_info(sbs);
+ if (!result) {
+ sbs->manager_present = 1;
+ for (id = 0; id < MAX_SBS_BAT; ++id)
+ if ((sbs->batteries_supported & (1 << id)))
+ acpi_battery_add(sbs, id);
}
if (!sbs->manager_present)
diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c
index 418951639..9f3b9b7d9 100644
--- a/drivers/acpi/x86/utils.c
+++ b/drivers/acpi/x86/utils.c
@@ -14,6 +14,7 @@
#include <linux/dmi.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/platform_data/x86/apple.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include "../internal.h"
@@ -608,6 +609,9 @@ bool acpi_quirk_skip_acpi_ac_and_battery(void)
long quirks = 0;
int i;
+ if (IS_ENABLED(CONFIG_MACSMC_POWER) && x86_apple_machine)
+ return true;
+
dmi_id = dmi_first_match(acpi_quirk_skip_dmi_ids);
if (dmi_id)
quirks = (unsigned long)dmi_id->driver_data;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 11/14] acpi: disable battery and adapter for x86 apple machines
2026-10-29 8:52 ` [RFC PATCH 11/14] acpi: disable battery and adapter for x86 apple machines Atharva Tiwari
@ 2026-10-29 9:13 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:13 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] acpi: sbs: SMBus hang and probe failure in acpi_sbs_probe() on Apple x86
- [High] acpi: missing CONFIG_MACSMC_ACPI check disables battery and AC on x86 Macs
--
commit a9ef8c0ab923a26344436713c10dccfd42ea84d9
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
acpi: disable battery and adapter for x86 apple machines
This commit skips the ACPI SBS, battery, and AC adapter drivers on x86 Apple
machines when CONFIG_MACSMC_POWER is enabled to avoid registering duplicate
power supply devices.
> diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
> index f10bbf13c242f..b12f2acc0a15d 100644
> --- a/drivers/acpi/sbs.c
> +++ b/drivers/acpi/sbs.c
> @@ -634,6 +634,9 @@ static int acpi_sbs_probe(struct platform_device *pdev)
> int result = 0;
> int id;
>
> + if (IS_ENABLED(CONFIG_MACSMC_POWER) && x86_apple_machine)
> + return -ENODEV;
> +
> device = ACPI_COMPANION(&pdev->dev);
> if (!device)
> return -ENODEV;
> @@ -655,14 +658,12 @@ static int acpi_sbs_probe(struct platform_device *pdev)
>
> result = 0;
>
> - if (!x86_apple_machine) {
> - result = acpi_manager_get_info(sbs);
> - if (!result) {
> - sbs->manager_present = 1;
> - for (id = 0; id < MAX_SBS_BAT; ++id)
> - if ((sbs->batteries_supported & (1 << id)))
> - acpi_battery_add(sbs, id);
> - }
> + result = acpi_manager_get_info(sbs);
> + if (!result) {
> + sbs->manager_present = 1;
> + for (id = 0; id < MAX_SBS_BAT; ++id)
> + if ((sbs->batteries_supported & (1 << id)))
> + acpi_battery_add(sbs, id);
> }
[Severity: High]
What happens on x86 Apple machines when CONFIG_MACSMC_POWER is disabled?
When CONFIG_MACSMC_POWER=n, acpi_sbs_probe() does not return early on
x86_apple_machine, and removing the !x86_apple_machine check causes it to
call acpi_manager_get_info(sbs) -> acpi_smbus_read(..., ACPI_SBS_MANAGER, ...).
Commit 9faf6136ff46 ("ACPI / SBS: Disable smart battery manager on Apple")
and commit 630b3aff8a51 guarded this call because accessing ACPI_SBS_MANAGER
on Apple hardware causes the SMBus and EC controller to hang for several
minutes when unplugging the AC adapter. Doesn't removing the
!x86_apple_machine check reintroduce this controller hang whenever
CONFIG_MACSMC_POWER is disabled?
In addition, by removing the !x86_apple_machine condition around
acpi_manager_get_info(sbs), the preceding result = 0 assignment becomes a
dead store, and sbs->manager_present remains 0 only when
acpi_manager_get_info(sbs) returns a non-zero error:
result = acpi_manager_get_info(sbs);
if (!result) {
sbs->manager_present = 1;
...
}
if (!sbs->manager_present)
acpi_battery_add(sbs, 0);
acpi_smbus_register_callback(sbs->hc, acpi_sbs_callback, sbs);
end:
if (result)
acpi_sbs_remove(pdev);
return result;
When acpi_manager_get_info(sbs) fails with a non-zero result, the
!sbs->manager_present fallback registers battery 0 and the SMBus callback,
but because result is still non-zero at the end label, acpi_sbs_remove(pdev)
immediately tears everything back down and fails the probe. Should the
!x86_apple_machine check be kept?
> diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c
> index 418951639f511..9f3b9b7d9c27c 100644
> --- a/drivers/acpi/x86/utils.c
> +++ b/drivers/acpi/x86/utils.c
> @@ -14,6 +14,7 @@
> #include <linux/dmi.h>
> #include <linux/pci.h>
> #include <linux/platform_device.h>
> +#include <linux/platform_data/x86/apple.h>
> #include <asm/cpu_device_id.h>
> #include <asm/intel-family.h>
> #include "../internal.h"
> @@ -608,6 +609,9 @@ bool acpi_quirk_skip_acpi_ac_and_battery(void)
> long quirks = 0;
> int i;
>
> + if (IS_ENABLED(CONFIG_MACSMC_POWER) && x86_apple_machine)
> + return true;
[Severity: High]
Should both acpi_quirk_skip_acpi_ac_and_battery() and acpi_sbs_probe() also
check IS_ENABLED(CONFIG_MACSMC_ACPI)?
In drivers/power/supply/Kconfig, MACSMC_POWER only depends on
MFD_MACSMC_CORE:
config MACSMC_POWER
tristate "Apple SMC Battery and Power Driver"
depends on MFD_MACSMC_CORE
However, on x86 Apple machines, the macsmc-power MFD device is only
registered when apple_smc_acpi_probe() in drivers/platform/x86/macsmc-acpi.c
probes the SMC device, which is controlled by CONFIG_MACSMC_ACPI in
drivers/platform/x86/Kconfig:
config MACSMC_ACPI
tristate "Apple SMC (System Management Controller) ACPI Backend"
depends on MFD_MACSMC_CORE && ACPI && !SENSORS_APPLESMC
If a kernel is built with CONFIG_MACSMC_POWER enabled and CONFIG_MACSMC_ACPI
disabled (for example, whenever CONFIG_SENSORS_APPLESMC is enabled), won't
checking only IS_ENABLED(CONFIG_MACSMC_POWER) disable the ACPI SBS, battery,
and AC adapter drivers on x86 Macs without any macsmc-acpi backend built to
instantiate macsmc-power?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=11
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 12/14] hwmon/macsmc: add more types
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (10 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 11/14] acpi: disable battery and adapter for x86 apple machines Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:18 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 13/14] hwmon: add support for x86 macs in macsmc-hwmon Atharva Tiwari
2026-10-29 8:52 ` [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys Atharva Tiwari
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
SMC keys come in many data types, and Intel Macs use a lot
that macsmc-hwmon couldn't read, such as the signed fixed
point types like sp1e.
Add support for the common integer and fixed point types,
and allow writing fan speeds.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/hwmon/macsmc-hwmon.c | 310 +++++++++++++++++++++++++++++++++++
1 file changed, 310 insertions(+)
diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
index 1500ec2cc..2e74dcd66 100644
--- a/drivers/hwmon/macsmc-hwmon.c
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -221,6 +221,313 @@ static int macsmc_hwmon_read_key(struct apple_smc *smc,
*val = (long)ioft;
break;
}
+ /* unsigned 8-bit integer */
+ case __SMC_KEY('u', 'i', '8', ' '): {
+ u8 ui8 = 0;
+
+ ret = apple_smc_read_u8(smc, sensor->macsmc_key, &ui8);
+ if (ret)
+ return ret;
+
+ *val = ui8 * scale;
+ break;
+ }
+ /* signed 8-bit integer */
+ case __SMC_KEY('s', 'i', '8', ' '): {
+ s8 si8 = 0;
+
+ ret = apple_smc_read_s8(smc, sensor->macsmc_key, &si8);
+ if (ret)
+ return ret;
+
+ *val = si8 * scale;
+ break;
+ }
+ /* unsigned 16-bit integer */
+ case __SMC_KEY('u', 'i', '1', '6'): {
+ u16 ui16 = 0;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &ui16);
+ if (ret)
+ return ret;
+
+ *val = ui16 * scale;
+ break;
+ }
+ /* signed 16-bit integer */
+ case __SMC_KEY('s', 'i', '1', '6'): {
+ s16 si16 = 0;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &si16);
+ if (ret)
+ return ret;
+
+ *val = si16 * scale;
+ break;
+ }
+ /* unsigned 32-bit integer */
+ case __SMC_KEY('u', 'i', '3', '2'): {
+ u32 ui32 = 0;
+
+ ret = apple_smc_read_u32(smc, sensor->macsmc_key, &ui32);
+ if (ret)
+ return ret;
+
+ *val = ui32 * scale;
+ break;
+ }
+ /* signed 32-bit integer */
+ case __SMC_KEY('s', 'i', '3', '2'): {
+ s32 si32;
+
+ ret = apple_smc_read_s32(smc, sensor->macsmc_key, &si32);
+ if (ret)
+ return ret;
+
+ *val = si32 * scale;
+ break;
+ }
+ /* 2.14 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '2', 'e'): {
+ u16 fp2e;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp2e);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp2e), scale, BIT(14));
+ break;
+ }
+
+ /* 3.13 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '3', 'd'): {
+ u16 fp3d;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp3d);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp3d), scale, BIT(13));
+ break;
+ }
+
+ /* 4.12 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '4', 'c'): {
+ u16 fp4c;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp4c);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp4c), scale, BIT(12));
+ break;
+ }
+
+ /* 5.11 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '5', 'b'): {
+ u16 fp5b;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp5b);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp5b), scale, BIT(11));
+ break;
+ }
+
+ /* 6.10 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '6', 'a'): {
+ u16 fp6a;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp6a);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp6a), scale, BIT(10));
+ break;
+ }
+
+ /* 8.8 unsigned fixed point decimal */
+ case __SMC_KEY('f', 'p', '8', '8'): {
+ u16 fp88;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp88);
+ if (ret)
+ return ret;
+
+ *val = mult_frac(be16_to_cpu(fp88), scale, BIT(8));
+ break;
+ }
+ /* 14.2 fixed point decimal */
+ case __SMC_KEY('f', 'p', 'e', '2'): {
+ /*
+ * fpe2 is only used for fan stats and fan control
+ * so precision is not needed
+ */
+ u16 fpe2;
+
+ ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fpe2);
+ if (ret)
+ return ret;
+
+ *val = (be16_to_cpu(fpe2) >> 2);
+ break;
+ }
+ /* 1.14 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '1', 'e'): {
+ s16 sp1e;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp1e);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp1e), scale, BIT(14));
+ break;
+ }
+
+ /* 2.13 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '2', 'd'): {
+ s16 sp2d;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp2d);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp2d), scale, BIT(13));
+ break;
+ }
+
+ /* 3.12 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '3', 'c'): {
+ s16 sp3c;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp3c);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp3c), scale, BIT(12));
+ break;
+ }
+
+ /* 4.11 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '4', 'b'): {
+ s16 sp4b;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp4b);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp4b), scale, BIT(11));
+ break;
+ }
+
+ /* 5.10 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '5', 'a'): {
+ s16 sp5a;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp5a);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp5a), scale, BIT(10));
+ break;
+ }
+
+ /* 6.9 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '6', '9'): {
+ s16 sp69;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp69);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp69), scale, BIT(9));
+ break;
+ }
+
+ /* 7.8 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '7', '8'): {
+ s16 sp78;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp78);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp78), scale, BIT(8));
+ break;
+ }
+
+ /* 8.7 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '8', '7'): {
+ s16 sp87;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp87);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp87), scale, BIT(7));
+ break;
+ }
+
+ /* 9.6 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', '9', '6'): {
+ s16 sp96;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp96);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(sp96), scale, BIT(6));
+ break;
+ }
+
+ /* 10.5 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', 'a', '5'): {
+ s16 spa5;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &spa5);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(spa5), scale, BIT(5));
+ break;
+ }
+
+ /* 12.3 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', 'c', '3'): {
+ s16 spc3;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &spc3);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(spc3), scale, BIT(3));
+ break;
+ }
+
+ /* 13.2 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', 'd', '2'): {
+ s16 spd2;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &spd2);
+ if (ret)
+ return ret;
+
+ *val = mult_frac((s16)be16_to_cpu(spd2), scale, BIT(2));
+ break;
+ }
+
+ /* 15.0 signed fixed point decimal */
+ case __SMC_KEY('s', 'p', 'f', '0'): {
+ s16 spf0;
+
+ ret = apple_smc_read_s16(smc, sensor->macsmc_key, &spf0);
+ if (ret)
+ return ret;
+
+ *val = (s16)be16_to_cpu(spf0) * scale;
+ break;
+ }
default:
return -EOPNOTSUPP;
}
@@ -263,6 +570,9 @@ static int macsmc_hwmon_write_key(struct apple_smc *smc,
/* unsigned 8-bit integer */
case __SMC_KEY('u', 'i', '8', ' '):
return apple_smc_write_u8(smc, sensor->macsmc_key, val);
+ /* 14.2 fixed point decimal */
+ case __SMC_KEY('f', 'p', 'e', '2'):
+ return apple_smc_write_u16(smc, sensor->macsmc_key, cpu_to_be16(val << 2));
default:
return -EOPNOTSUPP;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 12/14] hwmon/macsmc: add more types
2026-10-29 8:52 ` [RFC PATCH 12/14] hwmon/macsmc: add more types Atharva Tiwari
@ 2026-10-29 9:18 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:18 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] hwmon: 32-bit integer overflow when scaling values in macsmc_hwmon_read_key()
--
commit 8d01f24623ebf973c1f3f366376ae2ac53217c82
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
hwmon/macsmc: add more types
This commit adds support for reading common integer (ui8, si8, ui16, si16,
ui32, si32) and fixed-point (fp* and sp*) SMC key data types in
macsmc_hwmon_read_key(), and allows writing fpe2 keys for fan speed control
in macsmc_hwmon_write_key().
> diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
> index 1500ec2cc9f83..2e74dcd6651b3 100644
> --- a/drivers/hwmon/macsmc-hwmon.c
> +++ b/drivers/hwmon/macsmc-hwmon.c
> @@ -221,6 +221,313 @@ static int macsmc_hwmon_read_key(struct apple_smc *smc,
> *val = (long)ioft;
> break;
> }
[ ... ]
> + /* unsigned 16-bit integer */
> + case __SMC_KEY('u', 'i', '1', '6'): {
> + u16 ui16 = 0;
> +
> + ret = apple_smc_read_u16(smc, sensor->macsmc_key, &ui16);
> + if (ret)
> + return ret;
> +
> + *val = ui16 * scale;
> + break;
> + }
> + /* signed 16-bit integer */
> + case __SMC_KEY('s', 'i', '1', '6'): {
> + s16 si16 = 0;
> +
> + ret = apple_smc_read_s16(smc, sensor->macsmc_key, &si16);
> + if (ret)
> + return ret;
> +
> + *val = si16 * scale;
> + break;
> + }
> + /* unsigned 32-bit integer */
> + case __SMC_KEY('u', 'i', '3', '2'): {
> + u32 ui32 = 0;
> +
> + ret = apple_smc_read_u32(smc, sensor->macsmc_key, &ui32);
> + if (ret)
> + return ret;
> +
> + *val = ui32 * scale;
> + break;
> + }
> + /* signed 32-bit integer */
> + case __SMC_KEY('s', 'i', '3', '2'): {
> + s32 si32;
> +
> + ret = apple_smc_read_s32(smc, sensor->macsmc_key, &si32);
> + if (ret)
> + return ret;
> +
> + *val = si32 * scale;
> + break;
> + }
> + /* 2.14 unsigned fixed point decimal */
> + case __SMC_KEY('f', 'p', '2', 'e'): {
> + u16 fp2e;
> +
> + ret = apple_smc_read_u16(smc, sensor->macsmc_key, &fp2e);
> + if (ret)
> + return ret;
> +
> + *val = mult_frac(be16_to_cpu(fp2e), scale, BIT(14));
> + break;
> + }
[ ... ]
> + /* 1.14 signed fixed point decimal */
> + case __SMC_KEY('s', 'p', '1', 'e'): {
> + s16 sp1e;
> +
> + ret = apple_smc_read_s16(smc, sensor->macsmc_key, &sp1e);
> + if (ret)
> + return ret;
> +
> + *val = mult_frac((s16)be16_to_cpu(sp1e), scale, BIT(14));
> + break;
> + }
[ ... ]
> + /* 15.0 signed fixed point decimal */
> + case __SMC_KEY('s', 'p', 'f', '0'): {
> + s16 spf0;
> +
> + ret = apple_smc_read_s16(smc, sensor->macsmc_key, &spf0);
> + if (ret)
> + return ret;
> +
> + *val = (s16)be16_to_cpu(spf0) * scale;
> + break;
> + }
[Severity: Medium]
Can these scaling calculations in macsmc_hwmon_read_key() overflow 32-bit
integers and corrupt sensor readings when called from macsmc_hwmon_read()?
In macsmc_hwmon_read(), scale is 1000000 for hwmon_power and 1000 for
hwmon_temp, hwmon_in, and hwmon_curr, while the scale parameter in
macsmc_hwmon_read_key() has type int:
drivers/hwmon/macsmc-hwmon.c:macsmc_hwmon_read() {
...
case hwmon_power:
/* SMC returns power in Watts with acceptable precision to scale to uW */
ret = macsmc_hwmon_read_key(hwmon->smc,
&hwmon->power.sensors[channel],
1000000, val);
break;
}
First, in the mult_frac() calls for the unsigned (fp2e through fp88) and
signed (sp1e through spd2) fixed-point types, the first argument has a 16-bit
type (u16 from be16_to_cpu() or s16 from (s16)be16_to_cpu()):
include/linux/math.h:
#define mult_frac(x, n, d) \
({ \
typeof(x) x_ = (x); \
typeof(n) n_ = (n); \
typeof(d) d_ = (d); \
\
typeof(x_) q = x_ / d_; \
typeof(x_) r = x_ % d_; \
q * n_ + r * n_ / d_; \
})
Because typeof(x_) is a 16-bit integer, q and r are promoted to 32-bit signed
int, and n_ (scale) is also a 32-bit signed int. Both q * n_ and r * n_ are
evaluated as 32-bit signed int multiplications before dividing by d_
(unsigned long from BIT(n)). When scale is 1000000, any remainder r >= 2148
(possible for fp2e, fp3d, fp4c, sp1e, sp2d, and sp3c) or quotient |q| >= 2148
(possible for spc3 and spd2) overflows 32-bit signed int into a negative
value, which then converts to a huge unsigned long when divided by BIT(n).
Second, for the signed sp* types (sp1e through spd2), passing a signed x_ with
an unsigned long divisor BIT(n) causes x_ / d_ and x_ % d_ inside mult_frac()
to be evaluated as unsigned division and modulo whenever x_ is negative,
corrupting negative readings.
Third, for ui16, si16, ui32, si32, and spf0, the multiplications by scale
(such as ui16 * scale, si32 * scale, and (s16)be16_to_cpu(spf0) * scale) are
all evaluated in 32-bit arithmetic before widening to long *val, which
overflows when scale is 1000000 (for values >= 2148 on signed/16-bit types or
>= 4295 on ui32) or 1000 (for 32-bit types).
Would it make sense to widen scale (or cast the operands and divisors to
signed long / s64) so that the multiplications and mult_frac() divisions are
performed in 64-bit signed arithmetic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=12
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 13/14] hwmon: add support for x86 macs in macsmc-hwmon
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (11 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 12/14] hwmon/macsmc: add more types Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:20 ` sashiko-bot
2026-10-29 8:52 ` [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys Atharva Tiwari
13 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Make macsmc-hwmon work on Intel Macs, as a replacement for
the hwmon part of applesmc.
Sensors now have readable names instead of raw keys like
TA0P. It also adds power, current and voltage sensors that
applesmc doesn't provide, and supports fan control on newer
machines aswell.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/hwmon/Kconfig | 5 +-
drivers/hwmon/macsmc-hwmon.c | 816 ++++++++++++++++++++++++++++++++++-
2 files changed, 800 insertions(+), 21 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 91ca7031c..4d0bb87ff 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1244,13 +1244,12 @@ config SENSORS_LTQ_CPUTEMP
sensor inside your CPU.
config SENSORS_MACSMC_HWMON
- tristate "Apple SMC (Apple Silicon)"
+ tristate "Apple SMC Hardware monitoring"
depends on MFD_MACSMC_CORE
help
This driver enables hwmon support for current, power, temperature,
and voltage sensors, as well as fan speed reporting and control
- on Apple Silicon devices. Say Y here if you have an Apple Silicon
- device.
+ on Apple devices. Say Y here if you have an Apple device.
This driver can also be built as a module. If so, the module will
be called macsmc-hwmon.
diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
index 2e74dcd66..29e43ff4b 100644
--- a/drivers/hwmon/macsmc-hwmon.c
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -2,7 +2,7 @@
/*
* Apple SMC hwmon driver for Apple Silicon platforms
*
- * The System Management Controller on Apple Silicon devices is responsible for
+ * The System Management Controller on Apple Silicon And Intel Macs is responsible for
* measuring data from sensors across the SoC and machine. These include power,
* temperature, voltage and current sensors. Some "sensors" actually expose
* derived values. An example of this is the key PHPC, which is an estimate
@@ -18,6 +18,11 @@
* Originally based on a concept by Jean-Francois Bortolotti <jeff@borto.fr>
*
* Copyright The Asahi Linux Contributors
+ *
+ * Copyright (C) 2026 Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
+ *
+ * Old macs fan control based on smcFanControl:
+ * Copyright (C) 2006 Hendrik Holtmann <holtmann@mac.com>
*/
#include <linux/bitfield.h>
@@ -47,7 +52,7 @@ MODULE_PARM_DESC(fan_control,
struct macsmc_hwmon_sensor {
struct apple_smc_key_info info;
smc_key macsmc_key;
- char label[MAX_LABEL_LENGTH];
+ char *label;
u32 attrs;
};
@@ -71,7 +76,8 @@ struct macsmc_hwmon_sensors {
struct macsmc_hwmon_fans {
struct hwmon_channel_info channel_info;
struct macsmc_hwmon_fan *fans;
- u32 count;
+ u8 count;
+ bool has_old_manual;
};
struct macsmc_hwmon {
@@ -553,8 +559,8 @@ static int macsmc_hwmon_write_f32(struct apple_smc *smc, smc_key key, long value
val <<= 23 - exp;
fval = FIELD_PREP(FLT_SIGN_MASK, neg) |
- FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) |
- FIELD_PREP(FLT_MANT_MASK, val & FLT_MANT_MASK);
+ FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) |
+ FIELD_PREP(FLT_MANT_MASK, val & FLT_MANT_MASK);
}
return apple_smc_write_u32(smc, key, fval);
@@ -599,6 +605,23 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan,
}
}
+static int macsmc_hwmon_write_old_fan_manual(struct macsmc_hwmon *hwmon, u8 val, int channel)
+{
+ int ret;
+ u16 buf;
+
+ ret = apple_smc_read_u16(hwmon->smc, __SMC_KEY('F', 'S', '!', ' '), &buf);
+ if (ret)
+ return ret;
+
+ if (val)
+ buf |= BIT(channel);
+ else
+ buf &= ~BIT(channel);
+
+ return apple_smc_write_u16(hwmon->smc, __SMC_KEY('F', 'S', '!', ' '), cpu_to_be16(buf));
+}
+
static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
long val)
{
@@ -606,7 +629,8 @@ static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
long min, max;
int ret;
- if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0)
+ if (!fan_control || (hwmon->fan.fans[channel].mode.macsmc_key == 0 &&
+ !hwmon->fan.has_old_manual))
return -EOPNOTSUPP;
/*
@@ -625,7 +649,10 @@ static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
if (val >= min && val <= max) {
if (!hwmon->fan.fans[channel].manual) {
/* Write 1 to mode key for manual control */
- ret = macsmc_hwmon_write_key(hwmon->smc,
+ if (hwmon->fan.has_old_manual)
+ ret = macsmc_hwmon_write_old_fan_manual(hwmon, 1, channel);
+ else
+ ret = macsmc_hwmon_write_key(hwmon->smc,
&hwmon->fan.fans[channel].mode, 1);
if (ret < 0)
return ret;
@@ -633,10 +660,13 @@ static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
hwmon->fan.fans[channel].manual = true;
}
return macsmc_hwmon_write_key(hwmon->smc,
- &hwmon->fan.fans[channel].set, val);
+ &hwmon->fan.fans[channel].set, val) > 0;
} else if (!val) {
if (hwmon->fan.fans[channel].manual) {
- ret = macsmc_hwmon_write_key(hwmon->smc,
+ if (hwmon->fan.has_old_manual)
+ ret = macsmc_hwmon_write_old_fan_manual(hwmon, 0, channel);
+ else
+ ret = macsmc_hwmon_write_key(hwmon->smc,
&hwmon->fan.fans[channel].mode, 0);
if (ret < 0)
return ret;
@@ -697,10 +727,11 @@ static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
}
static umode_t macsmc_hwmon_fan_is_visible(const struct macsmc_hwmon_fan *fan,
- u32 attr)
+ u32 attr, bool has_old_manual)
{
if (fan->attrs & BIT(attr)) {
- if (attr == hwmon_fan_target && fan_control && fan->mode.macsmc_key)
+ if (attr == hwmon_fan_target && fan_control &&
+ (fan->mode.macsmc_key || has_old_manual))
return 0644;
return 0444;
@@ -710,8 +741,8 @@ static umode_t macsmc_hwmon_fan_is_visible(const struct macsmc_hwmon_fan *fan,
}
static umode_t macsmc_hwmon_is_visible(const void *data,
- enum hwmon_sensor_types type, u32 attr,
- int channel)
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
{
const struct macsmc_hwmon *hwmon = data;
struct macsmc_hwmon_sensor *sensor;
@@ -730,7 +761,8 @@ static umode_t macsmc_hwmon_is_visible(const void *data,
sensor = &hwmon->temp.sensors[channel];
break;
case hwmon_fan:
- return macsmc_hwmon_fan_is_visible(&hwmon->fan.fans[channel], attr);
+ return macsmc_hwmon_fan_is_visible(&hwmon->fan.fans[channel],
+ attr, hwmon->fan.has_old_manual);
default:
return 0;
}
@@ -800,6 +832,578 @@ static int macsmc_hwmon_create_sensor(struct device *dev, struct apple_smc *smc,
return 0;
}
+struct macsmc_sensor_desc {
+ char *name;
+ smc_key key;
+};
+
+/*
+ * Key's name taken from:
+ * https://github.com/kaiT2en/KaiT2en-Fedora/blob/main/apps/t2-smc-control/src/main.rs
+ * https://github.com/acidanthera/VirtualSMC/tree/master/Docs/
+ */
+
+static const struct macsmc_sensor_desc temp_sensors[] = {
+ { "Ambient 2 Raw Temperature (TA1p)", SMC_KEY(TA1p) },
+ { "Ambient 3 Raw Temperature (TA2p)", SMC_KEY(TA2p) },
+ { "Ambient Cooked Temperature (TA0P)", SMC_KEY(TA0P) },
+ { "Ambient Cooked Temperature 1 (TA1P)", SMC_KEY(TA1P) },
+ { "Ambient Cooked Temperature 2 (TA2P)", SMC_KEY(TA2P) },
+ { "Ambient Raw Temperature (TA0p)", SMC_KEY(TA0p) },
+ { "Ambient Virtual Temperature (TA0V)", SMC_KEY(TA0V) },
+ { "Audio L Temperature (TaLC)", SMC_KEY(TaLC) },
+ { "Audio R Temperature (TaRC)", SMC_KEY(TaRC) },
+ { "Backlight Controller Proximity Cooked Temperature (Tb0P)", SMC_KEY(Tb0P) },
+ { "Backlight Controller Proximity Raw Temperature (Tb0p)", SMC_KEY(Tb0p) },
+ { "Battery Ambient Temperature (TB3T)", SMC_KEY(TB3T) },
+ { "Battery Temperature (TB0T)", SMC_KEY(TB0T) },
+ { "Battery Temperature, Same As TB0T (TBXT)", SMC_KEY(TBXT) },
+ { "Battery TS1 Temperature (TB1T)", SMC_KEY(TB1T) },
+ { "Battery TS2 Temperature (TB2T)", SMC_KEY(TB2T) },
+ { "Charger Proximity Temperature (TCHP)", SMC_KEY(TCHP) },
+ { "CPU 1 Heatsink Alt. Temperature (TCAH)", SMC_KEY(TCAH) },
+ { "CPU 1 Heatsink Temperature (TC0H)", SMC_KEY(TC0H) },
+ { "CPU 1 Package Temperature (TCAD)", SMC_KEY(TCAD) },
+ { "CPU 2 Diode Filtered Temperature (TC1F)", SMC_KEY(TC1F) },
+ { "CPU 2 Diode Virtual Temperature (TC1E)", SMC_KEY(TC1E) },
+ { "CPU 2 Heatsink Alt. Temperature (TCBH)", SMC_KEY(TCBH) },
+ { "CPU 2 Heatsink Temperature (TC1H)", SMC_KEY(TC1H) },
+ { "CPU 2 Package Alt. Temperature (TCBD)", SMC_KEY(TCBD) },
+ { "CPU 2 Package Temperature (TC1D)", SMC_KEY(TC1D) },
+ { "CPU 2 Proximity Raw Temperature (TC1p)", SMC_KEY(TC1p) },
+ { "CPU Core 0 Absolute Raw Temperature PECI (TC0c)", SMC_KEY(TC0c) },
+ { "CPU Core 1 Absolute Cooked Temperature PECI (TC1C)", SMC_KEY(TC1C) },
+ { "CPU Core 1 Absolute Raw Temperature PECI (TC1c)", SMC_KEY(TC1c) },
+ { "CPU Core 2 Absolute Cooked Temperature PECI (TC2C)", SMC_KEY(TC2C) },
+ { "CPU Core 2 Absolute Raw Temperature PECI (TC2c)", SMC_KEY(TC2c) },
+ { "CPU Core 3 Absolute Cooked Temperature PECI (TC3C)", SMC_KEY(TC3C) },
+ { "CPU Core 3 Absolute Raw Temperature PECI (TC3c)", SMC_KEY(TC3c) },
+ { "CPU Core 3 Temperature PECI (TC4C)", SMC_KEY(TC4C) },
+ { "CPU Core 4 Absolute Raw Temperature PECI (TC4c)", SMC_KEY(TC4c) },
+ { "CPU Core 5 Absolute Raw Temperature PECI (TC5c)", SMC_KEY(TC5c) },
+ { "CPU Core 6 Absolute Raw Temperature PECI (TC6c)", SMC_KEY(TC6c) },
+ { "CPU Core 7 Absolute Raw Temperature PECI (TC7c)", SMC_KEY(TC7c) },
+ { "CPU Core 8 Absolute Raw Temperature PECI (TC8c)", SMC_KEY(TC8c) },
+ { "CPU Core 9 Absolute Raw Temperature PECI (TC9c)", SMC_KEY(TC9c) },
+ { "CPU Core PECI Temperature (TC0C)", SMC_KEY(TC0C) },
+ { "CPU Die Raw Temperature (TC0d)", SMC_KEY(TC0d) },
+ { "CPU Die Temperature (TC0D)", SMC_KEY(TC0D) },
+ { "CPU eDRAM Hot Indicator PECI (TCMc)", SMC_KEY(TCMc) },
+ { "CPU Gfx Core Absolute Cooked Temperature PECI (TCGC)", SMC_KEY(TCGC) },
+ { "CPU Gfx Core Absolute Raw Temperature PECI (TCGc)", SMC_KEY(TCGc) },
+ { "CPU Heatpipe Temperature (Th0H)", SMC_KEY(Th0H) },
+ { "CPU Max Package Core Absolute Cooked Temperature PECI (TCXC)", SMC_KEY(TCXC) },
+ { "CPU Max Package Core Absolute Raw Temperature PECI (TCXc)", SMC_KEY(TCXc) },
+ { "CPU Max Package Core Relative Cooked Temperature PECI (TCXR)", SMC_KEY(TCXR) },
+ { "CPU Max Package Core Relative Raw Temperature PECI (TCXr)", SMC_KEY(TCXr) },
+ { "CPU PECI Die Filtered And Adjusted Temperature (TC0F)", SMC_KEY(TC0F) },
+ { "CPU PECI Die Filtered Temperature (TC0E)", SMC_KEY(TC0E) },
+ { "CPU PECI Die Temperature Adjustment (TC0G)", SMC_KEY(TC0G) },
+ { "CPU PECI Die Temperature Filter Coeff (TCFC)", SMC_KEY(TCFC) },
+ { "CPU PECI Die Temperature Max Error (TC0J)", SMC_KEY(TC0J) },
+ { "CPU PECI Die Temperature Trend (TC0T)", SMC_KEY(TC0T) },
+ { "CPU PECI Die Temperature Trend (TCTD)", SMC_KEY(TCTD) },
+ { "CPU Proximity Raw Temperature (TC0p)", SMC_KEY(TC0p) },
+ { "CPU Proximity Temperature (TC0P)", SMC_KEY(TC0P) },
+ { "CPU Relative To ProcHot 1 (TCAC)", SMC_KEY(TCAC) },
+ { "CPU Relative To ProcHot 2 (TCBC)", SMC_KEY(TCBC) },
+ { "CPU System Agent Core Absolute Cooked Temperature PECI (TCSC)", SMC_KEY(TCSC) },
+ { "CPU System Agent Core Absolute Raw Temperature PECI (TCSc)", SMC_KEY(TCSc) },
+ { "CPU System Agent Core Temperature PECI (TCSA)", SMC_KEY(TCSA) },
+ { "CPU VR Proximity Cooked Temperature (TC1P)", SMC_KEY(TC1P) },
+ { "DCIn Air Flow Adjusted Temperature (TaSP)", SMC_KEY(TaSP) },
+ { "DCIn Air Flow Temperature (Ta0P)", SMC_KEY(Ta0P) },
+ { "DIMM 0 PECI Cooked Temperature (TM0D)", SMC_KEY(TM0D) },
+ { "DIMM 0 Proximity Raw Temperature (TM0p)", SMC_KEY(TM0p) },
+ { "DIMM 1 PECI Cooked Temperature (TM1D)", SMC_KEY(TM1D) },
+ { "DIMM 1 Proximity Cooked Temperature (TM1P)", SMC_KEY(TM1P) },
+ { "DIMM 1 Proximity Raw Temperature (TM1p)", SMC_KEY(TM1p) },
+ { "DIMM 2 PECI Cooked Temperature (TM2D)", SMC_KEY(TM2D) },
+ { "DIMM 2 Proximity Cooked Temperature (TM2P)", SMC_KEY(TM2P) },
+ { "DIMM 2 Proximity Raw Temperature (TM2p)", SMC_KEY(TM2p) },
+ { "DIMM 3 PECI Cooked Temperature (TM3D)", SMC_KEY(TM3D) },
+ { "DIMM 3 Proximity Cooked Temperature (TM3P)", SMC_KEY(TM3P) },
+ { "DIMM 3 Proximity Raw Temperature (TM3p)", SMC_KEY(TM3p) },
+ { "DIMM Bandwidth Estimate (TMBS)", SMC_KEY(TMBS) },
+ { "DIMM Max Cooked Temperature (TMXD)", SMC_KEY(TMXD) },
+ { "DIMM Proximity Max Cooked Temperature (TMXP)", SMC_KEY(TMXP) },
+ { "DIMM Virtual Temperature (TM0V)", SMC_KEY(TM0V) },
+ { "DIMM Virtual Temperature For 2x DIMM Slot 1+2 Or 2+3 Config (TM2b)", SMC_KEY(TM2b) },
+ { "DIMM Virtual Temperature For 2x DIMM Slot 1+2+3+4 Config (TM1a)", SMC_KEY(TM1a) },
+ { "DIMM Virtual Temperature For 2x DIMM Slot 1+3 Config (TM2a)", SMC_KEY(TM2a) },
+ { "DIMM Virtual Temperature For 2x DIMM Slot 1+4 Or 2+4 Config (TM2c)", SMC_KEY(TM2c) },
+ { "DIMM Virtual Temperature For 2x DIMM Slot 3+4 Config (TM2d)", SMC_KEY(TM2d) },
+ { "DIMM Virtual Temperature For 3x DIMM Slot 1+2+3 Config (TM3d)", SMC_KEY(TM3d) },
+ { "DIMM Virtual Temperature For 3x DIMM Slot 1+2+4 Config (TM3c)", SMC_KEY(TM3c) },
+ { "DIMM Virtual Temperature For 3x DIMM Slot 1+3+4 Config (TM3b)", SMC_KEY(TM3b) },
+ { "DIMM Virtual Temperature For 3x DIMM Slot 2+3+4 Config (TM3a)", SMC_KEY(TM3a) },
+ { "DIMM Virtual Temperature For 4x DIMM Slot 1+2+3+4 Config (TM4a)", SMC_KEY(TM4a) },
+ { "Drive 0 Backup Virtual DRAM Temperature (TH0V)", SMC_KEY(TH0V) },
+ { "Drive 0 OOBv1 Mapped Temperature (TH0O)", SMC_KEY(TH0O) },
+ { "Drive 0 OOBv1 Raw Bit-Mapped Temperature (TH0o)", SMC_KEY(TH0o) },
+ { "Drive 0 OOBv3 Absolute Cooked Temperature A (TH0A)", SMC_KEY(TH0A) },
+ { "Drive 0 OOBv3 Absolute Cooked Temperature B (TH0B)", SMC_KEY(TH0B) },
+ { "Drive 0 OOBv3 Absolute Cooked Temperature C (TH0C)", SMC_KEY(TH0C) },
+ { "Drive 0 OOBv3 Absolute Raw Temperature A (TH0a)", SMC_KEY(TH0a) },
+ { "Drive 0 OOBv3 Absolute Raw Temperature B (TH0b)", SMC_KEY(TH0b) },
+ { "Drive 0 OOBv3 Absolute Raw Temperature C (TH0c)", SMC_KEY(TH0c) },
+ { "Drive 0 OOBv3 Raw Temperature Max (TH0x)", SMC_KEY(TH0x) },
+ { "Drive 0 OOBv3 Relative Cooked Temperature Max (TH0R)", SMC_KEY(TH0R) },
+ { "Drive 0 OOBv3 Relative Filtered Temperature Max (TH0F)", SMC_KEY(TH0F) },
+ { "Drive 0 Temperature Sensor Existence Used For TM0V (TH0e)", SMC_KEY(TH0e) },
+ { "Drive 1 OOBv1 Filtered Temperature (TH1P)", SMC_KEY(TH1P) },
+ { "Drive 1 OOBv1 Mapped Temperature (TH1O)", SMC_KEY(TH1O) },
+ { "Drive 1 OOBv1 Raw Bit-Mapped Temperature (TH1o)", SMC_KEY(TH1o) },
+ { "Drive 1 OOBv3 Absolute Cooked Temperature A (TH1A)", SMC_KEY(TH1A) },
+ { "Drive 1 OOBv3 Absolute Cooked Temperature B (TH1B)", SMC_KEY(TH1B) },
+ { "Drive 1 OOBv3 Absolute Cooked Temperature C (TH1C)", SMC_KEY(TH1C) },
+ { "Drive 1 OOBv3 Absolute Raw Temperature A (TH1a)", SMC_KEY(TH1a) },
+ { "Drive 1 OOBv3 Absolute Raw Temperature B (TH1b)", SMC_KEY(TH1b) },
+ { "Drive 1 OOBv3 Absolute Raw Temperature C (TH1c)", SMC_KEY(TH1c) },
+ { "Drive 1 OOBv3 Relative Cooked Temperature Max (TH1R)", SMC_KEY(TH1R) },
+ { "Drive 1 OOBv3 Relative Filtered Temperature Max (TH1F)", SMC_KEY(TH1F) },
+ { "EMC1428 Internal Diode Raw Temperature (Tm0p)", SMC_KEY(Tm0p) },
+ { "Expansion Slots Temperature (TS0C)", SMC_KEY(TS0C) },
+ { "Fan 0 Synthetic Temperature (TF0S)", SMC_KEY(TF0S) },
+ { "Fan Target Temperature (TTF0)", SMC_KEY(TTF0) },
+ { "Filtered Power Supply 1 Proximity Temperature (TpFP)", SMC_KEY(TpFP) },
+ { "Filtered Vent Proximity Temperature (TVFP)", SMC_KEY(TVFP) },
+ { "Fin Stack Temperature (THFH)", SMC_KEY(THFH) },
+ { "GFX Memory Temperature (TG0M)", SMC_KEY(TG0M) },
+ { "GPU 0 Die Cooked Temperature (TG0D)", SMC_KEY(TG0D) },
+ { "GPU 0 Die Filtered And Adjusted Temperature (TG0F)", SMC_KEY(TG0F) },
+ { "GPU 0 Die Filtered Temperature (TG0E)", SMC_KEY(TG0E) },
+ { "GPU 0 Die Raw Temperature (TG0d)", SMC_KEY(TG0d) },
+ { "GPU 0 Die Temperature Max Error (TG0J)", SMC_KEY(TG0J) },
+ { "GPU 0 Die Temperature Trend (TG0T)", SMC_KEY(TG0T) },
+ { "GPU 0 Proximity Cooked Temperature (TG0P)", SMC_KEY(TG0P) },
+ { "GPU 0 Proximity Raw Temperature (TG0p)", SMC_KEY(TG0p) },
+ { "GPU AMD Die Digital Temperature (TGDD)", SMC_KEY(TGDD) },
+ { "GPU B VR Proximity Cooked Temperature (TG5P)", SMC_KEY(TG5P) },
+ { "GPU B VRAM Proximity Cooked Temperature (TG4P)", SMC_KEY(TG4P) },
+ { "GPU Die Analog Temperature (TGDF)", SMC_KEY(TGDF) },
+ { "GPU Digital Die Filtered Temperature (TG1F)", SMC_KEY(TG1F) },
+ { "GPU Digital Die Raw Temperature Without Offset (TG1d)", SMC_KEY(TG1d) },
+ { "GPU Digital Die Temperature (TG1D)", SMC_KEY(TG1D) },
+ { "GPU FB VR Proximity 3 Cooked Temperature (TG3P)", SMC_KEY(TG3P) },
+ { "GPU FB VR Proximity 3 Raw Temperature (TG3p)", SMC_KEY(TG3p) },
+ { "GPU GDDR5 Proximity 1 Cooked Temperature (TG1P)", SMC_KEY(TG1P) },
+ { "GPU GDDR5 Proximity 1 Raw Temperature (TG1p)", SMC_KEY(TG1p) },
+ { "GPU VR Proximity 2 Cooked Temperature (TG2P)", SMC_KEY(TG2P) },
+ { "GPU VR Proximity 2 Raw Temperature (TG2p)", SMC_KEY(TG2p) },
+ { "GPU VR Temperature (TGVP)", SMC_KEY(TGVP) },
+ { "HDD Bay 3 Temperature (TH2P)", SMC_KEY(TH2P) },
+ { "HDD Bay 4 Temperature (TH3P)", SMC_KEY(TH3P) },
+ { "HDD Proximity Temperature (TH0P)", SMC_KEY(TH0P) },
+ { "Heat Pipe Temperature (TG0H)", SMC_KEY(TG0H) },
+ { "Heat Pipe Temperature 2 (TG1H)", SMC_KEY(TG1H) },
+ { "Heat Spreader Proximity Temperature (Th0P)", SMC_KEY(Th0P) },
+ { "High Speed I/O Proximity 0 Temperature (TI0P)", SMC_KEY(TI0P) },
+ { "High Speed I/O Proximity 1 Temperature (TI1P)", SMC_KEY(TI1P) },
+ { "LCD Filtered Center Front-Of-Screen Temperature Feeding TL1V (TL1v)", SMC_KEY(TL1v) },
+ { "LCD Filtered Center Front-Of-Screen Temperature For Color Compensation (TL1V)", SMC_KEY(TL1V) },
+ { "LCD Filtered Front-Of-Screen Hotspot Virtual Temperature (TL0V)", SMC_KEY(TL0V) },
+ { "LCD IR Target Hotspot Calculated Temperature (TL2p)", SMC_KEY(TL2p) },
+ { "LCD IR Temperature Sensor Die Raw Temperature (TL3p)", SMC_KEY(TL3p) },
+ { "LCD TCON Proximity Cooked Temperature (TL1P)", SMC_KEY(TL1P) },
+ { "LCD TCON Proximity Raw Temperature (TL1p)", SMC_KEY(TL1p) },
+ { "LCD Unfiltered Front-Of-Screen Cooked Temperature (TL0P)", SMC_KEY(TL0P) },
+ { "LCD Unfiltered Front-Of-Screen Raw Temperature (TL0p)", SMC_KEY(TL0p) },
+ { "Left Fin Stack Temperature (Th2H)", SMC_KEY(Th2H) },
+ { "Max PECI Reported Temperature (TCMX)", SMC_KEY(TCMX) },
+ { "MCH Diode Temperature (TN0C)", SMC_KEY(TN0C) },
+ { "MCH Heatsink Temperature (TN0H)", SMC_KEY(TN0H) },
+ { "MCP Die 2 Temperature (TN1D)", SMC_KEY(TN1D) },
+ { "MCP Die Temperature (TN0D)", SMC_KEY(TN0D) },
+ { "MCP Proximity Temperature (TN0P)", SMC_KEY(TN0P) },
+ { "Mem Bank B1 Temperature (TM8P)", SMC_KEY(TM8P) },
+ { "Mem Bank B2 Temperature (TM9P)", SMC_KEY(TM9P) },
+ { "Mem Module A2 Temperature (TM1S)", SMC_KEY(TM1S) },
+ { "Mem Module A3 Temperature (TM2S)", SMC_KEY(TM2S) },
+ { "Mem Module A4 Temperature (TM3S)", SMC_KEY(TM3S) },
+ { "Mem Module B1 Temperature (TM8S)", SMC_KEY(TM8S) },
+ { "Mem Module B2 Temperature (TM9S)", SMC_KEY(TM9S) },
+ { "Memory Controller/Integrated Graphics PECI Temperature (TMCD)", SMC_KEY(TMCD) },
+ { "Memory Proximity Temperature (TM0P)", SMC_KEY(TM0P) },
+ { "MLB Bottom Side Temperature (Tm1P)", SMC_KEY(Tm1P) },
+ { "MLB Proximity 0 Cooked Temperature (Tm0P)", SMC_KEY(Tm0P) },
+ { "MLB Proximity 1 Raw Temperature (Tm1p)", SMC_KEY(Tm1p) },
+ { "MLB Proximity 2 Cooked Temperature (Tm2P)", SMC_KEY(Tm2P) },
+ { "MLB Proximity 2 Raw Temperature (Tm2p)", SMC_KEY(Tm2p) },
+ { "MLB Proximity Temperature (TMLB)", SMC_KEY(TMLB) },
+ { "MLB Temperature (Tp0P)", SMC_KEY(Tp0P) },
+ { "NAND Temperature (Th0N)", SMC_KEY(Th0N) },
+ { "Northbridge 2 Temperature (TN1P)", SMC_KEY(TN1P) },
+ { "Optical Drive Temperature (TO0P)", SMC_KEY(TO0P) },
+ { "Palm Rest Temperature (Ts0P)", SMC_KEY(Ts0P) },
+ { "Palm Rest Temperature 2 (Ts1P)", SMC_KEY(Ts1P) },
+ { "PCH Die Cooked Temperature (TPCD)", SMC_KEY(TPCD) },
+ { "PCH Digital Die Raw Temperature (TPCd)", SMC_KEY(TPCd) },
+ { "PCH Diode Temperature (TP0D)", SMC_KEY(TP0D) },
+ { "PCH Proximity Temperature (TP0P)", SMC_KEY(TP0P) },
+ { "PCI Slot 1 Pos 1 Temperature (TA0S)", SMC_KEY(TA0S) },
+ { "PCI Slot 1 Pos 2 Temperature (TA1S)", SMC_KEY(TA1S) },
+ { "PCI Slot 2 Pos 1 Temperature (TA2S)", SMC_KEY(TA2S) },
+ { "PCI Slot 2 Pos 2 Temperature (TA3S)", SMC_KEY(TA3S) },
+ { "Power Supply 1 Alt. Temperature (Tp0C)", SMC_KEY(Tp0C) },
+ { "Power Supply 2 Alt. Temperature (Tp1C)", SMC_KEY(Tp1C) },
+ { "Power Supply 3 Temperature (Tp2P)", SMC_KEY(Tp2P) },
+ { "Power Supply 4 Temperature (Tp3P)", SMC_KEY(Tp3P) },
+ { "Power Supply 5 Temperature (Tp4P)", SMC_KEY(Tp4P) },
+ { "Power Supply 6 Temperature (Tp5P)", SMC_KEY(Tp5P) },
+ { "Power Supply T2 Secondary Heatsink Cooked Temperature (Tp2H)", SMC_KEY(Tp2H) },
+ { "Power Supply T2 Secondary Heatsink Filtered Temperature (Tp2F)", SMC_KEY(Tp2F) },
+ { "Power Supply T2 Secondary Heatsink Raw Temperature (Tp2h)", SMC_KEY(Tp2h) },
+ { "PSU Secondary Heatsink Diode Cooked Temperature (Tp0T)", SMC_KEY(Tp0T) },
+ { "Right Fin Stack Temperature (Th1H)", SMC_KEY(Th1H) },
+ { "RIO Temperature (Th0R)", SMC_KEY(Th0R) },
+ { "S2 Camera Proximity Temperature (TS2P)", SMC_KEY(TS2P) },
+ { "Secondary Power Supply Proximity Temperature (Tp1P)", SMC_KEY(Tp1P) },
+ { "Skin 0 Raw Temperature (TS0p)", SMC_KEY(TS0p) },
+ { "Skin 0 Virtual Temperature (TS0V)", SMC_KEY(TS0V) },
+ { "Speaker Proximity 2 Temperature (TS1P)", SMC_KEY(TS1P) },
+ { "Speaker Proximity Temperature (TS0P)", SMC_KEY(TS0P) },
+ { "Speaker Remote 2 Temperature (TS1D)", SMC_KEY(TS1D) },
+ { "Speaker Remote Temperature (TS0D)", SMC_KEY(TS0D) },
+ { "SSD 0 OOBv3 Cooked Temperature Max (TH0X)", SMC_KEY(TH0X) },
+ { "SSD 1 OOBv3 Cooked Temperature Max (TH1X)", SMC_KEY(TH1X) },
+ { "Synthetic Bottom Skin Temperature (Ts0S)", SMC_KEY(Ts0S) },
+ { "Synthetic DIMM Estimate (TM0S)", SMC_KEY(TM0S) },
+ { "Synthetic Top Skin Temperature (Ts1S)", SMC_KEY(Ts1S) },
+ { "TBT Proximity Raw Temperature (TI0p)", SMC_KEY(TI0p) },
+ { "TBT Thermal Diode Raw Temperature (Te0t)", SMC_KEY(Te0t) },
+ { "TBT Thermal Diode Temperature (Te0T)", SMC_KEY(Te0T) },
+ { "Thermal Trap Hysteresis (TsTH)", SMC_KEY(TsTH) },
+ { "Thermal Trap Threshold (TsTP)", SMC_KEY(TsTP) },
+ { "Thermal Warning Hysteresis (TsHS)", SMC_KEY(TsHS) },
+ { "Thermal Warning Threshold (TsWS)", SMC_KEY(TsWS) },
+ { "Thunderbolt Die Temperature (THSP)", SMC_KEY(THSP) },
+ { "Thunderbolt L Temperature (TTLD)", SMC_KEY(TTLD) },
+ { "Thunderbolt Proximity Cooked Temperature (TI0T)", SMC_KEY(TI0T) },
+ { "Thunderbolt R Temperature (TTRD)", SMC_KEY(TTRD) },
+ { "Touchpad Temperature (Ts2S)", SMC_KEY(Ts2S) },
+ { "Vent Proximity Temperature (TV0P)", SMC_KEY(TV0P) },
+ { "WiFi Proximity Raw Temperature (TW0p)", SMC_KEY(TW0p) },
+ { "Wireless Proximity Temperature (TW0P)", SMC_KEY(TW0P) },
+};
+
+static const struct macsmc_sensor_desc power_sensors[] = {
+ { "12 V Rail (Pp0R)", SMC_KEY(Pp0R) },
+ { "2nd GPU Plimit (MSTi)", SMC_KEY(MSTi) },
+ { "3.3V In S0 Power (PR3C)", SMC_KEY(PR3C) },
+ { "3.3V S0 Power (PR0C)", SMC_KEY(PR0C) },
+ { "3.3V S3 Power (PR1C)", SMC_KEY(PR1C) },
+ { "3.3V S5 Power (PR5C)", SMC_KEY(PR5C) },
+ { "5V Lowside Power (P50R)", SMC_KEY(P50R) },
+ { "A8 Load-Side Rail (PA8C)", SMC_KEY(PA8C) },
+ { "Audio ScratchPad Register (MST1)", SMC_KEY(MST1) },
+ { "Audio ScratchPad2 Register (MST2)", SMC_KEY(MST2) },
+ { "Average Main Chipset Power (Pc0R)", SMC_KEY(Pc0R) },
+ { "Battery Rail (PB0R)", SMC_KEY(PB0R) },
+ { "Battery Rail (PBLC)", SMC_KEY(PBLC) },
+ { "BL Rail 12 V (PBLR)", SMC_KEY(PBLR) },
+ { "Chipset IMon Power (PCS1)", SMC_KEY(PCS1) },
+ { "Chipset Lowside Power (PCS0)", SMC_KEY(PCS0) },
+ { "CPU 1V5 Power (PD1R)", SMC_KEY(PD1R) },
+ { "CPU Core (PCAC)", SMC_KEY(PCAC) },
+ { "CPU Core 1 (PC0C)", SMC_KEY(PC0C) },
+ { "CPU Core 2 (PC1C)", SMC_KEY(PC1C) },
+ { "CPU Core 3 (PC2C)", SMC_KEY(PC2C) },
+ { "CPU Core 4 (PC3C)", SMC_KEY(PC3C) },
+ { "CPU Core 5 (PC4C)", SMC_KEY(PC4C) },
+ { "CPU Core 6 (PC5C)", SMC_KEY(PC5C) },
+ { "CPU Core 7 (PC6C)", SMC_KEY(PC6C) },
+ { "CPU Core 8 (PC7C)", SMC_KEY(PC7C) },
+ { "CPU Core IMON (PCAM)", SMC_KEY(PCAM) },
+ { "CPU Cores (PCPC)", SMC_KEY(PCPC) },
+ { "CPU DRAM (PCPD)", SMC_KEY(PCPD) },
+ { "CPU GFX (PCPG)", SMC_KEY(PCPG) },
+ { "CPU High-Side Average (PC0R)", SMC_KEY(PC0R) },
+ { "CPU I/O High-Side (PC0I)", SMC_KEY(PC0I) },
+ { "CPU I/O High-Side 2 (PC0M)", SMC_KEY(PC0M) },
+ { "CPU I/O Load-Side Rail (PCIC)", SMC_KEY(PCIC) },
+ { "CPU Integrated GPU (PC0G)", SMC_KEY(PC0G) },
+ { "CPU Package Total PECI (PCPT)", SMC_KEY(PCPT) },
+ { "CPU Package Total Power SMC (PCPR)", SMC_KEY(PCPR) },
+ { "CPU Package Total Power SMC Load Sensors (PCLT)", SMC_KEY(PCLT) },
+ { "CPU Plimit (MSTc)", SMC_KEY(MSTc) },
+ { "CPU Rail (PC1R)", SMC_KEY(PC1R) },
+ { "CPU Raw Package (PC0c)", SMC_KEY(PC0c) },
+ { "CPU S0 Rail (PC5R)", SMC_KEY(PC5R) },
+ { "CPU System Agent (PC0S)", SMC_KEY(PC0S) },
+ { "CPU Total (PCPL)", SMC_KEY(PCPL) },
+ { "CPU Total (PCTR)", SMC_KEY(PCTR) },
+ { "CPU VccEDRAM (PCEC)", SMC_KEY(PCEC) },
+ { "CPU VCCSA (PCSC)", SMC_KEY(PCSC) },
+ { "CT Load-Side Rail (PCTC)", SMC_KEY(PCTC) },
+ { "DC-In MLB S5 Rail (PD5R)", SMC_KEY(PD5R) },
+ { "DC-In MLB Total (PDMR)", SMC_KEY(PDMR) },
+ { "DDR (PM1C)", SMC_KEY(PM1C) },
+ { "DDR Power 1.8V CPU (PM2C)", SMC_KEY(PM2C) },
+ { "DDR S3 Power (PM3C)", SMC_KEY(PM3C) },
+ { "DIMM Load-Side Power Per Fan RPM Squared (PM0f)", SMC_KEY(PM0f) },
+ { "Discrete BMON On Battery Power (PB1R)", SMC_KEY(PB1R) },
+ { "Drive 0 (PH0R)", SMC_KEY(PH0R) },
+ { "Drive 0 Filtered Total Power (PH0F)", SMC_KEY(PH0F) },
+ { "Drive 1 (PH1R)", SMC_KEY(PH1R) },
+ { "Drive 1 Filtered Power (PH1F)", SMC_KEY(PH1F) },
+ { "Drive 2 Power (PH2R)", SMC_KEY(PH2R) },
+ { "External GPU 1.05 V (PG2C)", SMC_KEY(PG2C) },
+ { "External GPU 1.35 V (PG3C)", SMC_KEY(PG3C) },
+ { "External GPU 1.8 V (PG1C)", SMC_KEY(PG1C) },
+ { "External GPU Rail 4 (PG4C)", SMC_KEY(PG4C) },
+ { "F3 Load-Side Rail (PF3C)", SMC_KEY(PF3C) },
+ { "Forced Idle Limit (MSTf)", SMC_KEY(MSTf) },
+ { "GFX B Total Power (PG1R)", SMC_KEY(PG1R) },
+ { "GPU (PG0C)", SMC_KEY(PG0C) },
+ { "GPU 0 Rail (PG0R)", SMC_KEY(PG0R) },
+ { "GPU A VDDCI Low Side Power (PG0S)", SMC_KEY(PG0S) },
+ { "GPU Aux Input-Side Power (PG1A)", SMC_KEY(PG1A) },
+ { "GPU Aux Load-Side Power (PG0A)", SMC_KEY(PG0A) },
+ { "GPU B VDDCI Low Side Power (PG1S)", SMC_KEY(PG1S) },
+ { "GPU Frame Buffer Input-Side Power (PG0F)", SMC_KEY(PG0F) },
+ { "GPU Frame Buffer Input-Side Power (PG1F)", SMC_KEY(PG1F) },
+ { "GPU Mem Ctl Load-Side Power (PG0M)", SMC_KEY(PG0M) },
+ { "GPU Package Total Power SMC (PGPR)", SMC_KEY(PGPR) },
+ { "GPU Plimit (MSTg)", SMC_KEY(MSTg) },
+ { "GPU Total (PGTR)", SMC_KEY(PGTR) },
+ { "GPU Uncore High-Side Power (PG0U)", SMC_KEY(PG0U) },
+ { "GPU VDDCI Load-Side Power (PG0I)", SMC_KEY(PG0I) },
+ { "GPU VRAM Load-Side Power (PG0V)", SMC_KEY(PG0V) },
+ { "Heatpipe (PHPC)", SMC_KEY(PHPC) },
+ { "HP Power Target (PTHC)", SMC_KEY(PTHC) },
+ { "HP Power Throttle (MSLT)", SMC_KEY(MSLT) },
+ { "I/O Board Total Power (PI0R)", SMC_KEY(PI0R) },
+ { "Intel GPU IMON (PCGC)", SMC_KEY(PCGC) },
+ { "Intel GPU IMON 2 (PCGM)", SMC_KEY(PCGM) },
+ { "LCD Panel (PLDC)", SMC_KEY(PLDC) },
+ { "Left Thunderbolt/I/O Rail (POLR)", SMC_KEY(POLR) },
+ { "Left USB-C Load-Side Rail (PULC)", SMC_KEY(PULC) },
+ { "Main 3.3 V Rail (PH02)", SMC_KEY(PH02) },
+ { "Main 5 V Rail (PH05)", SMC_KEY(PH05) },
+ { "MC Load-Side Rail (PMCC)", SMC_KEY(PMCC) },
+ { "MCH (PN0C)", SMC_KEY(PN0C) },
+ { "MCP Memory Power (PN1C)", SMC_KEY(PN1C) },
+ { "MCP VCore Power (PN0R)", SMC_KEY(PN0R) },
+ { "MEM Plimit (MSTm)", SMC_KEY(MSTm) },
+ { "Memory Average (PM0C)", SMC_KEY(PM0C) },
+ { "Memory Power (PZDM)", SMC_KEY(PZDM) },
+ { "Memory Rail (PM0R)", SMC_KEY(PM0R) },
+ { "Memory Total Power (PMTR)", SMC_KEY(PMTR) },
+ { "Misc. Rail (PO0R)", SMC_KEY(PO0R) },
+ { "ODD Power (PZOD)", SMC_KEY(PZOD) },
+ { "Other 3.3V High Side Power (PO3R)", SMC_KEY(PO3R) },
+ { "Other 5 V High-Side (PO5R)", SMC_KEY(PO5R) },
+ { "PBus (PP0R)", SMC_KEY(PP0R) },
+ { "PBus Battery Discharge (PPBR)", SMC_KEY(PPBR) },
+ { "PCH Core Power (PSBC)", SMC_KEY(PSBC) },
+ { "PCH Rail (PN1R)", SMC_KEY(PN1R) },
+ { "Power Supply Filter Coefficient (PD0K)", SMC_KEY(PD0K) },
+ { "Power Supply Filtered Power (PD0E)", SMC_KEY(PD0E) },
+ { "Power Supply Max Error Filtered Output (PD0J)", SMC_KEY(PD0J) },
+ { "Power Supply Power Filtered Then Adjusted (PD0F)", SMC_KEY(PD0F) },
+ { "Power Supply Power Trend (PD0D)", SMC_KEY(PD0D) },
+ { "Power Zone AP (PZAP)", SMC_KEY(PZAP) },
+ { "Power Zone Backlight (PZBL)", SMC_KEY(PZBL) },
+ { "Power Zone Storage (PZHD)", SMC_KEY(PZHD) },
+ { "Right Thunderbolt/I/O Rail (PORR)", SMC_KEY(PORR) },
+ { "Right USB-C Load-Side Rail (PURC)", SMC_KEY(PURC) },
+ { "S0 5.0V Power (PR0R)", SMC_KEY(PR0R) },
+ { "S1 Load-Side Rail (PS1C)", SMC_KEY(PS1C) },
+ { "S2 Camera Power (PCMC)", SMC_KEY(PCMC) },
+ { "S2 Camera Power (PS2C)", SMC_KEY(PS2C) },
+ { "S3 3.30V Power (PR1R)", SMC_KEY(PR1R) },
+ { "S3 5.0V Power (PR2R)", SMC_KEY(PR2R) },
+ { "S5 3.30V Power (PR3R)", SMC_KEY(PR3R) },
+ { "SL Rail 3.3 V (PSLC)", SMC_KEY(PSLC) },
+ { "SSD 3.3 V Load-Side Rail 0 (PH0C)", SMC_KEY(PH0C) },
+ { "SSD 3.3 V Load-Side Rail 1 (PH1C)", SMC_KEY(PH1C) },
+ { "SSD Power (PHDC)", SMC_KEY(PHDC) },
+ { "SSD Power (PSDC)", SMC_KEY(PSDC) },
+ { "System Total, 1 s Delayed (PSTR)", SMC_KEY(PSTR) },
+ { "T101 Accuator Power (PTPC)", SMC_KEY(PTPC) },
+ { "T25 Track Pad Power (PTPR)", SMC_KEY(PTPR) },
+ { "Thunderbolt Power (PHSC)", SMC_KEY(PHSC) },
+ { "Thunderbolt Power (PHSP)", SMC_KEY(PHSP) },
+ { "WiFi (PAPC)", SMC_KEY(PAPC) },
+ { "Wifi Load-Side Power (PW0R)", SMC_KEY(PW0R) },
+ { "Heatpipe Average (PZ0G)", SMC_KEY(PZ0G) },
+ { "Heatpipe Average Target (PZ0E)", SMC_KEY(PZ0E) },
+ { "Heatpipe Filtered (PZ0F)", SMC_KEY(PZ0F) },
+ { "Heatpipe Throttle (PZ0T)", SMC_KEY(PZ0T) },
+ { "CPU Average (PZ1G)", SMC_KEY(PZ1G) },
+ { "CPU Filtered (PZ1F)", SMC_KEY(PZ1F) },
+ { "CPU Target (PZ1E)", SMC_KEY(PZ1E) },
+ { "CPU Throttle (PZ1T)", SMC_KEY(PZ1T) },
+ { "dGPU Average (PZ2G)", SMC_KEY(PZ2G) },
+ { "dGPU Filtered (PZ2F)", SMC_KEY(PZ2F) },
+ { "dGPU Target (PZ2E)", SMC_KEY(PZ2E) },
+ { "dGPU Throttle (PZ2T)", SMC_KEY(PZ2T) },
+ { "Batt aux Average (PZ3G)", SMC_KEY(PZ3G) },
+ { "Batt aux Filtered (PZ3F)", SMC_KEY(PZ3F) },
+ { "Batt aux Target (PZ3E)", SMC_KEY(PZ3E) },
+ { "Batt aux Throttle (PZ3T)", SMC_KEY(PZ3T) },
+ { "Battery Average (PZ4G)", SMC_KEY(PZ4G) },
+ { "Battery Filtered (PZ4F)", SMC_KEY(PZ4F) },
+ { "Battery Target (PZ4E)", SMC_KEY(PZ4E) },
+ { "Battery Throttle (PZ4T)", SMC_KEY(PZ4T) },
+ { "Zone 5 Abstract Throttle (PZ5T)", SMC_KEY(PZ5T) },
+ { "Zone 5 Average Power (PZ5G)", SMC_KEY(PZ5G) },
+ { "Zone 5 Filtered Power (PZ5F)", SMC_KEY(PZ5F) },
+ { "Zone 5 Target Power (PZ5E)", SMC_KEY(PZ5E) },
+};
+
+static const struct macsmc_sensor_desc current_sensors[] = {
+ { "3.3V S0 Current (IR0C)", SMC_KEY(IR0C) },
+ { "3.3V S0 Current (IR3C)", SMC_KEY(IR3C) },
+ { "5.0V S0 Current (IR5C)", SMC_KEY(IR5C) },
+ { "5V Lowside Current (I50R)", SMC_KEY(I50R) },
+ { "Airport Current (IZOP)", SMC_KEY(IZOP) },
+ { "Backlight Current (IBLC)", SMC_KEY(IBLC) },
+ { "Battery Current (IBAC)", SMC_KEY(IBAC) },
+ { "Battery Current Discrete (IP0R)", SMC_KEY(IP0R) },
+ { "Battery Discharge Current MLB (IB0R)", SMC_KEY(IB0R) },
+ { "Camera S2 Current (ICMC)", SMC_KEY(ICMC) },
+ { "CPU 1V5 Current (ID1R)", SMC_KEY(ID1R) },
+ { "CPU And MEM Current (IM0C)", SMC_KEY(IM0C) },
+ { "CPU AXG Low-Side Current (IC0G)", SMC_KEY(IC0G) },
+ { "CPU Core Current (IC0C)", SMC_KEY(IC0C) },
+ { "CPU Core Current (ICS0)", SMC_KEY(ICS0) },
+ { "CPU Current From CPU IMON (ICS1)", SMC_KEY(ICS1) },
+ { "CPU DDR Current (IC3C)", SMC_KEY(IC3C) },
+ { "CPU DDR Current (IM1C)", SMC_KEY(IM1C) },
+ { "CPU High Side Current (IC0R)", SMC_KEY(IC0R) },
+ { "CPU High Side Current From EMC1704 (IC1R)", SMC_KEY(IC1R) },
+ { "CPU I/O High-Side Current (IC0I)", SMC_KEY(IC0I) },
+ { "CPU Mem Low-Side Current (IC0M)", SMC_KEY(IC0M) },
+ { "CPU Riser 12V High Side Current (ICTR)", SMC_KEY(ICTR) },
+ { "CPU VCCIO Current (IC1C)", SMC_KEY(IC1C) },
+ { "CPU VCCSA Current (IC2C)", SMC_KEY(IC2C) },
+ { "CPU VSA Low Side Current (IC0S)", SMC_KEY(IC0S) },
+ { "CSreg Current (Ic0R)", SMC_KEY(Ic0R) },
+ { "DDR S3 Current (IM3C)", SMC_KEY(IM3C) },
+ { "DIMM Current (IZDM)", SMC_KEY(IZDM) },
+ { "DIMM Load-Side Current (IM0R)", SMC_KEY(IM0R) },
+ { "Discrete Battery Current (IPB1)", SMC_KEY(IPB1) },
+ { "Drive 0 12V Load-Side Current (IH02)", SMC_KEY(IH02) },
+ { "Drive 0 5V Load-Side Current (IH05)", SMC_KEY(IH05) },
+ { "Drive 1 3.3V Load-Side Current (IH1R)", SMC_KEY(IH1R) },
+ { "Ext GPU 1.05V Current (IG2C)", SMC_KEY(IG2C) },
+ { "Ext GPU 1.35V Current (IG3C)", SMC_KEY(IG3C) },
+ { "Ext GPU High Side Current (IG0R)", SMC_KEY(IG0R) },
+ { "GFX B Riser 12V High Side Current (IG1R)", SMC_KEY(IG1R) },
+ { "GFX Vcore Current (IN0R)", SMC_KEY(IN0R) },
+ { "GPU A VDDCI Low Side Current (IG0S)", SMC_KEY(IG0S) },
+ { "GPU Aux Input-Side Current (IG1A)", SMC_KEY(IG1A) },
+ { "GPU B VDDCI Low Side Current (IG1S)", SMC_KEY(IG1S) },
+ { "GPU Core Current (IG0C)", SMC_KEY(IG0C) },
+ { "GPU Core Input-Side Current (IG1C)", SMC_KEY(IG1C) },
+ { "GPU FB High-Side Current (IG0F)", SMC_KEY(IG0F) },
+ { "GPU FB Input-Side Current (IG1F)", SMC_KEY(IG1F) },
+ { "GPU Uncore High-Side Current (IG0U)", SMC_KEY(IG0U) },
+ { "GPU VDDCI Load-Side Current (IG0I)", SMC_KEY(IG0I) },
+ { "HDD Current (IZHD)", SMC_KEY(IZHD) },
+ { "I/O Board 12V High Side Current (II0R)", SMC_KEY(II0R) },
+ { "Internal GFX Core Current (IN0C)", SMC_KEY(IN0C) },
+ { "LCD Backlight Current (IZBL)", SMC_KEY(IZBL) },
+ { "LCD Panel Current (ILDC)", SMC_KEY(ILDC) },
+ { "LPDDR Current 1.8V (IM2C)", SMC_KEY(IM2C) },
+ { "MCP DDR Current (IN1C)", SMC_KEY(IN1C) },
+ { "Memory 1V5 High Side Current (IMTR)", SMC_KEY(IMTR) },
+ { "ODD Current (IODC)", SMC_KEY(IODC) },
+ { "ODD Current (IZOD)", SMC_KEY(IZOD) },
+ { "Other 3.3V High Side Current (IO3R)", SMC_KEY(IO3R) },
+ { "Other 5V High Side Current (IO5R)", SMC_KEY(IO5R) },
+ { "Other High Side Current (IO0R)", SMC_KEY(IO0R) },
+ { "Other High Side Current (IR1C)", SMC_KEY(IR1C) },
+ { "PBus On Battery Current (IPBR)", SMC_KEY(IPBR) },
+ { "PCH 1V05 Current (IN1R)", SMC_KEY(IN1R) },
+ { "PCH Core Current (ISBC)", SMC_KEY(ISBC) },
+ { "PCH/GPU/TBT 1.05V High-Side Current (IR1R)", SMC_KEY(IR1R) },
+ { "Power Supply 12V Current Dynamic Filter Coeff (ID2L)", SMC_KEY(ID2L) },
+ { "Power Supply 12V Current Filter Input (ID2I)", SMC_KEY(ID2I) },
+ { "Power Supply 12V Current Filtered (ID2E)", SMC_KEY(ID2E) },
+ { "Power Supply 12V Current Filtered And Adjusted (ID2F)", SMC_KEY(ID2F) },
+ { "Power Supply 12V Current Max Error (ID2J)", SMC_KEY(ID2J) },
+ { "Power Supply 12V Current Trend (ID2T)", SMC_KEY(ID2T) },
+ { "S2 Camera Current (IS2C)", SMC_KEY(IS2C) },
+ { "S3 5.0V Current (IR2C)", SMC_KEY(IR2C) },
+ { "SSD 3V3 Low Side Current (IH0R)", SMC_KEY(IH0R) },
+ { "SSD Current (IHDC)", SMC_KEY(IHDC) },
+ { "SSD Current (ISDC)", SMC_KEY(ISDC) },
+ { "Thunderbolt Current (IHSC)", SMC_KEY(IHSC) },
+ { "Thunderbolt Current (IHSP)", SMC_KEY(IHSP) },
+ { "Trackpad Actuator Current (ITPC)", SMC_KEY(ITPC) },
+ { "Wifi Load-Side Current (IW0R)", SMC_KEY(IW0R) },
+ { "WLAN Current (IZAP)", SMC_KEY(IZAP) },
+ { "WLAN Lowside Current (IAPC)", SMC_KEY(IAPC) },
+};
+
+static const struct macsmc_sensor_desc voltage_sensors[] = {
+ { "1.05V To CPU/PCH Lowside Voltage (VC1C)", SMC_KEY(VC1C) },
+ { "1.2V To CPU/MEM Lowside Voltage (VM0C)", SMC_KEY(VM0C) },
+ { "1.8V S3 Lowside Voltage (VM1C)", SMC_KEY(VM1C) },
+ { "3.3V Load-Side Voltage (VR3R)", SMC_KEY(VR3R) },
+ { "5V Lowside Voltage (V50R)", SMC_KEY(V50R) },
+ { "ACC Voltage (VACC)", SMC_KEY(VACC) },
+ { "ACDC Cooked Voltage (Vp0C)", SMC_KEY(Vp0C) },
+ { "Backlight Output Voltage (VBLC)", SMC_KEY(VBLC) },
+ { "Backlight Voltage (VZBL)", SMC_KEY(VZBL) },
+ { "CPU AXG Low-Side Voltage (VC0G)", SMC_KEY(VC0G) },
+ { "CPU Core Voltage (VC0C)", SMC_KEY(VC0C) },
+ { "CPU Core Voltage (VCS0)", SMC_KEY(VCS0) },
+ { "CPU High Side Voltage From EMC1704 (VC1R)", SMC_KEY(VC1R) },
+ { "CPU Mem Load-Side Voltage (VC0M)", SMC_KEY(VC0M) },
+ { "CPU Ripple Voltage (VCRC)", SMC_KEY(VCRC) },
+ { "CPU Ripple Voltage (VCRP)", SMC_KEY(VCRP) },
+ { "CPU VCCSA Voltage (VC2C)", SMC_KEY(VC2C) },
+ { "CPU VSA Low Side Voltage (VC0S)", SMC_KEY(VC0S) },
+ { "DIMM Load-Side Voltage (VM0R)", SMC_KEY(VM0R) },
+ { "Drive 0 5V Load-Side Voltage (VH05)", SMC_KEY(VH05) },
+ { "FIVR CPU Supply Voltage (VCFR)", SMC_KEY(VCFR) },
+ { "GFX Vcore Voltage (VN0R)", SMC_KEY(VN0R) },
+ { "GPU A VDDCI Low Side Voltage (VG0S)", SMC_KEY(VG0S) },
+ { "GPU B VDDCI Low Side Voltage (VG1S)", SMC_KEY(VG1S) },
+ { "GPU Core Input-Side Voltage (VG1C)", SMC_KEY(VG1C) },
+ { "GPU Core Voltage (VG0C)", SMC_KEY(VG0C) },
+ { "GPU Frame Buffer Input-Side Voltage (VG0F)", SMC_KEY(VG0F) },
+ { "GPU Frame Buffer Input-Side Voltage (VG1F)", SMC_KEY(VG1F) },
+ { "GPU Uncore High-Side Voltage (VG0U)", SMC_KEY(VG0U) },
+ { "GPU VDDCI Load-Side Voltage (VG0I)", SMC_KEY(VG0I) },
+ { "LCD IR Temp Sensor Voltage Raw Register (VL3t)", SMC_KEY(VL3t) },
+ { "MCP Core Voltage (VN0C)", SMC_KEY(VN0C) },
+ { "MCP Memory Voltage (VN1C)", SMC_KEY(VN1C) },
+ { "Memory Voltage (VZDM)", SMC_KEY(VZDM) },
+ { "ODD Voltage (VODC)", SMC_KEY(VODC) },
+ { "ODD Voltage (VZOD)", SMC_KEY(VZOD) },
+ { "PBus Rail Voltage (VP0R)", SMC_KEY(VP0R) },
+ { "PCH Low-Side Voltage (VN1R)", SMC_KEY(VN1R) },
+ { "PCH/GPU/TBT Load-Side Voltage (VR1R)", SMC_KEY(VR1R) },
+ { "PSU Cooked Temperature As Voltage (VP0T)", SMC_KEY(VP0T) },
+ { "SD Card Voltage (VSDC)", SMC_KEY(VSDC) },
+ { "SSD 3.3V Low-Side Voltage (VH1R)", SMC_KEY(VH1R) },
+ { "SSD 3V3 Low Side Voltage (VH0R)", SMC_KEY(VH0R) },
+ { "SSD Voltage (VZHD)", SMC_KEY(VZHD) },
+ { "T101 Accuator Voltage (VTPC)", SMC_KEY(VTPC) },
+ { "WIFI Voltage (VZAP)", SMC_KEY(VZAP) },
+ { "Wireless Voltage (VAPC)", SMC_KEY(VAPC) },
+};
+
+static int macsmc_hwmon_create_sensor_acpi(struct apple_smc *smc,
+ struct macsmc_hwmon_sensor *sensor, smc_key key, char *name, u32 attrs)
+{
+ int ret;
+
+ ret = apple_smc_get_key_info(smc, key, &sensor->info);
+ if (ret)
+ return ret;
+
+ sensor->macsmc_key = key;
+ sensor->label = name;
+ sensor->attrs = attrs;
+
+ return 0;
+}
/*
* Fan data is exposed by the SMC as multiple sensors.
*
@@ -871,6 +1475,57 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
return 0;
}
+static int macsmc_hwmon_create_fan_acpi(struct apple_smc *smc,
+ struct macsmc_hwmon_fan *fan, int num)
+{
+ const char num_char = num + '0';
+ int ret;
+
+ ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->now,
+ __SMC_KEY('F', num_char, 'A', 'c'), NULL, 0);
+ if (ret)
+ return ret;
+
+ if (apple_smc_key_exists(smc, __SMC_KEY('F', num_char, 'I', 'D'))) {
+ char buf[17];
+
+ ret = apple_smc_read(smc, __SMC_KEY('F', num_char, 'I', 'D'), buf, 16);
+ if (ret)
+ return ret;
+
+ buf[16] = '\0';
+ snprintf(fan->label, sizeof(fan->label), "%s", buf + 4);
+ } else
+ snprintf(fan->label, sizeof(fan->label), "Fan %d", num);
+
+ ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->min,
+ __SMC_KEY('F', num_char, 'M', 'n'), NULL, 0);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->max,
+ __SMC_KEY('F', num_char, 'M', 'x'), NULL, 0);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->set,
+ __SMC_KEY('F', num_char, 'T', 'g'), NULL, 0);
+ if (ret)
+ return ret;
+
+ if (apple_smc_key_exists(smc, __SMC_KEY('F', num_char, 'M', 'd'))) {
+ ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->mode,
+ __SMC_KEY('F', num_char, 'M', 'd'), NULL, 0);
+ if (ret)
+ return ret;
+ }
+
+ fan->attrs = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_TARGET;
+ fan->manual = false;
+
+ return 0;
+}
+
static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
struct device_node *hwmon_node)
{
@@ -907,7 +1562,7 @@ static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
if (n_fan) {
hwmon->fan.fans = devm_kcalloc(hwmon->dev, n_fan,
- sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
+ sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
if (!hwmon->fan.fans)
return -ENOMEM;
@@ -990,6 +1645,101 @@ static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
return 0;
}
+static int macsmc_hwmon_register_sensors_acpi(
+ struct macsmc_hwmon *hwmon,
+ struct macsmc_hwmon_sensors *sensors,
+ const struct macsmc_sensor_desc *descs,
+ size_t num_descs,
+ u32 attrs)
+{
+ size_t count = 0;
+ size_t i;
+ int ret;
+
+ for (i = 0; i < num_descs; i++) {
+ if (apple_smc_key_exists(hwmon->smc, descs[i].key))
+ count++;
+ }
+
+ if (!count)
+ return 0;
+
+ sensors->sensors = devm_kcalloc(hwmon->dev, count,
+ sizeof(*sensors->sensors),
+ GFP_KERNEL);
+ if (!sensors->sensors)
+ return -ENOMEM;
+
+ sensors->count = 0;
+
+ for (i = 0; i < num_descs; i++) {
+ if (!apple_smc_key_exists(hwmon->smc, descs[i].key))
+ continue;
+
+ ret = macsmc_hwmon_create_sensor_acpi(hwmon->smc,
+ &sensors->sensors[sensors->count],
+ descs[i].key,
+ descs[i].name,
+ attrs);
+ if (ret)
+ return ret;
+
+ sensors->count++;
+ }
+
+ return 0;
+}
+
+static int macsmc_hwmon_populate_sensors_acpi(struct macsmc_hwmon *hwmon)
+{
+ int ret, i;
+
+ ret = apple_smc_read_u8(hwmon->smc, SMC_KEY(FNum), &hwmon->fan.count);
+ if (hwmon->fan.count && !ret) {
+ hwmon->fan.fans = devm_kcalloc(hwmon->dev, hwmon->fan.count,
+ sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
+ if (!hwmon->fan.fans)
+ return -ENOMEM;
+
+ hwmon->fan.has_old_manual = apple_smc_key_exists(hwmon->smc,
+ __SMC_KEY('F', 'S', '!', ' '));
+
+ for (i = 0; i < hwmon->fan.count; i++) {
+ macsmc_hwmon_create_fan_acpi(hwmon->smc, &hwmon->fan.fans[i], i);
+ if (ret)
+ return ret;
+ }
+ }
+ ret = macsmc_hwmon_register_sensors_acpi(hwmon, &hwmon->temp,
+ temp_sensors,
+ ARRAY_SIZE(temp_sensors),
+ HWMON_T_INPUT | HWMON_T_LABEL);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_register_sensors_acpi(hwmon, &hwmon->power,
+ power_sensors,
+ ARRAY_SIZE(power_sensors),
+ HWMON_P_INPUT | HWMON_P_LABEL);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_register_sensors_acpi(hwmon, &hwmon->curr,
+ current_sensors,
+ ARRAY_SIZE(current_sensors),
+ HWMON_C_INPUT | HWMON_C_LABEL);
+ if (ret)
+ return ret;
+
+ ret = macsmc_hwmon_register_sensors_acpi(hwmon, &hwmon->volt,
+ voltage_sensors,
+ ARRAY_SIZE(voltage_sensors),
+ HWMON_I_INPUT | HWMON_I_LABEL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
/* Create NULL-terminated config arrays */
static void macsmc_hwmon_populate_configs(u32 *configs, const struct macsmc_hwmon_sensors *sensors)
{
@@ -1090,9 +1840,9 @@ static int macsmc_hwmon_probe(struct platform_device *pdev)
/*
* The MFD driver will try to probe us unconditionally. Some devices
* with the SMC do not have hwmon capabilities. Only probe if we have
- * a hwmon node.
+ * a hwmon node or the machine is a intel mac.
*/
- if (!pdev->dev.of_node)
+ if (!pdev->dev.of_node && !smc->is_acpi)
return -ENODEV;
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon),
@@ -1102,8 +1852,12 @@ static int macsmc_hwmon_probe(struct platform_device *pdev)
hwmon->dev = &pdev->dev;
hwmon->smc = smc;
+ platform_set_drvdata(pdev, hwmon);
- ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node);
+ if (hwmon->smc->is_acpi)
+ ret = macsmc_hwmon_populate_sensors_acpi(hwmon);
+ else
+ ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node);
if (ret) {
dev_err(hwmon->dev, "Could not parse sensors\n");
return ret;
@@ -1142,6 +1896,30 @@ static int macsmc_hwmon_probe(struct platform_device *pdev)
return 0;
}
+static int macsmc_hwmon_resume(struct platform_device *pdev)
+{
+ struct macsmc_hwmon *hwmon = dev_get_drvdata(&pdev->dev);
+
+ if (hwmon->smc->is_acpi && !hwmon->fan.has_old_manual) {
+ int idx;
+ int ret;
+
+ for (idx = 0; idx < hwmon->fan.count; idx++)
+ if (hwmon->fan.fans[idx].manual) {
+ ret = macsmc_hwmon_write_key(hwmon->smc,
+ &hwmon->fan.fans[idx].mode, 1);
+ if (ret < 0) {
+ dev_warn(&pdev->dev,
+ "Unable to reenable fan manual, disabling\n");
+ hwmon->fan.fans[idx].manual = false;
+ }
+ }
+
+ }
+
+ return 0;
+}
+
static const struct of_device_id macsmc_hwmon_of_table[] = {
{ .compatible = "apple,smc-hwmon" },
{}
@@ -1150,6 +1928,7 @@ MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table);
static struct platform_driver macsmc_hwmon_driver = {
.probe = macsmc_hwmon_probe,
+ .resume = macsmc_hwmon_resume,
.driver = {
.name = "macsmc-hwmon",
.of_match_table = macsmc_hwmon_of_table,
@@ -1160,3 +1939,4 @@ module_platform_driver(macsmc_hwmon_driver);
MODULE_DESCRIPTION("Apple Silicon SMC hwmon driver");
MODULE_AUTHOR("James Calligeros <jcalligeros99@gmail.com>");
MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:macsmc-hwmon");
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 13/14] hwmon: add support for x86 macs in macsmc-hwmon
2026-10-29 8:52 ` [RFC PATCH 13/14] hwmon: add support for x86 macs in macsmc-hwmon Atharva Tiwari
@ 2026-10-29 9:20 ` sashiko-bot
0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:20 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] hwmon: macsmc: NULL pointer dereference in DT sensor label handling
- [High] hwmon: macsmc: macsmc_hwmon_create_fan_acpi() treats positive read return as error
- [High] hwmon: macsmc: unchecked fan setup in macsmc_hwmon_populate_sensors_acpi()
- [High] hwmon: macsmc: endianness bug in macsmc_hwmon_write_old_fan_manual()
- [Medium] hwmon: macsmc: macsmc_hwmon_write_fan() swallows target write errors with > 0
--
commit 4bcde7b8b0bda2dbb66c2bfcaa1b0806af093c48
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
hwmon: add support for x86 macs in macsmc-hwmon
This commit adds support for Intel Macs to macsmc-hwmon, exposing readable
sensor names for temperature, power, current, and voltage keys, as well as
fan speed reporting and manual control on ACPI platforms.
> diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
> index 2e74dcd6651b3..29e43ff4b4feb 100644
> --- a/drivers/hwmon/macsmc-hwmon.c
> +++ b/drivers/hwmon/macsmc-hwmon.c
[ ... ]
> @@ -47,7 +52,7 @@ MODULE_PARM_DESC(fan_control,
> struct macsmc_hwmon_sensor {
> struct apple_smc_key_info info;
> smc_key macsmc_key;
> - char label[MAX_LABEL_LENGTH];
> + char *label;
> u32 attrs;
> };
[Severity: High]
Will changing sensor->label from an inline char array to a pointer cause a
null pointer dereference on Device Tree platforms during
macsmc_hwmon_probe() -> macsmc_hwmon_populate_sensors()?
In macsmc_hwmon_populate_sensors(), the sensor arrays are allocated with
devm_kcalloc(), leaving sensor->label initialized to NULL. When
macsmc_hwmon_create_sensor() runs for a Device Tree node with a label
property, it still writes directly to sensor->label using sizeof(char *):
drivers/hwmon/macsmc-hwmon.c:macsmc_hwmon_create_sensor() {
...
ret = of_property_read_string(sensor_node, "label", &label);
if (ret)
dev_dbg(dev, "No label found for sensor %s\n", key);
else
strscpy_pad(sensor->label, label, sizeof(sensor->label));
}
And if a Device Tree sensor node does not have a label property,
sensor->label remains NULL when macsmc_hwmon_populate_sensors() immediately
dereferences it across the current, power, temperature, and voltage loops:
drivers/hwmon/macsmc-hwmon.c:macsmc_hwmon_populate_sensors() {
...
if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
sensor->attrs = HWMON_C_INPUT;
if (*sensor->label)
sensor->attrs |= HWMON_C_LABEL;
...
}
[ ... ]
> @@ -599,6 +605,23 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan,
> }
> }
>
> +static int macsmc_hwmon_write_old_fan_manual(struct macsmc_hwmon *hwmon, u8 val, int channel)
> +{
> + int ret;
> + u16 buf;
> +
> + ret = apple_smc_read_u16(hwmon->smc, __SMC_KEY('F', 'S', '!', ' '), &buf);
> + if (ret)
> + return ret;
> +
> + if (val)
> + buf |= BIT(channel);
> + else
> + buf &= ~BIT(channel);
> +
> + return apple_smc_write_u16(hwmon->smc, __SMC_KEY('F', 'S', '!', ' '), cpu_to_be16(buf));
> +}
[Severity: High]
Should buf be converted with be16_to_cpu() after apple_smc_read_u16() in
macsmc_hwmon_write_old_fan_manual()?
Because apple_smc_read_u16() reads raw big-endian bytes into buf without
byte-swapping, modifying BIT(channel) on the unswapped value and then
passing cpu_to_be16(buf) to apple_smc_write_u16() swaps the high and low
bytes of the manual fan bitmask on little-endian systems. For example, once
fan 0 is placed in manual mode (0x0001 big-endian, read into buf as 0x0100),
clearing BIT(0) on buf is a no-op and writes 0x0001 big-endian back to the
SMC, leaving fan 0 stuck in manual mode.
> static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
> long val)
> {
[ ... ]
> @@ -625,7 +649,10 @@ static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
> if (val >= min && val <= max) {
> if (!hwmon->fan.fans[channel].manual) {
> /* Write 1 to mode key for manual control */
> - ret = macsmc_hwmon_write_key(hwmon->smc,
> + if (hwmon->fan.has_old_manual)
> + ret = macsmc_hwmon_write_old_fan_manual(hwmon, 1, channel);
> + else
> + ret = macsmc_hwmon_write_key(hwmon->smc,
> &hwmon->fan.fans[channel].mode, 1);
> if (ret < 0)
> return ret;
>
> hwmon->fan.fans[channel].manual = true;
> }
> return macsmc_hwmon_write_key(hwmon->smc,
> - &hwmon->fan.fans[channel].set, val);
> + &hwmon->fan.fans[channel].set, val) > 0;
[Severity: Medium]
Why is the return value of macsmc_hwmon_write_key() compared with > 0 here
in macsmc_hwmon_write_fan()?
The relational comparison turns any negative error code returned by
macsmc_hwmon_write_key() into 0, causing hwmon_attr_store() to treat failed
writes to the fan target key as successful.
[ ... ]
> @@ -871,6 +1475,57 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
> return 0;
> }
>
> +static int macsmc_hwmon_create_fan_acpi(struct apple_smc *smc,
> + struct macsmc_hwmon_fan *fan, int num)
> +{
> + const char num_char = num + '0';
> + int ret;
> +
> + ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->now,
> + __SMC_KEY('F', num_char, 'A', 'c'), NULL, 0);
> + if (ret)
> + return ret;
> +
> + if (apple_smc_key_exists(smc, __SMC_KEY('F', num_char, 'I', 'D'))) {
> + char buf[17];
> +
> + ret = apple_smc_read(smc, __SMC_KEY('F', num_char, 'I', 'D'), buf, 16);
> + if (ret)
> + return ret;
[Severity: High]
Does checking if (ret) after apple_smc_read() cause
macsmc_hwmon_create_fan_acpi() to abort on success whenever the F%dID key is
present?
On ACPI platforms, apple_smc_read() delegates to apple_smc_acpi_read(),
which returns the positive number of bytes read (16) on success:
drivers/platform/x86/macsmc-acpi.c:apple_smc_acpi_read() {
...
if (ret < 0)
return ret;
return size;
}
When F%dID exists and is read, ret is 16, so macsmc_hwmon_create_fan_acpi()
returns 16 early before initializing fan->label, the min/max/target/mode
sensors, and fan->attrs.
[ ... ]
> @@ -990,6 +1645,101 @@ static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
[ ... ]
> +static int macsmc_hwmon_populate_sensors_acpi(struct macsmc_hwmon *hwmon)
> +{
> + int ret, i;
> +
> + ret = apple_smc_read_u8(hwmon->smc, SMC_KEY(FNum), &hwmon->fan.count);
> + if (hwmon->fan.count && !ret) {
> + hwmon->fan.fans = devm_kcalloc(hwmon->dev, hwmon->fan.count,
> + sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
> + if (!hwmon->fan.fans)
> + return -ENOMEM;
> +
> + hwmon->fan.has_old_manual = apple_smc_key_exists(hwmon->smc,
> + __SMC_KEY('F', 'S', '!', ' '));
> +
> + for (i = 0; i < hwmon->fan.count; i++) {
> + macsmc_hwmon_create_fan_acpi(hwmon->smc, &hwmon->fan.fans[i], i);
> + if (ret)
> + return ret;
> + }
> + }
[Severity: High]
Was ret meant to capture the return value of macsmc_hwmon_create_fan_acpi()
in macsmc_hwmon_populate_sensors_acpi()?
Because ret is not assigned when calling macsmc_hwmon_create_fan_acpi(),
if (ret) always checks the stale 0 from apple_smc_read_u8(), ignoring fan
initialization failures and leaving hwmon->fan.fans[i].attrs as 0.
Also, if apple_smc_read_u8() fails after modifying hwmon->fan.count,
hwmon->fan.count can remain non-zero while hwmon->fan.fans is still NULL,
which would later dereference NULL in macsmc_hwmon_create_infos() ->
macsmc_hwmon_populate_fan_configs().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=13
^ permalink raw reply [flat|nested] 34+ messages in thread
* [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys
2026-10-29 8:51 [RFC PATCH 00/14] Add macsmc support for Intel Macs, replacing applesmc Atharva Tiwari
` (12 preceding siblings ...)
2026-10-29 8:52 ` [RFC PATCH 13/14] hwmon: add support for x86 macs in macsmc-hwmon Atharva Tiwari
@ 2026-10-29 8:52 ` Atharva Tiwari
2026-10-29 9:22 ` Sasha Finkelstein
2026-10-29 9:26 ` sashiko-bot
13 siblings, 2 replies; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 8:52 UTC (permalink / raw)
Cc: Atharva Tiwari, Bjorn Helgaas, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Rafael J. Wysocki, Len Brown, Linus Walleij, Bartosz Golaszewski,
Sven Peter, Janne Grunau, Neal Gompa, Guenter Roeck,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Lee Jones, Pavel Machek, Griffin Kroah-Hartman, Hans de Goede,
Ilpo Järvinen, Sebastian Reichel, Alexandre Belloni,
Wim Van Sebroeck, Jarkko Sakkinen, Mimi Zohar, Roberto Sassu,
Dmitry Kasatkin, Eric Snowberg, Paul Moore, James Morris,
Serge E. Hallyn, linux-kernel, linux-pci, linux-acpi, linux-gpio,
asahi, linux-arm-kernel, linux-hwmon, linux-iio, linux-leds, mfd,
platform-driver-x86, linux-pm, linux-rtc, linux-watchdog,
linux-integrity, keyrings, linux-security-module
Add a /dev/smc device so userspace can talk to the SMC
directly. Some keys that need to be written aren't handled by
any driver, or affect several drivers at once, so they
can't be exposed cleanly through the kernel interfaces.
Keys already managed by kernel drivers can't be written this
way, to avoid conflicts.
Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
---
drivers/mfd/macsmc-core.c | 155 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/macsmc.h | 27 +++++++
2 files changed, 182 insertions(+)
diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
index bc4200f57..a30f7386e 100644
--- a/drivers/mfd/macsmc-core.c
+++ b/drivers/mfd/macsmc-core.c
@@ -6,10 +6,13 @@
#include <linux/bitfield.h>
#include <linux/device.h>
+#include <linux/fs.h>
#include <linux/mfd/core.h>
#include <linux/mfd/macsmc.h>
+#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
+#include <linux/uaccess.h>
static const struct mfd_cell apple_smc_rtkit_devs[] = {
MFD_CELL_NAME("macsmc-input"),
@@ -112,6 +115,144 @@ static void apple_smc_disable_notifications(void *data)
smc->be->disable_notifications(smc->be_cookie);
}
+static long apple_smc_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct apple_smc *smc = file->private_data;
+ void __user *argp = (void __user *)arg;
+ int ret;
+
+ switch (cmd) {
+ case IOCTL_SMC_READ: {
+ struct ioctl_smc_rw_payload payload;
+ struct apple_smc_key_info info;
+
+ if (copy_from_user(&payload, argp, sizeof(payload)))
+ return -EFAULT;
+
+ ret = apple_smc_get_key_info(smc, payload.key, &info);
+ if (ret < 0)
+ return ret;
+
+ if (payload.size != info.size)
+ return -EINVAL;
+
+ if (payload.size > 32)
+ return -EINVAL;
+
+ ret = apple_smc_read(smc, payload.key, payload.buf, payload.size);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &payload, sizeof(payload)))
+ return -EFAULT;
+ return 0;
+ }
+ case IOCTL_SMC_WRITE: {
+ struct ioctl_smc_rw_payload payload;
+ struct apple_smc_key_info info;
+
+ if (copy_from_user(&payload, argp, sizeof(payload)))
+ return -EFAULT;
+
+ ret = apple_smc_get_key_info(smc, payload.key, &info);
+ if (ret < 0)
+ return ret;
+
+ if (payload.size != info.size)
+ return -EINVAL;
+
+ if (payload.size > 32)
+ return -EINVAL;
+
+ switch (payload.key) {
+ case __SMC_KEY('C', 'H', '0', 'I'):
+ case __SMC_KEY('C', 'H', '0', 'C'):
+ case __SMC_KEY('C', 'H', '0', 'K'):
+ case __SMC_KEY('C', 'H', '0', 'B'):
+ case __SMC_KEY('C', 'H', 'T', 'E'):
+ case __SMC_KEY('C', 'H', 'L', 'S'):
+ case __SMC_KEY('B', 'C', 'L', 'M'):
+ case __SMC_KEY('M', 'B', 'S', 'E'):
+ case __SMC_KEY('C', 'L', 'K', 'L'):
+ case __SMC_KEY('C', 'L', 'K', 'O'):
+ case __SMC_KEY('O', 'S', 'W', 'D'):
+ case __SMC_KEY('N', 'A', 'T', 'J'):
+ case __SMC_KEY('N', 'A', 'T', 'i'):
+ case __SMC_KEY('N', 'E', 'S', 'N'):
+ case __SMC_KEY('M', 'O', 'C', 'N'):
+ case __SMC_KEY('L', 'K', 'S', 'B'):
+ case __SMC_KEY('N', 'T', 'O', 'K'):
+ case __SMC_KEY('N', 'T', 'A', 'P'):
+ case __SMC_KEY('F', '0', 'T', 'g'):
+ case __SMC_KEY('F', '1', 'T', 'g'):
+ case __SMC_KEY('F', '0', 'M', 'd'):
+ case __SMC_KEY('F', '1', 'M', 'd'):
+ case __SMC_KEY('F', 'S', '!', ' '):
+ return -EPERM;
+ }
+
+ ret = apple_smc_write(smc, payload.key, payload.buf, payload.size);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+ }
+ case IOCTL_SMC_GET_KEY_INDEX: {
+ struct ioctl_smc_index_payload payload;
+
+ if (copy_from_user(&payload, argp, sizeof(payload)))
+ return -EFAULT;
+
+ ret = apple_smc_get_key_by_index(smc, payload.index, &payload.key);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &payload, sizeof(payload)))
+ return -EFAULT;
+
+ return 0;
+ }
+ case IOCTL_SMC_GET_KEY_INFO: {
+ struct ioctl_smc_info_payload payload;
+
+ if (copy_from_user(&payload, argp, sizeof(payload)))
+ return -EFAULT;
+
+ ret = apple_smc_get_key_info(smc, payload.key, &payload.info);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user(argp, &payload, sizeof(payload)))
+ return -EFAULT;
+
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apple_smc_fops_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *misc = file->private_data;
+
+ file->private_data = container_of(misc, struct apple_smc, miscdev);
+ return 0;
+}
+
+static const struct file_operations apple_smc_fops = {
+ .owner = THIS_MODULE,
+ .open = apple_smc_fops_open,
+ .unlocked_ioctl = apple_smc_fops_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = noop_llseek,
+};
+
+static void apple_smc_misc_unregister(void *data)
+{
+ misc_deregister(data);
+}
+
int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
void *cookie, struct blocking_notifier_head *event_handlers, bool is_acpi)
{
@@ -150,6 +291,20 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
if (ret)
return ret;
+ smc->miscdev.minor = MISC_DYNAMIC_MINOR;
+ smc->miscdev.name = "smc";
+ smc->miscdev.fops = &apple_smc_fops;
+ smc->miscdev.parent = dev;
+ smc->miscdev.mode = 0600;
+
+ ret = misc_register(&smc->miscdev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, apple_smc_misc_unregister, &smc->miscdev);
+ if (ret)
+ return ret;
+
if (smc->is_acpi) {
ret = devm_mfd_add_devices(dev, -1, apple_smc_acpi_common,
ARRAY_SIZE(apple_smc_acpi_common), NULL, 0, NULL);
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
index 4b4368b99..29fd7b20c 100644
--- a/include/linux/mfd/macsmc.h
+++ b/include/linux/mfd/macsmc.h
@@ -8,6 +8,8 @@
#ifndef _LINUX_MFD_MACSMC_H
#define _LINUX_MFD_MACSMC_H
+#include <linux/ioctl.h>
+#include <linux/miscdevice.h>
#include <linux/soc/apple/rtkit.h>
/**
@@ -125,6 +127,7 @@ struct apple_smc_backend_ops {
* @is_acpi: Is the backend ACPI based
* @mutex: Mutex for non-atomic mode
* @lock: Spinlock for atomic mode
+ * @miscdev: Misc Device struct for ioctl ops
*/
struct apple_smc {
struct device *dev;
@@ -141,8 +144,32 @@ struct apple_smc {
struct mutex mutex;
spinlock_t lock;
+
+ struct miscdevice miscdev;
+};
+
+struct ioctl_smc_rw_payload {
+ smc_key key;
+ size_t size;
+ u8 buf[32];
+};
+
+struct ioctl_smc_index_payload {
+ int index;
+ smc_key key;
+};
+
+struct ioctl_smc_info_payload {
+ smc_key key;
+ struct apple_smc_key_info info;
};
+#define APPLE_SMC_MAGIC 'S'
+#define IOCTL_SMC_READ _IOWR(APPLE_SMC_MAGIC, 1, struct ioctl_smc_rw_payload)
+#define IOCTL_SMC_WRITE _IOW(APPLE_SMC_MAGIC, 2, struct ioctl_smc_rw_payload)
+#define IOCTL_SMC_GET_KEY_INDEX _IOWR(APPLE_SMC_MAGIC, 3, struct ioctl_smc_index_payload)
+#define IOCTL_SMC_GET_KEY_INFO _IOWR(APPLE_SMC_MAGIC, 4, struct ioctl_smc_info_payload)
+
/**
* apple_smc_read - Read size bytes from given SMC key into buf
* @smc: Pointer to apple_smc struct
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys
2026-10-29 8:52 ` [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys Atharva Tiwari
@ 2026-10-29 9:22 ` Sasha Finkelstein
2026-10-29 14:39 ` Atharva Tiwari
2026-10-29 9:26 ` sashiko-bot
1 sibling, 1 reply; 34+ messages in thread
From: Sasha Finkelstein @ 2026-10-29 9:22 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-kernel, linux-pci, linux-acpi, linux-gpio, asahi,
linux-arm-kernel, linux-hwmon, mfd, platform-driver-x86, linux-pm,
linux-rtc, linux-watchdog
On Oct 29, 2026, at 09:52, Atharva Tiwari <atharvatiwarilinuxdev@gmail.com> wrote:
>
> Add a /dev/smc device so userspace can talk to the SMC
> directly.
This should either be dropped from the series, or be hidden behind a
`melt_my_mac`-style module flag, or switched to a whitelist approach.
We do not know what keys will be introduced in new smc firmware
versions, and there are already keys that should not be randomly poked
at.
^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys
2026-10-29 9:22 ` Sasha Finkelstein
@ 2026-10-29 14:39 ` Atharva Tiwari
2026-10-29 14:53 ` Sasha Finkelstein
0 siblings, 1 reply; 34+ messages in thread
From: Atharva Tiwari @ 2026-10-29 14:39 UTC (permalink / raw)
To: k
Cc: alexandre.belloni, andy, asahi, atharvatiwarilinuxdev, bhelgaas,
bp, brgl, dave.hansen, dlechner, dmitry.kasatkin, eric.snowberg,
griffin, hansg, hpa, ilpo.jarvinen, j, jarkko, jic23, jmorris,
lee, lenb, linusw, linux-acpi, linux-arm-kernel, linux-gpio,
linux-hwmon, linux-kernel, linux-pci, linux-pm, linux-rtc,
linux-watchdog, linux, mfd, mingo, neal, nuno.sa, paul, pavel,
platform-driver-x86, rafael, roberto.sassu, serge, sre, sven,
tglx, wim, x86, zohar
> This should either be dropped from the series, or be hidden behind a
> `melt_my_mac`-style module flag, or switched to a whitelist approach.
> We do not know what keys will be introduced in new smc firmware
> versions, and there are already keys that should not be randomly poked
> at.
1. The patch is required, atleast on intel macs, as without writing GPU and CPU
states to smc, the system gives PROCHOT signal under small load
, and heats up a lot.
2. the smc dosent expose keys of nature that can physically damage the system
it only exposes power/volt data, and the most you can poke is reboot
keys and gpu/cpu p-states.
3. If a person is randomly touching keys, Its their problem and not ours.
4. There are a lot of things that can mess the system way more and permanently
than smc (undervolting for example), but they are not safe guarded, why
should this be then?
^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys
2026-10-29 14:39 ` Atharva Tiwari
@ 2026-10-29 14:53 ` Sasha Finkelstein
0 siblings, 0 replies; 34+ messages in thread
From: Sasha Finkelstein @ 2026-10-29 14:53 UTC (permalink / raw)
To: Atharva Tiwari
Cc: alexandre.belloni, andy, asahi, bhelgaas, bp, brgl, dave.hansen,
dlechner, dmitry.kasatkin, eric.snowberg, griffin, hansg, hpa,
ilpo.jarvinen, j, jarkko, jic23, jmorris, lee, lenb, linusw,
linux-acpi, linux-arm-kernel, linux-gpio, linux-hwmon,
linux-kernel, linux-pci, linux-pm, linux-rtc, linux-watchdog,
linux, mfd, mingo, neal, nuno.sa, paul, pavel,
platform-driver-x86, rafael, roberto.sassu, serge, sre, sven,
tglx, wim, x86, zohar
On Oct 29, 2026, at 15:39, Atharva Tiwari <atharvatiwarilinuxdev@gmail.com> wrote:
> 1. The patch is required, atleast on intel macs, as without writing GPU and CPU
> states to smc, the system gives PROCHOT signal under small load
> , and heats up a lot.
In that case, why are you exposing a generic smc key interface instead
of one that just does that? If you think that this data should be set
from userspace - whitelist the set of keys that are needed.
> 2. the smc dosent expose keys of nature that can physically damage the system
> it only exposes power/volt data, and the most you can poke is reboot
> keys and gpu/cpu p-states.
Are you willing to make that claim for every smc-equipped device, not
just T2? Are you willing to make the same claim for future versions of
smc firmware?
> 3. If a person is randomly touching keys, Its their problem and not ours.
>
> 4. There are a lot of things that can mess the system way more and permanently
> than smc (undervolting for example), but they are not safe guarded, why
> should this be then?
I think it is inadvisable to leave a loaded footgun around and then
blame the user for using it. As an "OS Vendor" you have a duty of care
to your users.
^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys
2026-10-29 8:52 ` [RFC PATCH 14/14] mfd/macsmc: add ioctl to access smc keys Atharva Tiwari
2026-10-29 9:22 ` Sasha Finkelstein
@ 2026-10-29 9:26 ` sashiko-bot
1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-10-29 9:26 UTC (permalink / raw)
To: Atharva Tiwari
Cc: Bjorn Helgaas, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Rafael J. Wysocki, Len Brown,
Linus Walleij, Bartosz Golaszewski, Sven Peter, Janne Grunau,
Neal Gompa, Guenter Roeck, Jonathan Cameron, David Lechner,
Nuno Sá, Andy Shevchenko, Lee Jones, Pavel Machek,
Griffin Kroah-Hartman, Hans de Goede, Ilpo Järvinen,
Sebastian Reichel, Alexandre Belloni, Wim Van Sebroeck,
Jarkko Sakkinen, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin,
Eric Snowberg, Paul Moore, James Morris, Serge E. Hallyn,
linux-pci, linux-gpio, linux-hwmon, linux-leds, mfd, linux-rtc,
linux-watchdog, keyrings, linux-security-module
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] mfd: macsmc: use-after-free in apple_smc_fops_ioctl() after device unbind
- [Medium] mfd: macsmc: size_t in ioctl payload breaks compat_ioctl in apple_smc_fops
- [Medium] mfd: macsmc: incomplete write blocklist in apple_smc_fops_ioctl()
- [Low] mfd: macsmc: return -ENOTTY for unknown cmd in apple_smc_fops_ioctl()
- [Low] mfd: macsmc: commit subject violates MFD prefix and capitalization rules
--
commit 352ee7db8cfde297319806ac2e104b433cfad104
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
mfd/macsmc: add ioctl to access smc keys
This commit registers a /dev/smc miscdevice in macsmc-core to allow
userspace to read, write, and query Apple SMC keys via ioctls, while
blocking writes to a static list of keys managed by kernel drivers.
> mfd/macsmc: add ioctl to access smc keys
>
> Add a /dev/smc device so userspace can talk to the SMC
> directly. Some keys that need to be written aren't handled by
> any driver, or affect several drivers at once, so they
> can't be exposed cleanly through the kernel interfaces.
>
> Keys already managed by kernel drivers can't be written this
> way, to avoid conflicts.
>
> Signed-off-by: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>
[Severity: Low]
This isn't a bug, but the MFD subsystem convention uses the prefix format
"mfd: <driver>: <Capitalized description>". Should the commit subject be
formatted as "mfd: macsmc: Add ioctl to access SMC keys"?
> diff --git a/drivers/mfd/macsmc-core.c b/drivers/mfd/macsmc-core.c
> index bc4200f574bd5..a30f7386ef9cd 100644
> --- a/drivers/mfd/macsmc-core.c
> +++ b/drivers/mfd/macsmc-core.c
[ ... ]
> @@ -112,6 +115,144 @@ static void apple_smc_disable_notifications(void *data)
> smc->be->disable_notifications(smc->be_cookie);
> }
>
> +static long apple_smc_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> + struct apple_smc *smc = file->private_data;
> + void __user *argp = (void __user *)arg;
> + int ret;
> +
> + switch (cmd) {
[ ... ]
> + case IOCTL_SMC_WRITE: {
> + struct ioctl_smc_rw_payload payload;
> + struct apple_smc_key_info info;
> +
> + if (copy_from_user(&payload, argp, sizeof(payload)))
> + return -EFAULT;
> +
> + ret = apple_smc_get_key_info(smc, payload.key, &info);
> + if (ret < 0)
> + return ret;
> +
> + if (payload.size != info.size)
> + return -EINVAL;
> +
> + if (payload.size > 32)
> + return -EINVAL;
> +
> + switch (payload.key) {
> + case __SMC_KEY('C', 'H', '0', 'I'):
> + case __SMC_KEY('C', 'H', '0', 'C'):
> + case __SMC_KEY('C', 'H', '0', 'K'):
> + case __SMC_KEY('C', 'H', '0', 'B'):
> + case __SMC_KEY('C', 'H', 'T', 'E'):
> + case __SMC_KEY('C', 'H', 'L', 'S'):
> + case __SMC_KEY('B', 'C', 'L', 'M'):
> + case __SMC_KEY('M', 'B', 'S', 'E'):
> + case __SMC_KEY('C', 'L', 'K', 'L'):
> + case __SMC_KEY('C', 'L', 'K', 'O'):
> + case __SMC_KEY('O', 'S', 'W', 'D'):
> + case __SMC_KEY('N', 'A', 'T', 'J'):
> + case __SMC_KEY('N', 'A', 'T', 'i'):
> + case __SMC_KEY('N', 'E', 'S', 'N'):
> + case __SMC_KEY('M', 'O', 'C', 'N'):
> + case __SMC_KEY('L', 'K', 'S', 'B'):
> + case __SMC_KEY('N', 'T', 'O', 'K'):
> + case __SMC_KEY('N', 'T', 'A', 'P'):
> + case __SMC_KEY('F', '0', 'T', 'g'):
> + case __SMC_KEY('F', '1', 'T', 'g'):
> + case __SMC_KEY('F', '0', 'M', 'd'):
> + case __SMC_KEY('F', '1', 'M', 'd'):
> + case __SMC_KEY('F', 'S', '!', ' '):
> + return -EPERM;
> + }
[Severity: Medium]
Does this static blocklist miss other keys actively managed by MFD child
drivers?
In drivers/hwmon/macsmc-hwmon.c, macsmc_hwmon_create_fan_acpi() registers
all fans up to FNum using keys F<num>Tg and F<num>Md, and
macsmc_hwmon_create_fan() registers fan keys from DT on Apple Silicon:
drivers/hwmon/macsmc-hwmon.c:macsmc_hwmon_create_fan_acpi() {
const char num_char = num + '0';
...
ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->set,
__SMC_KEY('F', num_char, 'T', 'g'), NULL, 0);
if (ret)
return ret;
if (apple_smc_key_exists(smc, __SMC_KEY('F', num_char, 'M', 'd'))) {
ret = macsmc_hwmon_create_sensor_acpi(smc, &fan->mode,
__SMC_KEY('F', num_char, 'M', 'd'), NULL, 0);
}
On systems with more than two fans, fans 2 and above remain writable via
IOCTL_SMC_WRITE, which bypasses the min/max RPM validation in
macsmc_hwmon_write_fan() and desynchronizes hwmon->fan.fans[channel].manual.
Similarly, drivers/gpio/gpio-macsmc.c (registered as macsmc-gpio) manages
and writes PMU GPIO keys gP00..gPff in macsmc_gpio_set():
drivers/gpio/gpio-macsmc.c:macsmc_gpio_set() {
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
smc_key key = macsmc_gpio_key(offset);
int ret;
value |= CMD_OUTPUT;
ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value);
}
None of the gPxx keys are filtered here either, allowing userspace to
modify GPIO outputs behind the kernel driver's back.
[ ... ]
> + default:
> + return -EINVAL;
> + }
> +}
[Severity: Low]
Should the default case in apple_smc_fops_ioctl() return -ENOTTY instead of
-EINVAL for unrecognized ioctl commands?
> +
> +static int apple_smc_fops_open(struct inode *inode, struct file *file)
> +{
> + struct miscdevice *misc = file->private_data;
> +
> + file->private_data = container_of(misc, struct apple_smc, miscdev);
> + return 0;
> +}
> +
> +static const struct file_operations apple_smc_fops = {
> + .owner = THIS_MODULE,
> + .open = apple_smc_fops_open,
> + .unlocked_ioctl = apple_smc_fops_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> + .llseek = noop_llseek,
> +};
> +
> +static void apple_smc_misc_unregister(void *data)
> +{
> + misc_deregister(data);
> +}
> +
> int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
> void *cookie, struct blocking_notifier_head *event_handlers, bool is_acpi)
> {
> @@ -150,6 +291,20 @@ int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
> if (ret)
> return ret;
>
> + smc->miscdev.minor = MISC_DYNAMIC_MINOR;
> + smc->miscdev.name = "smc";
> + smc->miscdev.fops = &apple_smc_fops;
> + smc->miscdev.parent = dev;
> + smc->miscdev.mode = 0600;
> +
> + ret = misc_register(&smc->miscdev);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(dev, apple_smc_misc_unregister, &smc->miscdev);
> + if (ret)
> + return ret;
> +
[Severity: High]
Can this trigger a use-after-free in apple_smc_fops_ioctl() if the backend
device is unbound (or if a subsequent devm_mfd_add_devices() call in
apple_smc_probe() fails) while /dev/smc is open?
In apple_smc_probe(), smc is allocated via devm_kzalloc(dev, sizeof(*smc),
GFP_KERNEL), and smc->be_cookie is similarly devm-allocated on the backend
device. When /dev/smc is opened, apple_smc_fops_open() stores a raw pointer
to smc in file->private_data, and misc_open() only pins apple_smc_fops.owner
(macsmc-core, not the backend driver).
When the device is unbound or probe fails after misc_register(), devres
runs apple_smc_misc_unregister() -> misc_deregister(&smc->miscdev) and then
frees both smc and smc->be_cookie. Because misc_deregister() does not wait
for in-flight ioctls or revoke already-open file descriptors, any concurrent
or subsequent call to apple_smc_fops_ioctl() on the open file will
dereference freed memory via file->private_data when locking smc->mutex and
invoking smc->be callbacks with smc->be_cookie.
> diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
> index 4b4368b99adb6..29fd7b20ce9c8 100644
> --- a/include/linux/mfd/macsmc.h
> +++ b/include/linux/mfd/macsmc.h
[ ... ]
> @@ -141,8 +144,32 @@ struct apple_smc {
>
> struct mutex mutex;
> spinlock_t lock;
> +
> + struct miscdevice miscdev;
> +};
> +
> +struct ioctl_smc_rw_payload {
> + smc_key key;
> + size_t size;
> + u8 buf[32];
> +};
> +
> +struct ioctl_smc_index_payload {
> + int index;
> + smc_key key;
> +};
> +
> +struct ioctl_smc_info_payload {
> + smc_key key;
> + struct apple_smc_key_info info;
> };
>
> +#define APPLE_SMC_MAGIC 'S'
> +#define IOCTL_SMC_READ _IOWR(APPLE_SMC_MAGIC, 1, struct ioctl_smc_rw_payload)
> +#define IOCTL_SMC_WRITE _IOW(APPLE_SMC_MAGIC, 2, struct ioctl_smc_rw_payload)
> +#define IOCTL_SMC_GET_KEY_INDEX _IOWR(APPLE_SMC_MAGIC, 3, struct ioctl_smc_index_payload)
> +#define IOCTL_SMC_GET_KEY_INFO _IOWR(APPLE_SMC_MAGIC, 4, struct ioctl_smc_info_payload)
[Severity: Medium]
Does using size_t in struct ioctl_smc_rw_payload break 32-bit compat ioctls
even though apple_smc_fops sets .compat_ioctl = compat_ptr_ioctl?
On 64-bit kernels, size_t is 8 bytes with 8-byte alignment, which adds 4
bytes of padding after key (placing size at offset 8, buf at offset 16, and
making sizeof(struct ioctl_smc_rw_payload) == 48). In 32-bit userspace,
size_t is 4 bytes with 4-byte alignment (placing size at offset 4, buf at
offset 8, and making sizeof(struct ioctl_smc_rw_payload) == 40). Because
IOCTL_SMC_READ and IOCTL_SMC_WRITE encode the struct size, 32-bit compat
callers will pass a command number with size 40 that fails to match the
64-bit kernel's command number, or have mismatched field offsets and copy
8 bytes past the userspace struct if invoked with the 64-bit command number.
Also, since these ioctl structs and macros form a userspace ABI, should they
be placed in a header under include/uapi/ using fixed-size UAPI types (__u32,
__u8) with explicit padding rather than in the kernel-internal
include/linux/mfd/macsmc.h header?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085504.3547-1-atharvatiwarilinuxdev@gmail.com?part=14
^ permalink raw reply [flat|nested] 34+ messages in thread