* [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info
@ 2026-07-10 12:32 Shih-Yuan Lee
2026-07-10 12:32 ` [PATCH 1/1] " Shih-Yuan Lee
` (5 more replies)
0 siblings, 6 replies; 20+ messages in thread
From: Shih-Yuan Lee @ 2026-07-10 12:32 UTC (permalink / raw)
To: Henrik Rydberg, Guenter Roeck; +Cc: linux-hwmon, linux-kernel, Shih-Yuan Lee
Hi Henrik and Guenter,
This patch converts the applesmc driver from using the deprecated
hwmon_device_register() function to the modern hwmon_device_register_with_info()
API. This silences the following deprecation warning from dmesg on load:
[ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 kbd=0
[ 24.706270] applesmc applesmc.768: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info().
Because the driver creates all its sysfs attributes dynamically on the platform
device, we define a minimal struct hwmon_chip_info with a single temperature
channel and implement a visibility callback that returns 0 (hidden) for it.
This satisfies the new API requirements while keeping all existing sysfs paths
and attributes completely unchanged under the platform device directory
(/sys/devices/platform/applesmc.768/), ensuring 100% backwards compatibility
with existing user-space fan control tools (such as mbpfan and macfanctld).
Shih-Yuan Lee (1):
hwmon: (applesmc) Convert to hwmon_device_register_with_info
drivers/hwmon/applesmc.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
--
2.39.5
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee @ 2026-07-10 12:32 ` Shih-Yuan Lee 2026-07-10 14:01 ` Guenter Roeck 2026-07-11 3:36 ` [PATCH v2 0/1] " Shih-Yuan Lee ` (4 subsequent siblings) 5 siblings, 1 reply; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-10 12:32 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck; +Cc: linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers a warning in dmesg during driver initialization: [ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 kbd=0 [ 24.706270] applesmc applesmc.768: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info(). To silence this warning, convert the driver to use the modern hwmon_device_register_with_info() API. Because the driver creates all its sysfs attributes dynamically on the platform device, we define a minimal struct hwmon_chip_info with a single temperature channel and implement a visibility callback that returns 0 (hidden) for it. This satisfies the new API requirements while keeping all existing sysfs paths and attributes completely unchanged, ensuring backwards compatibility. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..2b10bef24d8d 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -1307,6 +1307,26 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static umode_t applesmc_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_is_visible, +}; + +static const struct hwmon_channel_info * const applesmc_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_chip_info applesmc_chip_info = { + .ops = &applesmc_hwmon_ops, + .info = applesmc_info, +}; + static int __init applesmc_init(void) { int ret; @@ -1363,7 +1383,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + &applesmc_chip_info, NULL); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 ` [PATCH 1/1] " Shih-Yuan Lee @ 2026-07-10 14:01 ` Guenter Roeck 2026-07-10 21:10 ` Armin Wolf 0 siblings, 1 reply; 20+ messages in thread From: Guenter Roeck @ 2026-07-10 14:01 UTC (permalink / raw) To: Shih-Yuan Lee, Henrik Rydberg; +Cc: linux-hwmon, linux-kernel On 7/10/26 05:32, Shih-Yuan Lee wrote: > The legacy hwmon_device_register() function is deprecated and triggers a > warning in dmesg during driver initialization: > > [ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 kbd=0 > [ 24.706270] applesmc applesmc.768: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info(). > > To silence this warning, convert the driver to use the modern > hwmon_device_register_with_info() API. > > Because the driver creates all its sysfs attributes dynamically on the platform > device, we define a minimal struct hwmon_chip_info with a single temperature > channel and implement a visibility callback that returns 0 (hidden) for it. > This satisfies the new API requirements while keeping all existing sysfs paths > and attributes completely unchanged, ensuring backwards compatibility. > Novel, but that would completely defeat the purpose of the new API. NACK. Guenter ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 14:01 ` Guenter Roeck @ 2026-07-10 21:10 ` Armin Wolf 2026-07-10 22:28 ` Guenter Roeck 0 siblings, 1 reply; 20+ messages in thread From: Armin Wolf @ 2026-07-10 21:10 UTC (permalink / raw) To: Guenter Roeck, Shih-Yuan Lee, Henrik Rydberg; +Cc: linux-hwmon, linux-kernel Am 10.07.26 um 16:01 schrieb Guenter Roeck: > On 7/10/26 05:32, Shih-Yuan Lee wrote: >> The legacy hwmon_device_register() function is deprecated and triggers a >> warning in dmesg during driver initialization: >> >> [ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 >> kbd=0 >> [ 24.706270] applesmc applesmc.768: hwmon_device_register() is >> deprecated. Please convert the driver to use >> hwmon_device_register_with_info(). >> >> To silence this warning, convert the driver to use the modern >> hwmon_device_register_with_info() API. >> >> Because the driver creates all its sysfs attributes dynamically on >> the platform >> device, we define a minimal struct hwmon_chip_info with a single >> temperature >> channel and implement a visibility callback that returns 0 (hidden) >> for it. >> This satisfies the new API requirements while keeping all existing >> sysfs paths >> and attributes completely unchanged, ensuring backwards compatibility. >> > > Novel, but that would completely defeat the purpose of the new API. > NACK. > > Guenter Agree, this deprecation warning should not be silenced by using cheap tricks. Lee, i think instead of abusing the hwmon_device_register_with_info() to emulate the behavior of the old API, i suggest that you use the new API to replace these two calls to applesmc_create_nodes(): ret = applesmc_create_nodes(fan_group, smcreg.fan_count); if (ret) goto out_info; ret = applesmc_create_nodes(temp_group, smcreg.index_count); if (ret) oto out_fans; Basically, you need to dynamically allocate a struct hwmon_channel_info each for the fan and temperature sensors. Then you basically use the new API to create the standard attributes for you, while the non-standard attributes are still created manually and passed using the extra_groups parameter. The non-standard attributes are: - fanX_safe - fanX_output (should be renamed to fanX_target to comply with the standard sysfs ABI) - fanX_manual (should be renamed to pwmX_enable to comply with the standard sysfs ABI) This changes are not exactly trivial and should be tested on real hardware. You should only attempt this if you have access to a compatible device for testing. Thanks, Armin Wolf ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 21:10 ` Armin Wolf @ 2026-07-10 22:28 ` Guenter Roeck 2026-07-11 2:51 ` Shih-Yuan Lee (FourDollars) 0 siblings, 1 reply; 20+ messages in thread From: Guenter Roeck @ 2026-07-10 22:28 UTC (permalink / raw) To: Armin Wolf, Shih-Yuan Lee, Henrik Rydberg; +Cc: linux-hwmon, linux-kernel On 7/10/26 14:10, Armin Wolf wrote: > Am 10.07.26 um 16:01 schrieb Guenter Roeck: > >> On 7/10/26 05:32, Shih-Yuan Lee wrote: >>> The legacy hwmon_device_register() function is deprecated and triggers a >>> warning in dmesg during driver initialization: >>> >>> [ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 kbd=0 >>> [ 24.706270] applesmc applesmc.768: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info(). >>> >>> To silence this warning, convert the driver to use the modern >>> hwmon_device_register_with_info() API. >>> >>> Because the driver creates all its sysfs attributes dynamically on the platform >>> device, we define a minimal struct hwmon_chip_info with a single temperature >>> channel and implement a visibility callback that returns 0 (hidden) for it. >>> This satisfies the new API requirements while keeping all existing sysfs paths >>> and attributes completely unchanged, ensuring backwards compatibility. >>> >> >> Novel, but that would completely defeat the purpose of the new API. >> NACK. >> >> Guenter > > Agree, this deprecation warning should not be silenced by using cheap tricks. > > Lee, i think instead of abusing the hwmon_device_register_with_info() to emulate the > behavior of the old API, i suggest that you use the new API to replace these two calls > to applesmc_create_nodes(): > > ret = applesmc_create_nodes(fan_group, smcreg.fan_count); > if (ret) > goto out_info; > > ret = applesmc_create_nodes(temp_group, smcreg.index_count); > if (ret) > oto out_fans; > > Basically, you need to dynamically allocate a struct hwmon_channel_info each for the fan > and temperature sensors. Then you basically use the new API to create the standard attributes > for you, while the non-standard attributes are still created manually and passed using the > extra_groups parameter. > > The non-standard attributes are: > - fanX_safe > - fanX_output (should be renamed to fanX_target to comply with the standard sysfs ABI) > - fanX_manual (should be renamed to pwmX_enable to comply with the standard sysfs ABI) > > This changes are not exactly trivial and should be tested on real hardware. You should > only attempt this if you have access to a compatible device for testing. > Exactly. Other drivers, such as the asus-ec-sensors driver, use the same approach. Thanks, Guenter ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 22:28 ` Guenter Roeck @ 2026-07-11 2:51 ` Shih-Yuan Lee (FourDollars) 0 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee (FourDollars) @ 2026-07-11 2:51 UTC (permalink / raw) To: Guenter Roeck, Armin Wolf; +Cc: Henrik Rydberg, linux-hwmon, linux-kernel Hi Guenter, Armin, Thank you for the detailed feedback and suggestions. I do have access to a real Intel-based MacBook8,1 for hardware testing. I agree that converting the driver properly and renaming the non-standard attributes (fanX_output -> fanX_target, fanX_manual -> pwmX_enable) to match the HWMON ABI is the right way forward. I will start working on a full refactoring of the fan and temperature attributes using dynamic hwmon_channel_info allocation, and test the resulting module on the hardware. I will send a v2 patch series once the implementation is complete and tested. Best regards, Shih-Yuan Lee On Sat, Jul 11, 2026 at 6:28 AM Guenter Roeck <linux@roeck-us.net> wrote: > > On 7/10/26 14:10, Armin Wolf wrote: > > Am 10.07.26 um 16:01 schrieb Guenter Roeck: > > > >> On 7/10/26 05:32, Shih-Yuan Lee wrote: > >>> The legacy hwmon_device_register() function is deprecated and triggers a > >>> warning in dmesg during driver initialization: > >>> > >>> [ 24.706091] applesmc: key=620 fan=0 temp=37 index=36 acc=0 lux=2 kbd=0 > >>> [ 24.706270] applesmc applesmc.768: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info(). > >>> > >>> To silence this warning, convert the driver to use the modern > >>> hwmon_device_register_with_info() API. > >>> > >>> Because the driver creates all its sysfs attributes dynamically on the platform > >>> device, we define a minimal struct hwmon_chip_info with a single temperature > >>> channel and implement a visibility callback that returns 0 (hidden) for it. > >>> This satisfies the new API requirements while keeping all existing sysfs paths > >>> and attributes completely unchanged, ensuring backwards compatibility. > >>> > >> > >> Novel, but that would completely defeat the purpose of the new API. > >> NACK. > >> > >> Guenter > > > > Agree, this deprecation warning should not be silenced by using cheap tricks. > > > > Lee, i think instead of abusing the hwmon_device_register_with_info() to emulate the > > behavior of the old API, i suggest that you use the new API to replace these two calls > > to applesmc_create_nodes(): > > > > ret = applesmc_create_nodes(fan_group, smcreg.fan_count); > > if (ret) > > goto out_info; > > > > ret = applesmc_create_nodes(temp_group, smcreg.index_count); > > if (ret) > > oto out_fans; > > > > Basically, you need to dynamically allocate a struct hwmon_channel_info each for the fan > > and temperature sensors. Then you basically use the new API to create the standard attributes > > for you, while the non-standard attributes are still created manually and passed using the > > extra_groups parameter. > > > > The non-standard attributes are: > > - fanX_safe > > - fanX_output (should be renamed to fanX_target to comply with the standard sysfs ABI) > > - fanX_manual (should be renamed to pwmX_enable to comply with the standard sysfs ABI) > > > > This changes are not exactly trivial and should be tested on real hardware. You should > > only attempt this if you have access to a compatible device for testing. > > > > Exactly. Other drivers, such as the asus-ec-sensors driver, use the same approach. > > Thanks, > Guenter > ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2026-07-10 12:32 ` [PATCH 1/1] " Shih-Yuan Lee @ 2026-07-11 3:36 ` Shih-Yuan Lee 2026-07-11 3:37 ` [PATCH v2 1/1] " Shih-Yuan Lee 2026-07-11 5:39 ` [PATCH v3 0/1] " Shih-Yuan Lee ` (3 subsequent siblings) 5 siblings, 1 reply; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 3:36 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee Hi Guenter, Armin, and Henrik, This is v2 of the patch series converting the applesmc driver from using the deprecated hwmon_device_register() function to the modern hwmon_device_register_with_info() API. Changes in v2: - Abandon the minimal dummy registration approach in v1. - Fully convert the driver to modern HWMON ABI standards by dynamically allocating temp and fan channels at initialization. - Rename legacy non-standard attributes to comply with the standard HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically register the remaining non-standard fanX_safe attributes under HWMON class directory via extra_groups. - Load and cache fan positions in smcreg.fan_positions to support returning labels by reference in .read_string. - Clean up unused static attribute groups and show/store callback functions to avoid unused symbol compiler warnings. - Verified compilation and successfully tested on real MacBook hardware. We appreciate your review and comments on this proper refactoring. Shih-Yuan Lee (1): hwmon: (applesmc) Convert to hwmon_device_register_with_info drivers/hwmon/applesmc.c | 458 ++++++++++++++++++++++++++------------- 1 file changed, 309 insertions(+), 149 deletions(-) -- 2.39.5 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-11 3:36 ` [PATCH v2 0/1] " Shih-Yuan Lee @ 2026-07-11 3:37 ` Shih-Yuan Lee 0 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 3:37 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers a warning in dmesg during driver initialization. Convert the driver to use the modern hwmon_device_register_with_info() API. This migration moves the registration of standard sensor attributes to the HWMON core: - Standard temperature attributes (temp*_input, temp*_label) are mapped to hwmon_temp channels. - Standard fan attributes (fan*_input, fan*_label, fan*_min, fan*_max) are mapped to hwmon_fan channels. - Non-standard fan*_output is renamed to standard fan*_target and mapped to HWMON_F_TARGET. - Non-standard fan*_manual is renamed to standard pwm*_enable and mapped to HWMON_PWM_ENABLE. - The remaining non-standard fan*_safe attribute is dynamically created and registered via extra_groups. To support the read_string callback for fan labels, load and cache the fan position names in smcreg.fan_positions during register initialization. Finally, clean up the now-unused static attributes (temp_group, fan_group) and their corresponding show/store callback functions to prevent compiler warnings about unused static symbols. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 458 ++++++++++++++++++++++++++------------- 1 file changed, 309 insertions(+), 149 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..3470912649c4 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -566,7 +567,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +598,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, "Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; @@ -808,33 +819,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -854,99 +838,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1094,22 +985,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1307,8 +1183,217 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> channel) & 0x01; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + + if (val != 0 && val != 1) + return -EINVAL; + mutex_lock(&smcreg.mutex); + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = applesmc_write_key(FANS_MANUAL, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + u32 *temp_config; + u32 *fan_config; + u32 *pwm_config; + struct hwmon_channel_info *info_temp; + struct hwmon_channel_info *info_fan; + struct hwmon_channel_info *info_pwm; + const struct hwmon_channel_info **info; + struct hwmon_chip_info *chip; + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1343,17 +1428,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + temp_config = devm_kcalloc(&pdev->dev, smcreg.index_count + 1, + sizeof(*temp_config), GFP_KERNEL); + fan_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_config), GFP_KERNEL); + pwm_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*pwm_config), GFP_KERNEL); + if (!temp_config || !fan_config || !pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + pwm_config[i] = HWMON_PWM_ENABLE; + } + fan_config[smcreg.fan_count] = 0; + pwm_config[smcreg.fan_count] = 0; + + info_temp = devm_kzalloc(&pdev->dev, sizeof(*info_temp), GFP_KERNEL); + info_fan = devm_kzalloc(&pdev->dev, sizeof(*info_fan), GFP_KERNEL); + info_pwm = devm_kzalloc(&pdev->dev, sizeof(*info_pwm), GFP_KERNEL); + if (!info_temp || !info_fan || !info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + info_temp->type = hwmon_temp; + info_temp->config = temp_config; + + info_fan->type = hwmon_fan; + info_fan->config = fan_config; + + info_pwm->type = hwmon_pwm; + info_pwm->config = pwm_config; + + info = devm_kcalloc(&pdev->dev, 4, sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto out_info; + } + + info[0] = info_temp; + info[1] = info_fan; + info[2] = info_pwm; + info[3] = NULL; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + ret = -ENOMEM; + goto out_info; + } + + chip->ops = &applesmc_hwmon_ops; + chip->info = info; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = devm_kcalloc(&pdev->dev, smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1363,7 +1528,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1377,10 +1543,6 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: applesmc_destroy_nodes(info_group); out_smcreg: @@ -1402,8 +1564,6 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v3 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2026-07-10 12:32 ` [PATCH 1/1] " Shih-Yuan Lee 2026-07-11 3:36 ` [PATCH v2 0/1] " Shih-Yuan Lee @ 2026-07-11 5:39 ` Shih-Yuan Lee 2026-07-11 5:39 ` [PATCH v3 1/1] " Shih-Yuan Lee 2026-07-11 6:06 ` [PATCH v4 0/1] " Shih-Yuan Lee ` (2 subsequent siblings) 5 siblings, 1 reply; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 5:39 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee Hi Guenter, Armin, and Henrik, This is v3 of the patch series converting the applesmc driver from using the deprecated hwmon_device_register() function to the modern hwmon_device_register_with_info() API. Changes in v3: - Fix a recursive mutex deadlock in applesmc_hwmon_write() when writing to pwmX_enable by using applesmc_get_entry_by_key() to resolve the entry locklessly and then calling the underlying raw read_smc/write_smc functions directly under the mutex lock. - Fix a fallback fan label truncation bug by pre-padding the generated fallback string with four spaces so that the "+ 4" pointer arithmetic offset yields the correct label "Fan %d". Changes in v2: - Abandon the minimal dummy registration approach in v1. - Fully convert the driver to modern HWMON ABI standards by dynamically allocating temp and fan channels at initialization. - Rename legacy non-standard attributes to comply with the standard HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically register the remaining non-standard fanX_safe attributes under HWMON class directory via extra_groups. - Load and cache fan positions in smcreg.fan_positions to support returning labels by reference in .read_string. - Clean up unused static attribute groups and show/store callback functions to avoid unused symbol compiler warnings. - Verified compilation and successfully tested on real MacBook hardware. We appreciate your review and comments on this refactoring. Shih-Yuan Lee (1): hwmon: (applesmc) Convert to hwmon_device_register_with_info drivers/hwmon/applesmc.c | 464 ++++++++++++++++++++++++++------------- 1 file changed, 315 insertions(+), 149 deletions(-) -- 2.39.5 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-11 5:39 ` [PATCH v3 0/1] " Shih-Yuan Lee @ 2026-07-11 5:39 ` Shih-Yuan Lee 0 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 5:39 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers a warning in dmesg during driver initialization. Convert the driver to use the modern hwmon_device_register_with_info() API. This migration moves the registration of standard sensor attributes to the HWMON core: - Standard temperature attributes (temp*_input, temp*_label) are mapped to hwmon_temp channels. - Standard fan attributes (fan*_input, fan*_label, fan*_min, fan*_max) are mapped to hwmon_fan channels. - Non-standard fan*_output is renamed to standard fan*_target and mapped to HWMON_F_TARGET. - Non-standard fan*_manual is renamed to standard pwm*_enable and mapped to HWMON_PWM_ENABLE. - The remaining non-standard fan*_safe attribute is dynamically created and registered via extra_groups. To support the read_string callback for fan labels, load and cache the fan position names in smcreg.fan_positions during register initialization. Finally, clean up the now-unused static attributes (temp_group, fan_group) and their corresponding show/store callback functions to prevent compiler warnings about unused static symbols. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 464 ++++++++++++++++++++++++++------------- 1 file changed, 315 insertions(+), 149 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..8281ba2ae9cf 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -566,7 +567,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +598,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; @@ -808,33 +819,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -854,99 +838,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1094,22 +985,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1307,8 +1183,223 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> channel) & 0x01; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 0 && val != 1) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + u32 *temp_config; + u32 *fan_config; + u32 *pwm_config; + struct hwmon_channel_info *info_temp; + struct hwmon_channel_info *info_fan; + struct hwmon_channel_info *info_pwm; + const struct hwmon_channel_info **info; + struct hwmon_chip_info *chip; + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1343,17 +1434,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + temp_config = devm_kcalloc(&pdev->dev, smcreg.index_count + 1, + sizeof(*temp_config), GFP_KERNEL); + fan_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_config), GFP_KERNEL); + pwm_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*pwm_config), GFP_KERNEL); + if (!temp_config || !fan_config || !pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + pwm_config[i] = HWMON_PWM_ENABLE; + } + fan_config[smcreg.fan_count] = 0; + pwm_config[smcreg.fan_count] = 0; + + info_temp = devm_kzalloc(&pdev->dev, sizeof(*info_temp), GFP_KERNEL); + info_fan = devm_kzalloc(&pdev->dev, sizeof(*info_fan), GFP_KERNEL); + info_pwm = devm_kzalloc(&pdev->dev, sizeof(*info_pwm), GFP_KERNEL); + if (!info_temp || !info_fan || !info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + info_temp->type = hwmon_temp; + info_temp->config = temp_config; + + info_fan->type = hwmon_fan; + info_fan->config = fan_config; + + info_pwm->type = hwmon_pwm; + info_pwm->config = pwm_config; + + info = devm_kcalloc(&pdev->dev, 4, sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto out_info; + } + + info[0] = info_temp; + info[1] = info_fan; + info[2] = info_pwm; + info[3] = NULL; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + ret = -ENOMEM; + goto out_info; + } + + chip->ops = &applesmc_hwmon_ops; + chip->info = info; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = devm_kcalloc(&pdev->dev, smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1363,7 +1534,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1377,10 +1549,6 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: applesmc_destroy_nodes(info_group); out_smcreg: @@ -1402,8 +1570,6 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee ` (2 preceding siblings ...) 2026-07-11 5:39 ` [PATCH v3 0/1] " Shih-Yuan Lee @ 2026-07-11 6:06 ` Shih-Yuan Lee 2026-07-11 6:06 ` [PATCH v4 1/1] " Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee 5 siblings, 1 reply; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 6:06 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee Hi Guenter, Armin, and Henrik, This is v4 of the patch series converting the applesmc driver from using the deprecated hwmon_device_register() function to the modern hwmon_device_register_with_info() API. Changes in v4: - Convert to devres-managed devm_hwmon_device_register_with_info() to fix a lifetime Use-After-Free mismatch when the driver is unbound via sysfs (releasing devres-managed attributes memory while keeping the hwmon sysfs files active). - Declare hwmon_dev locally in applesmc_init() and remove it from the global static variables, and clean up the redundant manual hwmon_device_unregister() call in applesmc_exit(). Changes in v3: - Fix a recursive mutex deadlock in applesmc_hwmon_write() when writing to pwmX_enable by using applesmc_get_entry_by_key() to resolve the entry locklessly and then calling the underlying raw read_smc/write_smc functions directly under the mutex lock. - Fix a fallback fan label truncation bug by pre-padding the generated fallback string with four spaces so that the "+ 4" pointer arithmetic offset yields the correct label "Fan %d". Changes in v2: - Abandon the minimal dummy registration approach in v1. - Fully convert the driver to modern HWMON ABI standards by dynamically allocating temp and fan channels at initialization. - Rename legacy non-standard attributes to comply with the standard HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically register the remaining non-standard fanX_safe attributes under HWMON class directory via extra_groups. - Load and cache fan positions in smcreg.fan_positions to support returning labels by reference in .read_string. - Clean up unused static attribute groups and show/store callback functions to avoid unused symbol compiler warnings. - Verified compilation and successfully tested on real MacBook hardware. We appreciate your review and comments on this refactoring. Shih-Yuan Lee (1): hwmon: (applesmc) Convert to hwmon_device_register_with_info drivers/hwmon/applesmc.c | 467 ++++++++++++++++++++++++++------------- 1 file changed, 316 insertions(+), 151 deletions(-) -- 2.39.5 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 1/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-11 6:06 ` [PATCH v4 0/1] " Shih-Yuan Lee @ 2026-07-11 6:06 ` Shih-Yuan Lee 0 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 6:06 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers a warning in dmesg during driver initialization. Convert the driver to use the modern hwmon_device_register_with_info() API. This migration moves the registration of standard sensor attributes to the HWMON core: - Standard temperature attributes (temp*_input, temp*_label) are mapped to hwmon_temp channels. - Standard fan attributes (fan*_input, fan*_label, fan*_min, fan*_max) are mapped to hwmon_fan channels. - Non-standard fan*_output is renamed to standard fan*_target and mapped to HWMON_F_TARGET. - Non-standard fan*_manual is renamed to standard pwm*_enable and mapped to HWMON_PWM_ENABLE. - The remaining non-standard fan*_safe attribute is dynamically created and registered via extra_groups. To support the read_string callback for fan labels, load and cache the fan position names in smcreg.fan_positions during register initialization. Finally, clean up the now-unused static attributes (temp_group, fan_group) and their corresponding show/store callback functions to prevent compiler warnings about unused static symbols. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 467 ++++++++++++++++++++++++++------------- 1 file changed, 316 insertions(+), 151 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..a93b1f91a35d 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -143,7 +144,6 @@ static s16 rest_x; static s16 rest_y; static u8 backlight_state[2]; -static struct device *hwmon_dev; static struct input_dev *applesmc_idev; /* @@ -566,7 +566,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +597,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; @@ -808,33 +818,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -854,99 +837,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1094,22 +984,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1307,8 +1182,224 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> channel) & 0x01; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 0 && val != 1) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + u32 *temp_config; + u32 *fan_config; + u32 *pwm_config; + struct hwmon_channel_info *info_temp; + struct hwmon_channel_info *info_fan; + struct hwmon_channel_info *info_pwm; + const struct hwmon_channel_info **info; + struct hwmon_chip_info *chip; + struct device *hwmon_dev; + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1343,17 +1434,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + temp_config = devm_kcalloc(&pdev->dev, smcreg.index_count + 1, + sizeof(*temp_config), GFP_KERNEL); + fan_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_config), GFP_KERNEL); + pwm_config = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*pwm_config), GFP_KERNEL); + if (!temp_config || !fan_config || !pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + pwm_config[i] = HWMON_PWM_ENABLE; + } + fan_config[smcreg.fan_count] = 0; + pwm_config[smcreg.fan_count] = 0; + + info_temp = devm_kzalloc(&pdev->dev, sizeof(*info_temp), GFP_KERNEL); + info_fan = devm_kzalloc(&pdev->dev, sizeof(*info_fan), GFP_KERNEL); + info_pwm = devm_kzalloc(&pdev->dev, sizeof(*info_pwm), GFP_KERNEL); + if (!info_temp || !info_fan || !info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + info_temp->type = hwmon_temp; + info_temp->config = temp_config; + + info_fan->type = hwmon_fan; + info_fan->config = fan_config; + + info_pwm->type = hwmon_pwm; + info_pwm->config = pwm_config; + + info = devm_kcalloc(&pdev->dev, 4, sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto out_info; + } + + info[0] = info_temp; + info[1] = info_fan; + info[2] = info_pwm; + info[3] = NULL; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + ret = -ENOMEM; + goto out_info; + } + + chip->ops = &applesmc_hwmon_ops; + chip->info = info; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = devm_kcalloc(&pdev->dev, smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = devm_kcalloc(&pdev->dev, smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1363,7 +1534,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1377,10 +1549,6 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: applesmc_destroy_nodes(info_group); out_smcreg: @@ -1398,12 +1566,9 @@ static int __init applesmc_init(void) static void __exit applesmc_exit(void) { - hwmon_device_unregister(hwmon_dev); applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v5 0/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee ` (3 preceding siblings ...) 2026-07-11 6:06 ` [PATCH v4 0/1] " Shih-Yuan Lee @ 2026-07-11 7:57 ` Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee ` (2 more replies) 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee 5 siblings, 3 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 7:57 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee Hi Guenter, Armin, and Henrik, This is v5 of the patch series converting the applesmc driver from using the deprecated hwmon_device_register() function to the modern hwmon_device_register_with_info() API. Changes in v5: - Split the refactoring into a 3-patch logical series: - Patch 1: Cache fan positions during register initialization (preparatory). - Patch 2: Fix a pre-existing concurrency bug in lockless cache validation by introducing proper memory barriers (smp_load_acquire and smp_store_release) with checkpatch-compliant comments. - Patch 3: Convert the driver to the modern HWMON API. - Address Sashiko AI's findings regarding the lifetime mismatch and teardown race condition: - Revert the driver from using devm allocations and managed hwmon registration. - Manually register with unmanaged hwmon_device_register_with_info() and manually free structures via applesmc_free_hwmon(). - Ensure unregistration order is safe: call hwmon_device_unregister() at the very beginning of applesmc_exit() to close the sysfs nodes prior to freeing the register structures and dynamic memory. Changes in v4: - Convert to devres-managed devm_hwmon_device_register_with_info() to fix a lifetime Use-After-Free mismatch when the driver is unbound via sysfs (releasing devres-managed attributes memory while keeping the hwmon sysfs files active). - Declare hwmon_dev locally in applesmc_init() and remove it from the global static variables, and clean up the redundant manual hwmon_device_unregister() call in applesmc_exit(). Changes in v3: - Fix a recursive mutex deadlock in applesmc_hwmon_write() when writing to pwmX_enable by using applesmc_get_entry_by_key() to resolve the entry locklessly and then calling the underlying raw SMC read/write calls. - Fix a fallback fan label truncation bug by pre-padding the generated fallback string with four spaces so that the "+ 4" pointer arithmetic offset yields the correct label "Fan %d". Changes in v2: - Abandon the minimal dummy registration approach in v1. - Fully convert the driver to modern HWMON ABI standards by dynamically allocating temp and fan channels at initialization. - Rename legacy non-standard attributes to comply with the standard HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically register the remaining non-standard fanX_safe attributes under HWMON class directory via extra_groups. - Load and cache fan positions in smcreg.fan_positions to support returning labels by reference in .read_string. - Clean up unused static attribute groups and show/store callback functions to avoid unused symbol compiler warnings. - Verified compilation and successfully tested on real MacBook hardware. We appreciate your review and comments on this refactoring. Shih-Yuan Lee (3): hwmon: (applesmc) Cache fan positions during register initialization hwmon: (applesmc) Fix lockless cache validation data race hwmon: (applesmc) Convert to hwmon_device_register_with_info drivers/hwmon/applesmc.c | 490 +++++++++++++++++++++++++++------------ 1 file changed, 338 insertions(+), 152 deletions(-) -- 2.39.5 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v5 1/3] hwmon: (applesmc) Cache fan positions during register initialization 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee @ 2026-07-11 7:57 ` Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 7:57 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee To support the read_string callback for fan labels in the modern HWMON API, load and cache the fan position names in smcreg.fan_positions during register initialization. Pre-pad fallback labels with four spaces to match the "+ 4" pointer arithmetic offset used by all fan labels in the read_string callback. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..9b2d9ecb20c0 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -566,7 +567,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +598,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v5 2/3] hwmon: (applesmc) Fix lockless cache validation data race 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee @ 2026-07-11 7:57 ` Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 7:57 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee In applesmc_get_entry_by_index(), the cache->valid flag is checked locklessly, but setting it to true lacks memory barriers. This can lead to a data race (TOCTOU) where another thread sees cache->valid as true before the actual cache contents (cache->key, cache->len, cache->type, etc.) are fully committed and visible to that CPU, potentially causing it to read uninitialized data and send incorrect keys to the Apple SMC hardware. Introduce memory barriers (smp_load_acquire and smp_store_release) with explanatory comments to ensure cache synchronization is thread-safe and fully visible across all CPUs. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 9b2d9ecb20c0..317135fc4b73 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -33,6 +33,7 @@ #include <linux/workqueue.h> #include <linux/err.h> #include <linux/bits.h> +#include <asm/barrier.h> /* data port used by Apple SMC */ #define APPLESMC_DATA_PORT 0x300 @@ -373,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) __be32 be; int ret = 0; - if (cache->valid) + /* Pairs with smp_store_release() to ensure cache contents are visible */ + if (smp_load_acquire(&cache->valid)) return cache; mutex_lock(&smcreg.mutex); @@ -392,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) cache->len = info[0]; memcpy(cache->type, &info[1], 4); cache->flags = info[5]; - cache->valid = true; + /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ + smp_store_release(&cache->valid, true); out: mutex_unlock(&smcreg.mutex); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v5 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee @ 2026-07-11 7:57 ` Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 7:57 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers warnings in dmesg. Convert the driver to the modern hwmon_device_register_with_info() API. This conversion does the following: - Dynamically allocates standard HWMON temp, fan, and pwm channels. - Configures HWMON ops callbacks (.is_visible, .read, .read_string, .write). - Standardizes attribute naming to match the HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically registers non-standard fanX_safe attributes under the HWMON class directory via extra_groups. - Cleans up legacy sysfs nodes, groups, and unused show/store static functions to avoid unused symbol compiler warnings. - Avoids recursive mutex deadlocks when writing to pwmX_enable by locklessly resolving the entry and invoking the underlying raw SMC read/write calls. - Avoids UAF race condition on module exit by using unmanaged registration and explicitly calling hwmon_device_unregister() as the first step of applesmc_exit(), guaranteeing that HWMON nodes are destroyed before static structures are freed. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 470 ++++++++++++++++++++++++++------------- 1 file changed, 321 insertions(+), 149 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 317135fc4b73..bec9e9e98896 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -145,8 +145,8 @@ static s16 rest_x; static s16 rest_y; static u8 backlight_state[2]; -static struct device *hwmon_dev; static struct input_dev *applesmc_idev; +static struct device *hwmon_dev; /* * Last index written to key_at_index sysfs file, and value to use for all other @@ -822,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -868,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1108,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1321,8 +1186,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static u32 *applesmc_temp_config; +static u32 *applesmc_fan_config; +static u32 *applesmc_pwm_config; +static struct hwmon_channel_info *applesmc_info_temp; +static struct hwmon_channel_info *applesmc_info_fan; +static struct hwmon_channel_info *applesmc_info_pwm; +static const struct hwmon_channel_info **applesmc_info_arr; +static struct hwmon_chip_info *applesmc_chip; + +static void applesmc_free_hwmon(void) +{ + kfree(applesmc_temp_config); + kfree(applesmc_fan_config); + kfree(applesmc_pwm_config); + kfree(applesmc_info_temp); + kfree(applesmc_info_fan); + kfree(applesmc_info_pwm); + kfree(applesmc_info_arr); + kfree(applesmc_chip); + kfree(fan_safe_attrs); + kfree(fan_safe_attr_list); +} + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> channel) & 0x01; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 0 && val != 1) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1357,17 +1452,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + applesmc_temp_config = kcalloc(smcreg.index_count + 1, + sizeof(*applesmc_temp_config), GFP_KERNEL); + applesmc_fan_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_fan_config), GFP_KERNEL); + applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_pwm_config), GFP_KERNEL); + if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + applesmc_temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + applesmc_pwm_config[i] = HWMON_PWM_ENABLE; + } + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + + applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); + applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); + applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_temp->type = hwmon_temp; + applesmc_info_temp->config = applesmc_temp_config; + + applesmc_info_fan->type = hwmon_fan; + applesmc_info_fan->config = applesmc_fan_config; + + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + + applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_arr[0] = applesmc_info_temp; + applesmc_info_arr[1] = applesmc_info_fan; + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + + applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_chip->ops = &applesmc_hwmon_ops; + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = kcalloc(smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1377,7 +1552,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + applesmc_chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1391,11 +1567,8 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: + applesmc_free_hwmon(); applesmc_destroy_nodes(info_group); out_smcreg: applesmc_destroy_smcreg(); @@ -1416,13 +1589,12 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); platform_driver_unregister(&applesmc_driver); release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); + applesmc_free_hwmon(); } module_init(applesmc_init); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 0/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee ` (4 preceding siblings ...) 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee @ 2026-07-11 9:33 ` Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee ` (2 more replies) 5 siblings, 3 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 9:33 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee Hi Guenter, Armin, and Henrik, This is v6 of the patch series converting the applesmc driver from using the deprecated hwmon_device_register() function to the modern hwmon_device_register_with_info() API. Changes in v6: - Fix HWMON ABI violation for pwmX_enable by mapping manual mode to 1 and automatic mode to 2 (instead of 1 and 0), and rejecting invalid values. Changes in v5: - Split the refactoring into a 3-patch logical series: - Patch 1: Cache fan positions during register initialization (preparatory). - Patch 2: Fix a pre-existing concurrency bug in lockless cache validation by introducing proper memory barriers (smp_load_acquire and smp_store_release) with checkpatch-compliant comments. - Patch 3: Convert the driver to the modern HWMON API. - Address Sashiko AI's findings regarding the lifetime mismatch and teardown race condition: - Revert the driver from using devm allocations and managed hwmon registration. - Manually register with unmanaged hwmon_device_register_with_info() and manually free structures via applesmc_free_hwmon(). - Ensure unregistration order is safe: call hwmon_device_unregister() at the very beginning of applesmc_exit() to close the sysfs nodes prior to freeing the register structures and dynamic memory. Changes in v4: - Convert to devres-managed devm_hwmon_device_register_with_info() to fix a lifetime Use-After-Free mismatch when the driver is unbound via sysfs (releasing devres-managed attributes memory while keeping the hwmon sysfs files active). - Declare hwmon_dev locally in applesmc_init() and remove it from the global static variables, and clean up the redundant manual hwmon_device_unregister() call in applesmc_exit(). Changes in v3: - Fix a recursive mutex deadlock in applesmc_hwmon_write() when writing to pwmX_enable by using applesmc_get_entry_by_key() to resolve the entry locklessly and then calling the underlying raw SMC read/write calls. - Fix a fallback fan label truncation bug by pre-padding the generated fallback string with four spaces so that the "+ 4" pointer arithmetic offset yields the correct label "Fan %d". Changes in v2: - Abandon the minimal dummy registration approach in v1. - Fully convert the driver to modern HWMON ABI standards by dynamically allocating temp and fan channels at initialization. - Rename legacy non-standard attributes to comply with the standard HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically register the remaining non-standard fanX_safe attributes under HWMON class directory via extra_groups. - Load and cache fan positions in smcreg.fan_positions to support returning labels by reference in .read_string. - Clean up unused static attribute groups and show/store callback functions to avoid unused symbol compiler warnings. - Verified compilation and successfully tested on real MacBook hardware. We appreciate your review and comments on this refactoring. Shih-Yuan Lee (3): hwmon: (applesmc) Cache fan positions during register initialization hwmon: (applesmc) Fix lockless cache validation data race hwmon: (applesmc) Convert to hwmon_device_register_with_info drivers/hwmon/applesmc.c | 490 +++++++++++++++++++++++++++------------ 1 file changed, 338 insertions(+), 152 deletions(-) -- 2.39.5 ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6 1/3] hwmon: (applesmc) Cache fan positions during register initialization 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee @ 2026-07-11 9:33 ` Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 9:33 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee To support the read_string callback for fan labels in the modern HWMON API, load and cache the fan position names in smcreg.fan_positions during register initialization. Pre-pad fallback labels with four spaces to match the "+ 4" pointer arithmetic offset used by all fan labels in the read_string callback. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 90a14a7f2c4c..9b2d9ecb20c0 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -133,6 +133,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -566,7 +567,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +598,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 2/3] hwmon: (applesmc) Fix lockless cache validation data race 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee @ 2026-07-11 9:33 ` Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 9:33 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee In applesmc_get_entry_by_index(), the cache->valid flag is checked locklessly, but setting it to true lacks memory barriers. This can lead to a data race (TOCTOU) where another thread sees cache->valid as true before the actual cache contents (cache->key, cache->len, cache->type, etc.) are fully committed and visible to that CPU, potentially causing it to read uninitialized data and send incorrect keys to the Apple SMC hardware. Introduce memory barriers (smp_load_acquire and smp_store_release) with explanatory comments to ensure cache synchronization is thread-safe and fully visible across all CPUs. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 9b2d9ecb20c0..317135fc4b73 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -33,6 +33,7 @@ #include <linux/workqueue.h> #include <linux/err.h> #include <linux/bits.h> +#include <asm/barrier.h> /* data port used by Apple SMC */ #define APPLESMC_DATA_PORT 0x300 @@ -373,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) __be32 be; int ret = 0; - if (cache->valid) + /* Pairs with smp_store_release() to ensure cache contents are visible */ + if (smp_load_acquire(&cache->valid)) return cache; mutex_lock(&smcreg.mutex); @@ -392,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) cache->len = info[0]; memcpy(cache->type, &info[1], 4); cache->flags = info[5]; - cache->valid = true; + /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ + smp_store_release(&cache->valid, true); out: mutex_unlock(&smcreg.mutex); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v6 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee @ 2026-07-11 9:33 ` Shih-Yuan Lee 2 siblings, 0 replies; 20+ messages in thread From: Shih-Yuan Lee @ 2026-07-11 9:33 UTC (permalink / raw) To: Henrik Rydberg, Guenter Roeck Cc: Armin Wolf, linux-hwmon, linux-kernel, Shih-Yuan Lee The legacy hwmon_device_register() function is deprecated and triggers warnings in dmesg. Convert the driver to the modern hwmon_device_register_with_info() API. This conversion does the following: - Dynamically allocates standard HWMON temp, fan, and pwm channels. - Configures HWMON ops callbacks (.is_visible, .read, .read_string, .write). - Standardizes attribute naming to match the HWMON ABI: - fanX_output -> fanX_target (HWMON_F_TARGET) - fanX_manual -> pwmX_enable (HWMON_PWM_ENABLE) - Dynamically registers non-standard fanX_safe attributes under the HWMON class directory via extra_groups. - Cleans up legacy sysfs nodes, groups, and unused show/store static functions to avoid unused symbol compiler warnings. - Avoids recursive mutex deadlocks when writing to pwmX_enable by locklessly resolving the entry and invoking the underlying raw SMC read/write calls. - Avoids UAF race condition on module exit by using unmanaged registration and explicitly calling hwmon_device_unregister() as the first step of applesmc_exit(), guaranteeing that HWMON nodes are destroyed before static structures are freed. Signed-off-by: Shih-Yuan Lee <fourdollars@debian.org> --- drivers/hwmon/applesmc.c | 470 ++++++++++++++++++++++++++------------- 1 file changed, 321 insertions(+), 149 deletions(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 317135fc4b73..ca56bd8b170e 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -145,8 +145,8 @@ static s16 rest_x; static s16 rest_y; static u8 backlight_state[2]; -static struct device *hwmon_dev; static struct input_dev *applesmc_idev; +static struct device *hwmon_dev; /* * Last index written to key_at_index sysfs file, and value to use for all other @@ -822,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -868,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1108,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1321,8 +1186,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static u32 *applesmc_temp_config; +static u32 *applesmc_fan_config; +static u32 *applesmc_pwm_config; +static struct hwmon_channel_info *applesmc_info_temp; +static struct hwmon_channel_info *applesmc_info_fan; +static struct hwmon_channel_info *applesmc_info_pwm; +static const struct hwmon_channel_info **applesmc_info_arr; +static struct hwmon_chip_info *applesmc_chip; + +static void applesmc_free_hwmon(void) +{ + kfree(applesmc_temp_config); + kfree(applesmc_fan_config); + kfree(applesmc_pwm_config); + kfree(applesmc_info_temp); + kfree(applesmc_info_fan); + kfree(applesmc_info_pwm); + kfree(applesmc_info_arr); + kfree(applesmc_chip); + kfree(fan_safe_attrs); + kfree(fan_safe_attr_list); +} + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 1 && val != 2) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val == 1) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1357,17 +1452,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + applesmc_temp_config = kcalloc(smcreg.index_count + 1, + sizeof(*applesmc_temp_config), GFP_KERNEL); + applesmc_fan_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_fan_config), GFP_KERNEL); + applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_pwm_config), GFP_KERNEL); + if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + applesmc_temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + applesmc_pwm_config[i] = HWMON_PWM_ENABLE; + } + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + + applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); + applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); + applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_temp->type = hwmon_temp; + applesmc_info_temp->config = applesmc_temp_config; + + applesmc_info_fan->type = hwmon_fan; + applesmc_info_fan->config = applesmc_fan_config; + + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + + applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_arr[0] = applesmc_info_temp; + applesmc_info_arr[1] = applesmc_info_fan; + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + + applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_chip->ops = &applesmc_hwmon_ops; + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = kcalloc(smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1377,7 +1552,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + applesmc_chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1391,11 +1567,8 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: + applesmc_free_hwmon(); applesmc_destroy_nodes(info_group); out_smcreg: applesmc_destroy_smcreg(); @@ -1416,13 +1589,12 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); platform_driver_unregister(&applesmc_driver); release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); + applesmc_free_hwmon(); } module_init(applesmc_init); -- 2.39.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-07-11 9:33 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-10 12:32 [PATCH 0/1] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2026-07-10 12:32 ` [PATCH 1/1] " Shih-Yuan Lee 2026-07-10 14:01 ` Guenter Roeck 2026-07-10 21:10 ` Armin Wolf 2026-07-10 22:28 ` Guenter Roeck 2026-07-11 2:51 ` Shih-Yuan Lee (FourDollars) 2026-07-11 3:36 ` [PATCH v2 0/1] " Shih-Yuan Lee 2026-07-11 3:37 ` [PATCH v2 1/1] " Shih-Yuan Lee 2026-07-11 5:39 ` [PATCH v3 0/1] " Shih-Yuan Lee 2026-07-11 5:39 ` [PATCH v3 1/1] " Shih-Yuan Lee 2026-07-11 6:06 ` [PATCH v4 0/1] " Shih-Yuan Lee 2026-07-11 6:06 ` [PATCH v4 1/1] " Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 0/3] " Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee 2026-07-11 7:57 ` [PATCH v5 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 0/3] " Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 1/3] hwmon: (applesmc) Cache fan positions during register initialization Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 2/3] hwmon: (applesmc) Fix lockless cache validation data race Shih-Yuan Lee 2026-07-11 9:33 ` [PATCH v6 3/3] hwmon: (applesmc) Convert to hwmon_device_register_with_info Shih-Yuan Lee
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox