From 995b085f0e287fcab7075d635580dcf436cc8bab Mon Sep 17 00:00:00 2001 From: Navon John Lukose Date: Mon, 14 Sep 2026 19:12:16 +0530 Subject: platform/x86: ideapad-laptop: Add Yoga Pro 7 14IAH10 Mode key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The F9 "Mode" key emits WMI fn-key event 0x46, which the keymap does not cover, so it reaches userspace as KEY_UNKNOWN and cannot be remapped from there. Under Windows it opens a performance profile chooser. The Fn+Q button already cycles platform_profile through ideapad_check_special_buttons(), so map 0x46 to KEY_PROG4 as a user-assignable key rather than cycling the profile a second way. Signed-off-by: Navon John Lukose Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260914134216.1295740-1-navonjohnlukose@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ideapad-laptop.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 8213524504ee..35c13d98b989 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -1360,6 +1360,8 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 0x45 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, /* Performance toggle also Fn+Q, handled inside ideapad_wmi_notify() */ { KE_KEY, 0x3d | IDEAPAD_WMI_KEY, { KEY_PROG4 } }, + /* Mode, opens a profile chooser; the Fn+Q button cycles the profile */ + { KE_KEY, 0x46 | IDEAPAD_WMI_KEY, { KEY_PROG4 } }, /* shift + prtsc */ { KE_KEY, 0x2d | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } }, { KE_KEY, 0x29 | IDEAPAD_WMI_KEY, { KEY_TOUCHPAD_TOGGLE } }, -- cgit v1.2.3 From 23cc56f6dea6a6935e5e0fe90f0d97a2d9bc6b35 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 5 Sep 2026 00:45:15 +0200 Subject: platform/x86: bitland-mifs-wmi: Detect failed function calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first 16-bit field inside the output data of a Mifs method call contains a return code that specifies if the method call succeeded (0x8000) or failed due to missing support (0xE000). The second 16-bit field should then contain the previously executed function id. Rework the layout of both input and output data to match the format used by the Mifs firmware and check the return code. Tested-by: Martiya Aryan Mehr Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260904224516.8893-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/bitland-mifs-wmi.c | 140 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 73 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c index 3a373184519d..cdb930eb678d 100644 --- a/drivers/platform/x86/bitland-mifs-wmi.c +++ b/drivers/platform/x86/bitland-mifs-wmi.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -39,25 +40,30 @@ #define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" enum bitland_mifs_operation { - WMI_METHOD_GET = 250, - WMI_METHOD_SET = 251, + WMI_METHOD_GET = 0xFA00, + WMI_METHOD_SET = 0xFB00, }; enum bitland_mifs_function { - WMI_FN_SYSTEM_PER_MODE = 8, - WMI_FN_GPU_MODE = 9, - WMI_FN_KBD_TYPE = 10, - WMI_FN_FN_LOCK = 11, - WMI_FN_TP_LOCK = 12, - WMI_FN_FAN_SPEEDS = 13, - WMI_FN_RGB_KB_MODE = 16, - WMI_FN_RGB_KB_COLOR = 17, - WMI_FN_RGB_KB_BRIGHTNESS = 18, - WMI_FN_SYSTEM_AC_TYPE = 19, - WMI_FN_MAX_FAN_SWITCH = 20, - WMI_FN_MAX_FAN_SPEED = 21, - WMI_FN_CPU_THERMOMETER = 22, - WMI_FN_CPU_POWER = 23, + WMI_FN_SYSTEM_PER_MODE = 0x0800, + WMI_FN_GPU_MODE = 0x0900, + WMI_FN_KBD_TYPE = 0x0A00, + WMI_FN_FN_LOCK = 0x0B00, + WMI_FN_TP_LOCK = 0x0C00, + WMI_FN_FAN_SPEEDS = 0x0D00, + WMI_FN_RGB_KB_MODE = 0x1000, + WMI_FN_RGB_KB_COLOR = 0x1100, + WMI_FN_RGB_KB_BRIGHTNESS = 0x1200, + WMI_FN_SYSTEM_AC_TYPE = 0x1300, + WMI_FN_MAX_FAN_SWITCH = 0x1400, + WMI_FN_MAX_FAN_SPEED = 0x1500, + WMI_FN_CPU_THERMOMETER = 0x1600, + WMI_FN_CPU_POWER = 0x1700, +}; + +enum bitland_mifs_return_code { + WMI_RETURN_CODE_SUCCESS = 0x8000, + WMI_RETURN_CODE_UNKNOWN_FUNCTION = 0xE000, }; enum bitland_system_ac_mode { @@ -114,18 +120,14 @@ enum bitland_wmi_device_type { }; struct bitland_mifs_input { - u8 reserved1; - u8 operation; - u8 reserved2; - u8 function; + __le16 operation; + __le16 function; u8 payload[28]; } __packed; struct bitland_mifs_output { - u8 reserved1; - u8 operation; - u8 reserved2; - u8 function; + __le16 return_code; + __le16 function; u8 data[28]; } __packed; @@ -171,15 +173,27 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data, guard(mutex)(&data->lock); - if (!output) - return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf); - - ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output)); + ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, + sizeof(struct bitland_mifs_output)); if (ret) return ret; - memcpy(output, out_buf.data, sizeof(*output)); - kfree(out_buf.data); + struct bitland_mifs_output *result __free(kfree) = out_buf.data; + + switch (le16_to_cpu(result->return_code)) { + case WMI_RETURN_CODE_SUCCESS: + break; + case WMI_RETURN_CODE_UNKNOWN_FUNCTION: + return -EOPNOTSUPP; + default: + return -EIO; + } + + if (result->function != input->function) + return -ENOMSG; + + if (output) + memcpy(output, result, sizeof(*output)); return 0; } @@ -189,10 +203,8 @@ static int laptop_profile_get(struct device *dev, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_GET, - .reserved2 = 0, - .function = WMI_FN_SYSTEM_PER_MODE, + .operation = cpu_to_le16(WMI_METHOD_GET), + .function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE), }; struct bitland_mifs_output result; int ret; @@ -223,8 +235,8 @@ static int laptop_profile_get(struct device *dev, static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data) { struct bitland_mifs_input input = { - .operation = WMI_METHOD_GET, - .function = WMI_FN_SYSTEM_AC_TYPE, + .operation = cpu_to_le16(WMI_METHOD_GET), + .function = cpu_to_le16(WMI_FN_SYSTEM_AC_TYPE), }; struct bitland_mifs_output output; int ret; @@ -248,10 +260,8 @@ static int laptop_profile_set(struct device *dev, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_SET, - .reserved2 = 0, - .function = WMI_FN_SYSTEM_PER_MODE, + .operation = cpu_to_le16(WMI_METHOD_SET), + .function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE), }; int ret; u8 val; @@ -344,22 +354,20 @@ static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_GET, - .reserved2 = 0, + .operation = cpu_to_le16(WMI_METHOD_GET), }; struct bitland_mifs_output res; int ret; switch (type) { case hwmon_temp: - input.function = WMI_FN_CPU_THERMOMETER; + input.function = cpu_to_le16(WMI_FN_CPU_THERMOMETER); ret = bitland_mifs_wmi_call(data, &input, &res); if (!ret) *val = res.data[0] * MILLIDEGREE_PER_DEGREE; return ret; case hwmon_fan: - input.function = WMI_FN_FAN_SPEEDS; + input.function = cpu_to_le16(WMI_FN_FAN_SPEEDS); ret = bitland_mifs_wmi_call(data, &input, &res); if (ret) return ret; @@ -420,10 +428,8 @@ static int laptop_kbd_led_set(struct led_classdev *led_cdev, struct bitland_mifs_wmi_data *data = container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_SET, - .reserved2 = 0, - .function = WMI_FN_RGB_KB_BRIGHTNESS, + .operation = cpu_to_le16(WMI_METHOD_SET), + .function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS), }; input.payload[0] = (u8)value; @@ -436,10 +442,8 @@ static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev) struct bitland_mifs_wmi_data *data = container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_GET, - .reserved2 = 0, - .function = WMI_FN_RGB_KB_BRIGHTNESS, + .operation = cpu_to_le16(WMI_METHOD_GET), + .function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS), }; struct bitland_mifs_output res; int ret; @@ -463,10 +467,8 @@ static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_GET, - .reserved2 = 0, - .function = WMI_FN_GPU_MODE, + .operation = cpu_to_le16(WMI_METHOD_GET), + .function = cpu_to_le16(WMI_FN_GPU_MODE), }; struct bitland_mifs_output res; u8 mode_val; @@ -488,10 +490,8 @@ static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_SET, - .reserved2 = 0, - .function = WMI_FN_GPU_MODE, + .operation = cpu_to_le16(WMI_METHOD_SET), + .function = cpu_to_le16(WMI_FN_GPU_MODE), }; int val; int ret; @@ -521,10 +521,8 @@ static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_GET, - .reserved2 = 0, - .function = WMI_FN_RGB_KB_MODE, + .operation = cpu_to_le16(WMI_METHOD_GET), + .function = cpu_to_le16(WMI_FN_RGB_KB_MODE), }; struct bitland_mifs_output res; u8 mode_val; @@ -546,10 +544,8 @@ static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_SET, - .reserved2 = 0, - .function = WMI_FN_RGB_KB_MODE, + .operation = cpu_to_le16(WMI_METHOD_SET), + .function = cpu_to_le16(WMI_FN_RGB_KB_MODE), }; // the wmi value (0, 1, 2 or 3) int val; @@ -575,10 +571,8 @@ static ssize_t fan_boost_store(struct device *dev, { struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev); struct bitland_mifs_input input = { - .reserved1 = 0, - .operation = WMI_METHOD_SET, - .reserved2 = 0, - .function = WMI_FN_MAX_FAN_SWITCH, + .operation = cpu_to_le16(WMI_METHOD_SET), + .function = cpu_to_le16(WMI_FN_MAX_FAN_SWITCH), }; bool val; int ret; -- cgit v1.2.3 From 4cfc3631d4ca19199f7220c16b4a24204218e588 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 5 Sep 2026 00:45:16 +0200 Subject: platform/x86: bitland-mifs-wmi: Treat WMI_FN_SYSTEM_AC_TYPE as optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some systems only support USB-C but can still enter performance mode if connected to a charger. Such systems usually do not support WMI_FN_SYSTEM_AC_TYPE, so bitland_check_performance_capability() currently always fails on them. Fix this by treating WMI_FN_SYSTEM_AC_TYPE as optional and skip the AC type check if WMI_FN_SYSTEM_AC_TYPE is not supported. Tested-by: Martiya Aryan Mehr Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260904224516.8893-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/bitland-mifs-wmi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c index cdb930eb678d..dd1648b7de77 100644 --- a/drivers/platform/x86/bitland-mifs-wmi.c +++ b/drivers/platform/x86/bitland-mifs-wmi.c @@ -246,7 +246,11 @@ static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *da return -EOPNOTSUPP; ret = bitland_mifs_wmi_call(data, &input, &output); - if (ret) + /* Not all systems support this function, do not perform further checks on them */ + if (ret == -EOPNOTSUPP) + return 0; + + if (ret < 0) return ret; if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE) -- cgit v1.2.3 From 8873641968ed5b3d825315baf627f9aebf592860 Mon Sep 17 00:00:00 2001 From: Pit Henrich Date: Tue, 25 Aug 2026 21:12:10 +0200 Subject: platform/x86: thinkpad-acpi: Add X1 Fold keyboard attachment detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ThinkPad X1 Fold 16 Gen 1 firmware reports whether the keyboard is magnetically attached (on the screen), but thinkpad-acpi does not expose this to userspace. The state can be obtained via ACPI methods: GDST (get device state). Add a read-only keyboard_attached_on_screen sysfs attribute, gated by a DMI match. Cache the state and emit a sysfs notification on TP_HKEY_EV_TABLET_CHANGED (0x60c0) when it changes. Initialize the cache during hotkey setup and refresh it before the resume notification to keep the state consistent across suspend and resume. Signed-off-by: Pit Henrich Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260825191210.55090-1-pithenrich2d@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 86 ++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 1661f070c571..61a63519a91d 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -222,8 +222,10 @@ enum tpacpi_hkey_event_t { TP_HKEY_EV_LID_OPEN = 0x5002, /* laptop lid opened */ TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */ TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */ - TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* X1 Yoga (2016): - * enter/leave tablet mode + TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* posture change event: + * X1 Yoga (2016): enter/leave tablet mode + * X1 Fold 16 Gen 1: keyboard + * attachment state changed */ TP_HKEY_EV_PEN_INSERTED = 0x500b, /* tablet pen inserted */ TP_HKEY_EV_PEN_REMOVED = 0x500c, /* tablet pen removed */ @@ -379,6 +381,7 @@ static struct { u32 kbd_lang:1; u32 trackpoint_doubletap_enable:1; u32 usbc_security_supported:1; + u32 has_keyboard_attached_on_screen:1; bool usbc_security_enabled; struct quirk_entry *quirks; } tp_features; @@ -2893,6 +2896,63 @@ static void hotkey_tablet_mode_notify_change(void) "hotkey_tablet_mode"); } +static bool keyboard_attached_on_screen; +static bool keyboard_attached_on_screen_initialized; + +static int x1_fold_keyboard_attached_on_screen_get(bool *attached) +{ + int state; + + if (!tp_features.has_keyboard_attached_on_screen) + return -ENODEV; + + if (!acpi_evalf(NULL, &state, "\\_SB.DEVD.GDST", "d")) + return -EIO; + + *attached = state != 0; + return 0; +} + +static ssize_t keyboard_attached_on_screen_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + bool attached; + int res; + + res = x1_fold_keyboard_attached_on_screen_get(&attached); + if (res) + return res; + + return sysfs_emit(buf, "%d\n", attached); +} + +static DEVICE_ATTR_RO(keyboard_attached_on_screen); + +static void keyboard_attached_on_screen_notify_change(void) +{ + if (tp_features.has_keyboard_attached_on_screen) + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, + "keyboard_attached_on_screen"); +} + +static bool keyboard_attached_on_screen_update(void) +{ + bool attached; + + if (x1_fold_keyboard_attached_on_screen_get(&attached)) + return false; + + if (keyboard_attached_on_screen_initialized && + keyboard_attached_on_screen == attached) + return false; + + keyboard_attached_on_screen = attached; + keyboard_attached_on_screen_initialized = true; + + return true; +} + /* sysfs wakeup reason (pollable) -------------------------------------- */ static ssize_t hotkey_wakeup_reason_show(struct device *dev, struct device_attribute *attr, @@ -3022,6 +3082,7 @@ static struct attribute *hotkey_attributes[] = { &dev_attr_hotkey_adaptive_all_mask.attr, &dev_attr_hotkey_recommended_mask.attr, &dev_attr_hotkey_tablet_mode.attr, + &dev_attr_keyboard_attached_on_screen.attr, &dev_attr_hotkey_radio_sw.attr, &dev_attr_doubletap_enable.attr, #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -3037,6 +3098,9 @@ static umode_t hotkey_attr_is_visible(struct kobject *kobj, if (attr == &dev_attr_hotkey_tablet_mode.attr) { if (!tp_features.hotkey_tablet) return 0; + } else if (attr == &dev_attr_keyboard_attached_on_screen.attr) { + if (!tp_features.has_keyboard_attached_on_screen) + return 0; } else if (attr == &dev_attr_hotkey_radio_sw.attr) { if (!tp_features.hotkey_wlsw) return 0; @@ -3453,6 +3517,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) } tabletsw_state = hotkey_init_tablet_mode(); + keyboard_attached_on_screen_update(); /* Set up key map */ keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable, @@ -3833,6 +3898,8 @@ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev) case TP_HKEY_EV_TABLET_CHANGED: tpacpi_input_send_tabletsw(); hotkey_tablet_mode_notify_change(); + if (keyboard_attached_on_screen_update()) + keyboard_attached_on_screen_notify_change(); *send_acpi_ev = false; return true; @@ -3989,6 +4056,8 @@ static void hotkey_resume(void) tpacpi_send_radiosw_update(); tpacpi_input_send_tabletsw(); hotkey_tablet_mode_notify_change(); + keyboard_attached_on_screen_update(); + keyboard_attached_on_screen_notify_change(); hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); @@ -4287,6 +4356,17 @@ static const struct dmi_system_id fwbug_list[] __initconst = { {} }; +static const struct dmi_system_id keyboard_attached_on_screen_list[] __initconst = { + { + .ident = "ThinkPad X1 Fold 16 Gen 1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Fold 16 Gen 1"), + }, + }, + {} +}; + static const struct pci_device_id fwbug_cards_ids[] __initconst = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24F3) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24FD) }, @@ -12360,6 +12440,8 @@ static int __init thinkpad_acpi_module_init(void) dmi_id = dmi_first_match(fwbug_list); if (dmi_id) tp_features.quirks = dmi_id->driver_data; + tp_features.has_keyboard_attached_on_screen = + dmi_check_system(keyboard_attached_on_screen_list); /* Device initialization */ tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, PLATFORM_DEVID_NONE, -- cgit v1.2.3 From d6479c25eceff5bca10c2fadb2088dcae1f53561 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 4 Aug 2026 21:03:36 -0700 Subject: platform/x86: thinkpad_acpi: Use input_device_enabled() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the new helper. Inspecting input device's 'users' member needs to be done under device's mutex, so add appropriate invocations. Signed-off-by: Andrzej Pietrasiewicz Acked-by: Henrique de Moraes Holschuh Link: https://lore.kernel.org/r/20200608112211.12125-7-andrzej.p@collabora.com Signed-off-by: Dmitry Torokhov Reviewed-by: Mark Pearson Link: https://patch.msgid.link/anK1j7hbMyFHGBUS@google.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 61a63519a91d..d456add19b30 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -2555,9 +2555,10 @@ static void hotkey_poll_setup(const bool may_warn) lockdep_assert_held(&hotkey_mutex); + mutex_lock(&tpacpi_inputdev->mutex); if (hotkey_poll_freq > 0 && (poll_driver_mask || - (poll_user_mask && tpacpi_inputdev->users > 0))) { + (poll_user_mask && input_device_enabled(tpacpi_inputdev)))) { if (!tpacpi_hotkey_task) { tpacpi_hotkey_task = kthread_run(hotkey_kthread, NULL, TPACPI_NVRAM_KTHREAD_NAME); @@ -2574,6 +2575,7 @@ static void hotkey_poll_setup(const bool may_warn) poll_user_mask, poll_driver_mask); } } + mutex_unlock(&tpacpi_inputdev->mutex); } static void hotkey_poll_setup_safe(const bool may_warn) -- cgit v1.2.3 From a3371fac5dd51518de4edcdcc3c7284a5c5a6607 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Aug 2026 23:19:22 -0700 Subject: platform/x86: thinkpad_acpi: convert mutex_lock() to guard(mutex) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert straightforward mutex_lock() and mutex_unlock() usages for hotkey_mutex, tpacpi_inputdev_send_mutex, kbdlight_mutex, lcdshadow_dev lock, and dytc_mutex to guard(mutex) and scoped_guard(mutex) helpers from linux/cleanup.h. This improves code readability and ensures that mutexes are automatically released when exiting their respective scopes. Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Dmitry Torokhov Tested-by: Ali Ahmet Memiş Reviewed-by: Mark Pearson Tested-by: Mark Pearson Link: https://patch.msgid.link/20260806061925.625482-1-dmitry.torokhov@gmail.com [ij: converted hotkey_poll_setup() as well] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 142 ++++++++++++---------------- 1 file changed, 58 insertions(+), 84 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index d456add19b30..e2fe7a6a9d56 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -2172,7 +2172,7 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) return 0; } - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); HOTKEY_CONFIG_CRITICAL_START hotkey_driver_mask = mask; @@ -2185,8 +2185,6 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) ~hotkey_source_mask); hotkey_poll_setup(true); - mutex_unlock(&hotkey_mutex); - return rc; } @@ -2210,15 +2208,12 @@ static void tpacpi_input_send_tabletsw(void) { int state; - if (tp_features.hotkey_tablet && - !hotkey_get_tablet_mode(&state)) { - mutex_lock(&tpacpi_inputdev_send_mutex); + if (tp_features.hotkey_tablet && !hotkey_get_tablet_mode(&state)) { + guard(mutex)(&tpacpi_inputdev_send_mutex); input_report_switch(tpacpi_inputdev, SW_TABLET_MODE, !!state); input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); } } @@ -2243,7 +2238,6 @@ static int get_camera_shutter(void) static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { - bool known_ev; u32 scancode; if (tpacpi_driver_event(hkey)) @@ -2286,11 +2280,8 @@ static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) scancode = hkey; } - mutex_lock(&tpacpi_inputdev_send_mutex); - known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true); - mutex_unlock(&tpacpi_inputdev_send_mutex); - - return known_ev; + guard(mutex)(&tpacpi_inputdev_send_mutex); + return sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true); } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2555,7 +2546,7 @@ static void hotkey_poll_setup(const bool may_warn) lockdep_assert_held(&hotkey_mutex); - mutex_lock(&tpacpi_inputdev->mutex); + guard(mutex)(&tpacpi_inputdev->mutex); if (hotkey_poll_freq > 0 && (poll_driver_mask || (poll_user_mask && input_device_enabled(tpacpi_inputdev)))) { @@ -2575,14 +2566,12 @@ static void hotkey_poll_setup(const bool may_warn) poll_user_mask, poll_driver_mask); } } - mutex_unlock(&tpacpi_inputdev->mutex); } static void hotkey_poll_setup_safe(const bool may_warn) { - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); hotkey_poll_setup(may_warn); - mutex_unlock(&hotkey_mutex); } static void hotkey_poll_set_freq(unsigned int freq) @@ -3146,13 +3135,11 @@ static void tpacpi_send_radiosw_update(void) /* Issue rfkill input event for WLSW switch */ if (!(wlsw < 0)) { - mutex_lock(&tpacpi_inputdev_send_mutex); + guard(mutex)(&tpacpi_inputdev_send_mutex); input_report_switch(tpacpi_inputdev, SW_RFKILL_ALL, (wlsw > 0)); input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); } /* @@ -3164,7 +3151,7 @@ static void tpacpi_send_radiosw_update(void) static void hotkey_exit(void) { - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); hotkey_poll_stop_sync(); dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); @@ -3174,8 +3161,6 @@ static void hotkey_exit(void) hotkey_mask_set(hotkey_orig_mask)) | hotkey_status_set(false)) != 0) pr_err("failed to restore hot key mask to BIOS defaults\n"); - - mutex_unlock(&hotkey_mutex); } /* @@ -3492,11 +3477,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (tp_features.hotkey_mask) { /* hotkey_source_mask *must* be zero for * the first hotkey_mask_get to return hotkey_orig_mask */ - mutex_lock(&hotkey_mutex); - res = hotkey_mask_get(); - mutex_unlock(&hotkey_mutex); - if (res) - return res; + scoped_guard(mutex, &hotkey_mutex) { + res = hotkey_mask_get(); + if (res) + return res; + } hotkey_orig_mask = hotkey_acpi_mask; } else { @@ -3596,11 +3581,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) hotkey_exit(); return res; } - mutex_lock(&hotkey_mutex); - res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask) - | hotkey_driver_mask) - & ~hotkey_source_mask); - mutex_unlock(&hotkey_mutex); + scoped_guard(mutex, &hotkey_mutex) { + res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask) + | hotkey_driver_mask) + & ~hotkey_source_mask); + } if (res < 0 && res != -ENXIO) { hotkey_exit(); return res; @@ -4049,11 +4034,11 @@ static void hotkey_resume(void) { tpacpi_disable_brightness_delay(); - mutex_lock(&hotkey_mutex); - if (hotkey_status_set(true) < 0 || - hotkey_mask_set(hotkey_acpi_mask) < 0) - pr_err("error while attempting to reset the event firmware interface\n"); - mutex_unlock(&hotkey_mutex); + scoped_guard(mutex, &hotkey_mutex) { + if (hotkey_status_set(true) < 0 || + hotkey_mask_set(hotkey_acpi_mask) < 0) + pr_err("error while attempting to reset the event firmware interface\n"); + } tpacpi_send_radiosw_update(); tpacpi_input_send_tabletsw(); @@ -5119,21 +5104,16 @@ static DEFINE_MUTEX(kbdlight_mutex); static int kbdlight_set_level(int level) { - int ret = 0; - if (!hkey_handle) return -ENXIO; - mutex_lock(&kbdlight_mutex); + guard(mutex)(&kbdlight_mutex); if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level)) - ret = -EIO; - else - kbdlight_brightness = level; - - mutex_unlock(&kbdlight_mutex); + return -EIO; - return ret; + kbdlight_brightness = level; + return 0; } static int kbdlight_get_level(void) @@ -10189,9 +10169,8 @@ static void lcdshadow_resume(void) if (!lcdshadow_dev) return; - mutex_lock(&lcdshadow_dev->lock); + guard(mutex)(&lcdshadow_dev->lock); lcdshadow_set_sw_state(lcdshadow_dev, lcdshadow_dev->sw_state); - mutex_unlock(&lcdshadow_dev->lock); } static int lcdshadow_read(struct seq_file *m) @@ -10223,9 +10202,8 @@ static int lcdshadow_write(char *buf) if (state >= 2 || state < 0) return -EINVAL; - mutex_lock(&lcdshadow_dev->lock); - res = lcdshadow_set_sw_state(lcdshadow_dev, state); - mutex_unlock(&lcdshadow_dev->lock); + scoped_guard(mutex, &lcdshadow_dev->lock) + res = lcdshadow_set_sw_state(lcdshadow_dev, state); drm_privacy_screen_call_notifier_chain(lcdshadow_dev); @@ -10689,26 +10667,26 @@ static const struct platform_profile_ops dytc_profile_ops = { static void dytc_profile_refresh(void) { enum platform_profile_option profile; - int output = 0, err = 0; + int output = 0, err; int perfmode, funcmode = 0; - mutex_lock(&dytc_mutex); - if (dytc_capabilities & BIT(DYTC_FC_MMC)) { - if (dytc_mmc_get_available) - err = dytc_command(DYTC_CMD_MMC_GET, &output); - else - err = dytc_cql_command(DYTC_CMD_GET, &output); - funcmode = DYTC_FUNCTION_MMC; - } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { - err = dytc_command(DYTC_CMD_GET, &output); - /* Check if we are PSC mode, or have AMT enabled */ - funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF; - } else { /* Unknown profile mode */ - err = -ENODEV; + scoped_guard(mutex, &dytc_mutex) { + if (dytc_capabilities & BIT(DYTC_FC_MMC)) { + if (dytc_mmc_get_available) + err = dytc_command(DYTC_CMD_MMC_GET, &output); + else + err = dytc_cql_command(DYTC_CMD_GET, &output); + funcmode = DYTC_FUNCTION_MMC; + } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { + err = dytc_command(DYTC_CMD_GET, &output); + /* Check if we are PSC mode, or have AMT enabled */ + funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF; + } else { /* Unknown profile mode */ + err = -ENODEV; + } + if (err) + return; } - mutex_unlock(&dytc_mutex); - if (err) - return; perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; err = convert_dytc_to_profile(funcmode, perfmode, &profile); @@ -11620,7 +11598,7 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) if (tp_features.kbdlight) { enum led_brightness brightness; - mutex_lock(&kbdlight_mutex); + guard(mutex)(&kbdlight_mutex); /* * Check the brightness actually changed, setting the brightness @@ -11632,8 +11610,6 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) led_classdev_notify_brightness_hw_changed( &tpacpi_led_kbdlight.led_classdev, brightness); } - - mutex_unlock(&kbdlight_mutex); } /* Key events are suppressed by default hotkey_user_mask */ return false; @@ -11655,11 +11631,11 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) enum drm_privacy_screen_status old_hw_state; bool changed; - mutex_lock(&lcdshadow_dev->lock); - old_hw_state = lcdshadow_dev->hw_state; - lcdshadow_get_hw_state(lcdshadow_dev); - changed = lcdshadow_dev->hw_state != old_hw_state; - mutex_unlock(&lcdshadow_dev->lock); + scoped_guard(mutex, &lcdshadow_dev->lock) { + old_hw_state = lcdshadow_dev->hw_state; + lcdshadow_get_hw_state(lcdshadow_dev); + changed = lcdshadow_dev->hw_state != old_hw_state; + } if (changed) drm_privacy_screen_call_notifier_chain(lcdshadow_dev); @@ -11680,12 +11656,10 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) pr_err("Error retrieving camera shutter state after shutter event\n"); return true; } - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); - input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); + scoped_guard(mutex, &tpacpi_inputdev_send_mutex) { + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + input_sync(tpacpi_inputdev); + } return true; case TP_HKEY_EV_DOUBLETAP_TOGGLE: /* Toggle kernel-level doubletap event filtering */ -- cgit v1.2.3 From ee26050614d02397eb743854d718689dcaf1889b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 11:26:47 +0800 Subject: platform/x86/intel/vsec: reset state before re-enumerating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The change referenced by the Fixes tag split the VSEC enumeration logic out of intel_vsec_pci_probe() and made PCIe error recovery call intel_vsec_pci_init() directly. This avoids another pcim_enable_device() devm action and another devm allocation during slot reset, but it also means that error recovery reuses the priv structure allocated by probe. On the initial probe, priv is zeroed by devm_kzalloc(), so state[], suppliers[] and found_caps start from their initial values. During slot reset, the existing aux devices are removed before re-enumerating, but the reused priv still records the old devices as registered and the old capabilities as found. The next intel_vsec_pci_init() call can therefore short-circuit in intel_vsec_register_device() with -EEXIST, or stop early because found_caps already matches info->caps. The aux devices removed during slot reset are then not recreated, leaving VSEC functionality missing after PCIe error recovery. Reset the VSEC registration state after removing the old aux devices and before re-enumerating them. Fixes: 348ccc754d89 ("platform/x86/intel/vsec: Fix enable_cnt imbalance on PCIe error recovery") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260708032647.712412-1-lgs201920130244@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 5ab2215fdd7f..5ee379715068 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -51,6 +51,13 @@ struct vsec_priv { unsigned long found_caps; }; +static void intel_vsec_reset_state(struct vsec_priv *priv) +{ + memset(priv->suppliers, 0, sizeof(priv->suppliers)); + memset(priv->state, 0, sizeof(priv->state)); + priv->found_caps = 0; +} + static const char *intel_vsec_name(enum intel_vsec_id id) { switch (id) { @@ -857,6 +864,7 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) { struct intel_vsec_device *intel_vsec_dev; pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT; + struct vsec_priv *priv = pci_get_drvdata(pdev); unsigned long index; dev_info(&pdev->dev, "Resetting PCI slot\n"); @@ -877,6 +885,13 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) devm_release_action(&pdev->dev, intel_vsec_remove_aux, &intel_vsec_dev->auxdev); } + + /* + * intel_vsec_pci_init() reuses priv. Reset the registration state + * after tearing down old aux devices or the rewalk will skip them as + * already registered. + */ + intel_vsec_reset_state(priv); pci_restore_state(pdev); intel_vsec_pci_init(pdev); -- cgit v1.2.3 From e70b4921c51d7b2e5066d0058c1b248914a2498e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 19:20:39 +0800 Subject: platform: arm64: Remove redundant dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() calls. Signed-off-by: Pan Chuang Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260717112103.213017-2-panchuang@vivo.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/acer-aspire1-ec.c | 2 +- drivers/platform/arm64/huawei-gaokun-ec.c | 2 +- drivers/platform/arm64/lenovo-thinkpad-t14s.c | 2 +- drivers/platform/arm64/lenovo-yoga-c630.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c index 08d0b155a197..501c62a096f8 100644 --- a/drivers/platform/arm64/acer-aspire1-ec.c +++ b/drivers/platform/arm64/acer-aspire1-ec.c @@ -514,7 +514,7 @@ static int aspire_ec_probe(struct i2c_client *client) aspire_ec_irq_handler, IRQF_ONESHOT, dev_name(dev), ec); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; return 0; } diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c index 80a8ba8b8dda..d4ce8ad6426c 100644 --- a/drivers/platform/arm64/huawei-gaokun-ec.c +++ b/drivers/platform/arm64/huawei-gaokun-ec.c @@ -783,7 +783,7 @@ static int gaokun_ec_probe(struct i2c_client *client) gaokun_ec_irq_handler, IRQF_ONESHOT, dev_name(dev), ec); if (ret) - return dev_err_probe(dev, ret, "Failed to request IRQ\n"); + return ret; ec->hwmon_dev = devm_hwmon_device_register_with_info(dev, "gaokun_ec_hwmon", ec, &gaokun_ec_hwmon_chip_info, NULL); diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c index e7acb66b77f2..191ecd7f85b5 100644 --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c @@ -594,7 +594,7 @@ static int t14s_ec_probe(struct i2c_client *client) t14s_ec_irq_handler, IRQF_ONESHOT, dev_name(dev), ec); if (ret < 0) - return dev_err_probe(dev, ret, "Failed to get IRQ\n"); + return ret; /* * Disable wakeup support by default, because the driver currently does diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c index a8600a977fbc..cad40c58c55b 100644 --- a/drivers/platform/arm64/lenovo-yoga-c630.c +++ b/drivers/platform/arm64/lenovo-yoga-c630.c @@ -221,7 +221,7 @@ static int yoga_c630_ec_probe(struct i2c_client *client) NULL, yoga_c630_ec_thread_intr, IRQF_ONESHOT, "yoga_c630_ec", ec); if (ret < 0) - return dev_err_probe(dev, ret, "unable to request irq\n"); + return ret; ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec); if (ret) -- cgit v1.2.3 From 57103b7dba2dd40dc6cc5db4a1a3ac9b4928a529 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 19:20:43 +0800 Subject: platform/mellanox: Remove redundant dev_err() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260717112103.213017-6-panchuang@vivo.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/mellanox/mlxbf-tmfifo.c | 1 - drivers/platform/mellanox/mlxreg-hotplug.c | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c index 3c6408581373..fb1d424659c6 100644 --- a/drivers/platform/mellanox/mlxbf-tmfifo.c +++ b/drivers/platform/mellanox/mlxbf-tmfifo.c @@ -1390,7 +1390,6 @@ static int mlxbf_tmfifo_probe(struct platform_device *pdev) mlxbf_tmfifo_irq_handler, 0, "tmfifo", &fifo->irq_info[i]); if (rc) { - dev_err(dev, "devm_request_irq failed\n"); fifo->irq_info[i].irq = 0; return rc; } diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index d246772aafd6..d6549dd21772 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -740,10 +740,8 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, priv->irq, mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING | IRQF_SHARED, "mlxreg-hotplug", priv); - if (err) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", err); + if (err) return err; - } disable_irq(priv->irq); spin_lock_init(&priv->lock); -- cgit v1.2.3 From f399772c364562975b524380f92dc16824978512 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 19:20:45 +0800 Subject: platform/x86: Remove redundant dev_err()/dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260717112103.213017-8-panchuang@vivo.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-tf103c-dock.c | 2 +- drivers/platform/x86/intel/bxtwc_tmu.c | 5 +---- drivers/platform/x86/intel/bytcrc_pwrsrc.c | 2 +- drivers/platform/x86/intel/crystal_cove_charger.c | 2 +- drivers/platform/x86/intel/int0002_vgpio.c | 4 +--- drivers/platform/x86/intel/punit_ipc.c | 4 +--- 6 files changed, 6 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-tf103c-dock.c b/drivers/platform/x86/asus-tf103c-dock.c index 92466477de9a..3be5b96de548 100644 --- a/drivers/platform/x86/asus-tf103c-dock.c +++ b/drivers/platform/x86/asus-tf103c-dock.c @@ -772,7 +772,7 @@ static int tf103c_dock_probe(struct i2c_client *client) IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN, "dock_irq", dock); if (ret) - return dev_err_probe(dev, ret, "requesting dock IRQ"); + return ret; dock->hpd_gpio = devm_gpiod_get(dev, "dock_hpd", GPIOD_IN); if (IS_ERR(dock->hpd_gpio)) diff --git a/drivers/platform/x86/intel/bxtwc_tmu.c b/drivers/platform/x86/intel/bxtwc_tmu.c index b3666704d85b..73d7f71b2fd0 100644 --- a/drivers/platform/x86/intel/bxtwc_tmu.c +++ b/drivers/platform/x86/intel/bxtwc_tmu.c @@ -64,11 +64,8 @@ static int bxt_wcove_tmu_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, wctmu->irq, NULL, bxt_wcove_tmu_irq_handler, IRQF_ONESHOT, "bxt_wcove_tmu", wctmu); - if (ret) { - dev_err(&pdev->dev, "request irq failed: %d,virq: %d\n", - ret, wctmu->irq); + if (ret) return ret; - } /* Unmask TMU second level Wake & System alarm */ regmap_update_bits(wctmu->regmap, BXTWC_MTMUIRQ_REG, diff --git a/drivers/platform/x86/intel/bytcrc_pwrsrc.c b/drivers/platform/x86/intel/bytcrc_pwrsrc.c index 68ac040082df..1f39850b80fd 100644 --- a/drivers/platform/x86/intel/bytcrc_pwrsrc.c +++ b/drivers/platform/x86/intel/bytcrc_pwrsrc.c @@ -221,7 +221,7 @@ static int crc_pwrsrc_probe(struct platform_device *pdev) crc_pwrsrc_irq_handler, IRQF_ONESHOT, KBUILD_MODNAME, data); if (ret) - return dev_err_probe(dev, ret, "requesting IRQ\n"); + return ret; } data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL); diff --git a/drivers/platform/x86/intel/crystal_cove_charger.c b/drivers/platform/x86/intel/crystal_cove_charger.c index e4299cfa2205..425c7eb32442 100644 --- a/drivers/platform/x86/intel/crystal_cove_charger.c +++ b/drivers/platform/x86/intel/crystal_cove_charger.c @@ -135,7 +135,7 @@ static int crystal_cove_charger_probe(struct platform_device *pdev) crystal_cove_charger_irq, IRQF_ONESHOT, KBUILD_MODNAME, charger); if (ret) - return dev_err_probe(&pdev->dev, ret, "requesting irq\n"); + return ret; return 0; } diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c index 562e88025643..96b36cfcd9e4 100644 --- a/drivers/platform/x86/intel/int0002_vgpio.c +++ b/drivers/platform/x86/intel/int0002_vgpio.c @@ -208,10 +208,8 @@ static int int0002_probe(struct platform_device *pdev) */ ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002", chip); - if (ret) { - dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); + if (ret) return ret; - } girq = &chip->irq; gpio_irq_chip_set_chip(girq, &int0002_irqchip); diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c index 6d770b950dfb..a3d2b0b64135 100644 --- a/drivers/platform/x86/intel/punit_ipc.c +++ b/drivers/platform/x86/intel/punit_ipc.c @@ -250,10 +250,8 @@ static int intel_punit_ipc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc, IRQF_NO_SUSPEND, "intel_punit_ipc", punit_ipcdev); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", irq); + if (ret) return ret; - } punit_ipcdev->irq = irq; } -- cgit v1.2.3 From aa5e0d0fa3cce4e5e54e51b68676ce659a6fd156 Mon Sep 17 00:00:00 2001 From: Tinoy Thomas Date: Wed, 5 Aug 2026 05:56:18 -0500 Subject: platform/x86: asus-wmi: add tablet-mode switch quirk for ASUS ProArt PX13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ProArt PX13 (HN7306WV) is a 360-degree convertible. It exposes no dedicated SW_TABLET_MODE input switch, but the firmware implements the ASUS WMI lid-flip query (DSTS dev_id 0x00060062): it returns 0x10000 with the lid open and 0x10001 when the screen is folded back, and the 0xfa (NOTIFY_LID_FLIP) WMI event fires on every fold/unfold transition. Add a DMI quirk selecting the lid-flip dev-id so the driver registers SW_TABLET_MODE on the "Asus WMI hotkeys" input device and reports the hinge state, matching the existing UX360 behavior. Signed-off-by: Tinoy Thomas Link: https://patch.msgid.link/20260805105618.30736-1-yo@t69.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-nb-wmi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index aeb461b1644d..5f8cdd92aa59 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -137,6 +137,10 @@ static struct quirk_entry quirk_asus_use_lid_flip_devid = { .tablet_switch_mode = asus_wmi_lid_flip_devid, }; +static struct quirk_entry quirk_asus_proart_px13 = { + .tablet_switch_mode = asus_wmi_lid_flip_devid, +}; + static struct quirk_entry quirk_asus_tablet_mode = { .wmi_backlight_set_devstate = true, .tablet_switch_mode = asus_wmi_lid_flip_rog_devid, @@ -562,6 +566,15 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_z13, }, + { + .callback = dmi_matched, + .ident = "ASUS ProArt PX13", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "HN7306WV"), + }, + .driver_data = &quirk_asus_proart_px13, + }, {}, }; -- cgit v1.2.3 From bf6e7e433e83924bf7e96514e7afb50b43723022 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 5 Aug 2026 20:28:07 +0200 Subject: platform/x86: asus-armoury: Add dgpu_power_state attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for WMI device ID 0x00120097 (ASUS_WMI_DEVID_DGPU_POWER_STATE) in the asus-armoury driver. This exposes a read-only sysfs attribute 'dgpu_power_state' under the firmware_attributes class to query the discrete GPU power state (0: D3 cold, 1: D0 active). Suggested-by: Denis Benato Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260805183131.3075-3-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 93d9665717af..f745924af8a8 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -786,6 +786,8 @@ ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness", "Set the panel brightness to Off<0> or On<1>"); ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED, "Show the eGPU connection status"); +ASUS_ATTR_GROUP_BOOL_RO(dgpu_power_state, "dgpu_power_state", ASUS_WMI_DEVID_DGPU_POWER_STATE, + "Show the dGPU power state (0: D3 cold, 1: D0 active)"); ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL, "Set the CPU slow package limit"); ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT, @@ -810,6 +812,7 @@ static const struct asus_attr_group armoury_attr_groups[] = { { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED }, { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU }, { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU }, + { &dgpu_power_state_attr_group, ASUS_WMI_DEVID_DGPU_POWER_STATE }, { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM }, { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL }, -- cgit v1.2.3 From b3f06a74fea6f1ddec192dfece8e52053d0b6c57 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 5 Aug 2026 20:28:08 +0200 Subject: platform/x86: asus-armoury: Prevent disabling dGPU when in use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check ASUS_WMI_DEVID_DGPU_POWER_STATE before attempting to disable the discrete GPU in dgpu_disable_current_value_store(). Return -EBUSY if the dGPU is currently active and in use. Fixes: f99eb098090e ("platform/x86: asus-armoury: move existing tunings to asus-armoury module") Suggested-by: Denis Benato Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260805183131.3075-4-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index f745924af8a8..2d5ca75bc727 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -515,13 +515,25 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, if (err) return err; - if (asus_armoury.gpu_mux_dev_id) { - err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id); - if (err) - return err; - if (!result && disable) { - pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n"); - return -EBUSY; + if (disable) { + if (asus_armoury.gpu_mux_dev_id) { + err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id); + if (err) + return err; + if (!result) { + pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n"); + return -EBUSY; + } + } + + if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU_POWER_STATE)) { + err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU_POWER_STATE); + if (err) + return err; + if (result) { + pr_warn("Cannot disable dGPU when it is in use\n"); + return -EBUSY; + } } } -- cgit v1.2.3 From 6a6f3051e705b5c8facb0bf5984d5b981509e0b7 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 15 Sep 2026 22:46:15 +0500 Subject: platform/x86: hp-bioscfg: fix slab-out-of-bounds write in hp_convert_hexstr_to_str() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_convert_hexstr_to_str() decodes an ACPI string made up of space-separated ASCII hex-byte tokens of the form "0xHH". For example, the four-byte input "0x41" decodes to the single character 'A', and the nine-byte input "0x09 0x41" decodes to "\tA": each token is read in a five-byte step (four characters for the token, one for the trailing delimiter), and produces one output byte, or two if that byte is '\\', '\r', '\n', or '\t', which are written back out as a backslash followed by the matching letter. The output buffer is sized with kmalloc(input_len, GFP_KERNEL), the raw encoded length of the input, not the decoded length. For well-formed input this is generous, since five input bytes never decode to more than two output bytes. But input_len comes directly from the ACPI string length reported by firmware and isn't guaranteed to respect the five-byte encoding, so a short input_len can undersize the allocation. With input_len == 1, only one byte is allocated, yet decoding still produces at least one output byte plus the NUL terminator written unconditionally afterwards, so two bytes are needed. That terminator write then lands one byte past the end of the allocation. KASAN caught exactly this during BIOS attribute enumeration on boot, triggered by a one-byte encoded input value: BUG: KASAN: slab-out-of-bounds in hp_convert_hexstr_to_str+0x6d8/0x710 [hp_bioscfg] Write of size 1 at addr ffff8881032e5d81 by task (udev-worker)/520 The buggy address is located 0 bytes to the right of allocated 1-byte region [ffff8881032e5d80, ffff8881032e5d81) Size the allocation to the worst-case decoded length instead of the raw input length: two output bytes for every five-byte input chunk (DIV_ROUND_UP(input_len, 5)), plus one byte for the terminator. Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260915174615.63924-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 309634c1cc20..ff28db7ced00 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -442,7 +442,7 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int * *len = 0; *str = NULL; - new_str = kmalloc(input_len, GFP_KERNEL); + new_str = kmalloc(2 * DIV_ROUND_UP(input_len, 5) + 1, GFP_KERNEL); if (!new_str) return -ENOMEM; -- cgit v1.2.3 From 17d0f874b1f8774c61ad495a4ad20ed8c829dfae Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Tue, 11 Aug 2026 10:21:15 +0800 Subject: platform/x86: Add ACPI_COMPANION() NULL check in topstar_acpi_probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add NULL check for ACPI_COMPANION() in topstar_acpi_probe() to prevent NULL pointer dereference when driver_override matches a device without ACPI companion. Fixes: 3471415c8186 ("platform/x86: topstar-laptop: Convert ACPI driver to a platform one") Signed-off-by: Xueqin Luo Reviewed-by: Rafael J. Wysocki Link: https://patch.msgid.link/20260811022115.338595-1-luoxueqin@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/topstar-laptop.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 0e842c55dcc5..730a599abf0f 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -287,10 +287,14 @@ static const struct dmi_system_id topstar_dmi_ids[] = { static int topstar_acpi_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; struct topstar_laptop *topstar; int err; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + dmi_check_system(topstar_dmi_ids); topstar = kzalloc_obj(struct topstar_laptop); -- cgit v1.2.3 From f03ec815842c4593f0d743ae21b11bb399137668 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 11 Aug 2026 08:57:03 +0200 Subject: platform/x86: asus-armoury: Add power limits quirk for GA401IHR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add power limits quirk entry for ASUS ROG GA401IHR laptop. The limits are extracted from the device's ThrottleGear XML configuration file. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260811065703.6626-1-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 0e301d663d0c..6dfe5ddfe4ac 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1056,6 +1056,20 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA401IHR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 15, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA401Q"), -- cgit v1.2.3 From c72dd9994952eb09442827caecb945474ae3072b Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sun, 13 Sep 2026 16:35:17 +0200 Subject: platform/x86: add Acer battery control driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Acer laptops can configure battery related features through Acer Care Center on Windows. This driver uses the power supply extension to set a battery charge limit and exposes the battery temperature. This driver is based on the existing acer-wmi-battery project on GitHub and was tested on an Acer Aspire A315-510P. Reviewed-by: Armin Wolf Signed-off-by: Jelle van der Waa Tested-by: Mohsen Tahmasebi Link: https://patch.msgid.link/20260913143521.1190506-2-jelle@vdwaa.nl [ij: simplify returns, remove version.h include] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/Kconfig | 12 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/acer-wmi-battery.c | 450 ++++++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 drivers/platform/x86/acer-wmi-battery.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 957034f39e4e..45f15f93afa7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -189,6 +189,18 @@ config ACER_WMI If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. +config ACER_WMI_BATTERY + tristate "Acer WMI Battery" + depends on ACPI_WMI + depends on ACPI_BATTERY + depends on DMI + help + This is a driver for Acer laptops with battery health control. It + adds charge limit control and battery temperature reporting. + + If you have an ACPI-WMI Battery compatible Acer laptop, say Y or M + here. + source "drivers/platform/x86/amd/Kconfig" config ADV_SWBUTTON diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 872ac3842391..a877acd937cd 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o +obj-$(CONFIG_ACER_WMI_BATTERY) += acer-wmi-battery.o # AMD obj-y += amd/ diff --git a/drivers/platform/x86/acer-wmi-battery.c b/drivers/platform/x86/acer-wmi-battery.c new file mode 100644 index 000000000000..10a34d4ab058 --- /dev/null +++ b/drivers/platform/x86/acer-wmi-battery.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * acer-wmi-battery.c: Acer battery health control driver + * + * This is a driver for the WMI battery health control interface found + * on some Acer laptops. This interface allows to enable/disable a + * battery charge limit ("health mode") and exposes the battery temperature. + * + * Based on acer-wmi-battery https://github.com/frederik-h/acer-wmi-battery/ + * + * Copyright (C) 2022-2025 Frederik Harwath + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "acer-wmi-battery" + +#define ACER_BATTERY_GUID "79772EC5-04B1-4BFD-843C-61E7F77B6CC9" + +/* + * The Acer OEM software seems to always use this battery index, + * so we emulate this behaviour to not confuse the underlying firmware. + * + * However this also means that we only fully support devices with a + * single battery for now. + */ +#define ACER_BATTERY_INDEX 0x1 + +static bool force; +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Enable health mode control without checking for supported devices\n"); + +struct get_battery_health_control_status_input { + u8 battery_no; + u8 function_query; + u8 reserved[2]; +} __packed; + +struct get_battery_health_control_status_output { + u8 function_list; + u8 ret[2]; + u8 function_status[5]; +} __packed; + +struct set_battery_health_control_input { + u8 battery_no; + u8 function_mask; + u8 function_status; + u8 reserved_in[5]; +} __packed; + +struct set_battery_health_control_output { + __le16 ret; + __le16 reserved_out; +} __packed; + +enum battery_mode { + HEALTH_MODE = 1, + CALIBRATION_MODE = 2, +}; + +struct acer_wmi_battery_data { + struct acpi_battery_hook hook; + struct wmi_device *wdev; + const struct power_supply_ext *battery_ext; +}; + +static bool health_mode __ro_after_init; + +static int acer_wmi_battery_get_information(struct acer_wmi_battery_data *data, + u32 index, u32 battery, u32 *result) +{ + u32 args[2] = { index, battery }; + struct wmi_buffer input = { .length = sizeof(args), .data = args }; + struct wmi_buffer output; + int ret; + + ret = wmidev_invoke_method(data->wdev, 0, 19, &input, &output, sizeof(*result)); + if (ret) + return ret; + + *result = le32_to_cpup((__le32 *)output.data); + + kfree(output.data); + + return 0; +} + +static int acer_wmi_battery_get_health_control_status(struct acer_wmi_battery_data *data, + bool *health_mode) +{ + /* + * Acer Care Center seems to always call the WMI method + * with fixed parameters. This yields information about + * the availability and state of both health and + * calibration mode. The modes probably apply to + * all batteries of the system. + */ + struct get_battery_health_control_status_input args = { + .battery_no = ACER_BATTERY_INDEX, + .function_query = 0x1, + .reserved = { 0x0, 0x0 }, + }; + struct wmi_buffer input = { .length = sizeof(args), .data = &args }; + struct wmi_buffer output; + int ret; + + ret = wmidev_invoke_method(data->wdev, 0, 20, &input, &output, + sizeof(struct get_battery_health_control_status_output)); + if (ret) + return ret; + + struct get_battery_health_control_status_output *status_output __free(kfree) = output.data; + + if (!(status_output->function_list & HEALTH_MODE)) + return -EINVAL; + + *health_mode = status_output->function_status[0] > 0; + + return 0; +} + +static int acer_wmi_battery_set_health_control(struct acer_wmi_battery_data *data, + u8 function, bool function_status) +{ + struct set_battery_health_control_input args = { + .battery_no = ACER_BATTERY_INDEX, + .function_mask = function, + .function_status = function_status ? 1 : 0, + .reserved_in = { 0x0, 0x0, 0x0, 0x0, 0x0 }, + }; + struct wmi_buffer input = { .length = sizeof(args), .data = &args }; + struct wmi_buffer output; + int ret; + + ret = wmidev_invoke_method(data->wdev, 0, 21, &input, &output, + sizeof(struct set_battery_health_control_output)); + if (ret) + return ret; + + struct set_battery_health_control_output *status_output __free(kfree) = output.data; + if (le16_to_cpu(status_output->ret) != 0) + return -EIO; + + return 0; +} + +static int acer_battery_ext_property_get(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct acer_wmi_battery_data *data = ext_data; + bool health_mode; + u32 value; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + ret = acer_wmi_battery_get_health_control_status(data, &health_mode); + if (ret) + return ret; + + val->intval = health_mode ? POWER_SUPPLY_CHARGE_TYPE_LONGLIFE + : POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = acer_wmi_battery_get_information(data, 0x8, ACER_BATTERY_INDEX, &value); + if (ret) + return ret; + + if (value > U16_MAX) + return -ERANGE; + + val->intval = value + ABSOLUTE_ZERO_MILLICELSIUS / MILLIDEGREE_PER_DECIDEGREE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int acer_battery_ext_property_set(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct acer_wmi_battery_data *data = ext_data; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + return acer_wmi_battery_set_health_control(data, HEALTH_MODE, + val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE); + default: + return -EINVAL; + } +} + +static int acer_battery_ext_property_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPES: + return true; + default: + return false; + } +} + +static const enum power_supply_property acer_battery_properties_v1[] = { + POWER_SUPPLY_PROP_TEMP, +}; + +static const enum power_supply_property acer_battery_properties_v2[] = { + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const struct power_supply_ext acer_wmi_battery_extension_v1 = { + .name = DRIVER_NAME, + .properties = acer_battery_properties_v1, + .num_properties = ARRAY_SIZE(acer_battery_properties_v1), + .get_property = acer_battery_ext_property_get, +}; + +static const struct power_supply_ext acer_wmi_battery_extension_v2 = { + .name = DRIVER_NAME, + .properties = acer_battery_properties_v2, + .num_properties = ARRAY_SIZE(acer_battery_properties_v2), + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE), + .get_property = acer_battery_ext_property_get, + .set_property = acer_battery_ext_property_set, + .property_is_writeable = acer_battery_ext_property_is_writeable, +}; + +static int acer_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook); + + return power_supply_register_extension(battery, data->battery_ext, + &data->wdev->dev, data); +} + +static int acer_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook); + + power_supply_unregister_extension(battery, data->battery_ext); + + return 0; +} + +static int acer_wmi_battery_probe(struct wmi_device *wdev, const void *context) +{ + struct acer_wmi_battery_data *data; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, data); + data->wdev = wdev; + data->battery_ext = health_mode ? &acer_wmi_battery_extension_v2 + : &acer_wmi_battery_extension_v1; + data->hook.name = "Acer Battery Extension"; + data->hook.add_battery = acer_battery_add; + data->hook.remove_battery = acer_battery_remove; + + return devm_battery_hook_register(&data->wdev->dev, &data->hook); +} + +static const struct wmi_device_id acer_wmi_battery_id_table[] = { + { ACER_BATTERY_GUID, NULL }, + { } +}; +MODULE_DEVICE_TABLE(wmi, acer_wmi_battery_id_table); + +static struct wmi_driver acer_wmi_battery_driver = { + .driver = { + .name = DRIVER_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = acer_wmi_battery_id_table, + .probe = acer_wmi_battery_probe, + .no_singleton = true, +}; + +static const struct dmi_system_id acer_wmi_battery_health_mode_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-510P"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-24PT"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-44P"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A715-42G"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-57"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Predator PHN16-71"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-34"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-43"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFE16-44"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFG16-72"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX14-71G"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX16-61G"), + }, + }, + {} +}; + +static int __init acer_wmi_battery_module_init(void) +{ + if (dmi_check_system(acer_wmi_battery_health_mode_table) || force) + health_mode = true; + else + health_mode = false; + + return wmi_driver_register(&acer_wmi_battery_driver); +} + +static void __exit acer_wmi_battery_module_exit(void) +{ + wmi_driver_unregister(&acer_wmi_battery_driver); +} + +module_init(acer_wmi_battery_module_init); +module_exit(acer_wmi_battery_module_exit); + +MODULE_AUTHOR("Frederik Harwath "); +MODULE_AUTHOR("Jelle van der Waa "); +MODULE_DESCRIPTION("Acer battery health control WMI driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From a97dd0e1e49a8590183cf2c79028315e0ac3f060 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 16 Sep 2026 05:46:06 +0500 Subject: platform/x86: hp-bioscfg: zero the hex-string decode buffer in hp_convert_hexstr_to_str() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_convert_hexstr_to_str() allocates its output buffer for the worst-case decoded length, then fills in only as many bytes as the input actually decodes to before shrinking the allocation down to that length with krealloc(). Well-formed input can decode to noticeably fewer bytes than the worst case, so the buffer is frequently only partially written by the time it is realloc'd and returned to the caller. Use kzalloc() instead of kmalloc() for the initial allocation, so any unused capacity starts out zeroed instead of holding leftover heap contents, rather than relying on every current and future caller and code path to fill the buffer exactly. Suggested-by: Ilpo Järvinen Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260916004606.165065-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index ff28db7ced00..2dab9c0624b2 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -442,7 +442,7 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int * *len = 0; *str = NULL; - new_str = kmalloc(2 * DIV_ROUND_UP(input_len, 5) + 1, GFP_KERNEL); + new_str = kzalloc(2 * DIV_ROUND_UP(input_len, 5) + 1, GFP_KERNEL); if (!new_str) return -ENOMEM; -- cgit v1.2.3 From 6860f40002df9a58a40e4feb02d6747f851b221a Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:36 +0530 Subject: platform/x86: asus-laptop: Fix typo in infos_show() comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "informations" to "information" in the comment describing the HWRS method. Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/6a9f827d1135258ac0e75ce2dd6d05e1cded6d44.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 449addd1ac7a..de607edb9490 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -865,7 +865,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr, len += sysfs_emit_at(page, len, "SFUN value : %#x\n", (uint) temp); /* - * The HWRS method return informations about the hardware. + * The HWRS method return information about the hardware. * 0x80 bit is for WLAN, 0x100 for Bluetooth. * 0x40 for WWAN, 0x10 for WIMAX. * The significance of others is yet to be found. -- cgit v1.2.3 From f1a7b185f985446f55e2d610c55e1f4cdb97ed9e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:37 +0530 Subject: platform/x86: asus-nb-wmi: Fix typo in asus_nb_wmi_quirks() comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "paramater" to "parameter". Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/accb5d1622035aadc423ceafab32f3e6f74bffb2.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-nb-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 5f8cdd92aa59..da1ddd6016c6 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -586,7 +586,7 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) driver->quirks = quirks; driver->panel_power = BACKLIGHT_POWER_ON; - /* overwrite the wapf setting if the wapf paramater is specified */ + /* overwrite the wapf setting if the wapf parameter is specified */ if (wapf != -1) quirks->wapf = wapf; else -- cgit v1.2.3 From 2205954cc4b6fab83b1084960ddeda0c192af9d4 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:38 +0530 Subject: platform/x86: hp_accel: Fix typo in lis3lv02d_acpi_write() comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "writting" to "writing". Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/402a3251fcffa3b080415513b05e65843da69825.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp_accel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c index 39b73dc473f1..40e1b9fb1b6d 100644 --- a/drivers/platform/x86/hp/hp_accel.c +++ b/drivers/platform/x86/hp/hp_accel.c @@ -122,7 +122,7 @@ static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret) static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val) { struct acpi_device *dev = lis3->bus_priv; - unsigned long long ret; /* Not used when writting */ + unsigned long long ret; /* Not used when writing */ union acpi_object in_obj[2]; struct acpi_object_list args = { 2, in_obj }; -- cgit v1.2.3 From b24e9b919dcab218ed5a8c93d417335be56d24fd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:39 +0530 Subject: platform/x86/intel/tpmi: Fix typo in TPMI_FEATURE_NAME_LEN comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "upto" to "up to". Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/4c84e4fd734d7e1aa70e045e6c51f8bda3fff5ba.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 0153dd57838e..fbe9c8af4aee 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -597,7 +597,7 @@ static const char *intel_tpmi_name(enum intel_tpmi_id id) } } -/* String Length for tpmi-"feature_name(upto 8 bytes)" */ +/* String Length for tpmi-"feature_name(up to 8 bytes)" */ #define TPMI_FEATURE_NAME_LEN 14 static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, -- cgit v1.2.3 From b6fb95abfb48a13de916c64dbb78d6400e846e66 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:40 +0530 Subject: platform/x86: msi-laptop: Fix typo in struct quirk_entry comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "cann't" to "can't". Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/2b5e16441db21479603772f0af0b47a1502e4667.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index ddef6b78d2fa..0a0c756a5598 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -117,7 +117,7 @@ struct quirk_entry { * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to * disable the original Wlan/Bluetooth control by BIOS when user press * fn key, then control Wlan/Bluetooth/3G by SCM (software control by - * OS). Without SCM, user cann't on/off 3G module on those 3G netbook. + * OS). Without SCM, user can't on/off 3G module on those 3G netbook. * On Linux, msi-laptop driver will do the same thing to disable the * original BIOS control, then might need use HAL or other userland * application to do the software control that simulate with SCM. -- cgit v1.2.3 From 70245ce9ecded6a87fc8a3a9078e08e4eb3354b6 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:41 +0530 Subject: platform/x86: panasonic-laptop: Fix typo in the file history comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "retrive" to "retrieve" in the v0.4 entry. Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/ffc97dd04a02c81d433d031581d9a17c703bcbb3.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/panasonic-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 19d194ff37ca..a3597344edef 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -107,7 +107,7 @@ * * Jul.25, 2004 Hiroshi Miura * - v0.4 first post version - * add function to retrive SIFR + * add function to retrieve SIFR * * Jul.24, 2004 Hiroshi Miura * - v0.3 get proper status of hotkey -- cgit v1.2.3 From e1ecb93fce699e85afd235a1199e72e4dccf030e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:42 +0530 Subject: platform/x86: samsung-laptop: Fix typo in sabi_configs comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "wihout" to "without". Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/1ef5ab664c2b4e0080641271a278c1659a29a167.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/samsung-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 710f3d5bf84c..18631e96bb17 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -295,7 +295,7 @@ static const struct sabi_config sabi_configs[] = { * d0, d1, d2, d3 - data fields * call - call SABI using command and data * - * This allow to call arbitrary sabi commands wihout + * This allow to call arbitrary sabi commands without * modifying the driver at all. * For example, setting the keyboard backlight brightness to 5 * -- cgit v1.2.3 From c78e206963e561815875f016ed7b507ab28bda6d Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 10 Sep 2026 10:14:43 +0530 Subject: platform/x86: uniwill-laptop: Fix typo in the kbd LED brightness comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct "device-specfic" to "device-specific" in the comment on restoring the default RGB value. Comment only, no functional change. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Mark Pearson Reviewed-by: Denis Benato Link: https://patch.msgid.link/5899c28bcbdc04c0c07ae3f1bf8501e928e7d4dd.1789015364.git.hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 7a2eeaec4c96..eeead0159828 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1705,7 +1705,7 @@ static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, /* * The EC interprets a RGB value of 0x000000 as a command to restore - * the device-specfic default RGB value. Work around this by writing + * the device-specific default RGB value. Work around this by writing * a RGB value of 0x010101 (faint white) instead. */ if (data->kbd_led_mc_subled_info[0].intensity == 0 && -- cgit v1.2.3 From 88a7f99c5871a7a04e6fd0e32481b80810683617 Mon Sep 17 00:00:00 2001 From: Asai Neko Date: Thu, 17 Sep 2026 02:38:24 +0800 Subject: platform/x86: asus-wmi: don't warn when LPS0 is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asus-wmi attempts to register its LPS0 suspend-to-idle callback during probe. acpi_register_lps0_dev() returns -ENODEV when the LPS0 interface is unavailable or explicitly disabled, but the driver treats this expected condition as a registration failure and prints: failed to register LPS0 sleep handler in asus-wmi This occurs on the ASUS ROG STRIX X670E-A GAMING WIFI with BIOS 2704. The system exposes no PNP0D80 ACPI device and uses deep suspend. The warning therefore misleadingly suggests a sleep-related malfunction on a platform where the LPS0 callback is not applicable. Suppress the warning for -ENODEV while retaining it for other errors. Leave callback registration and suspend/resume behavior unchanged. Reviewed-by: Mario Limonciello (AMD) Reviewed-by: Denis Benato Signed-off-by: Asai Neko Link: https://patch.msgid.link/20260917-asus-wmi-lps0-unavailable-warn-v2-1-aa8e3d73634a@sne.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a65090429ca7..c2af1f049b67 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -5421,8 +5421,11 @@ static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = { static void asus_s2idle_check_register(void) { - if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops)) - pr_warn("failed to register LPS0 sleep handler in asus-wmi\n"); + int ret; + + ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops); + if (ret && ret != -ENODEV) + pr_warn("failed to register LPS0 sleep handler: %d\n", ret); } static void asus_s2idle_check_unregister(void) -- cgit v1.2.3 From 639bc2254816b7c4e7a9801a7b3aa71bfa3f915c Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Tue, 15 Sep 2026 17:26:07 +0000 Subject: platform/x86: asus-wmi: fix use-after-free unregistering kbd_led led MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kbd_led_update_all() registers asus->kbd_led with devm_led_classdev_register() on the platform device, but kbd_led is embedded in struct asus_wmi, which asus_wmi_remove() frees at its end. The driver core calls .remove() before devres_release_all(), so the deferred devres release runs led_classdev_unregister() on memory that has already been freed. Whether that faults depends on whether the slab has been reused in between, which makes the crash on module unload intermittent and silently corrupts the led classdev state in the remaining runs. Unregister the led classdev explicitly in asus_wmi_led_exit(), after the led workqueue has been destroyed: kbd_led_work can then neither run nor be queued anymore, since asus_ref.asus is NULL too, so kbd_led_registered is stable and the classdev can be released while the memory embedding it is still alive. devm_led_classdev_unregister() also drops the devres entry, leaving nothing to run at unbind cleanup time; it warns when no devres entry exists, hence the kbd_led_registered check. Fixes: fac55d29581f ("platform/x86: asus-wmi: Add support for multiple kbd led handlers") Closes: https://lore.kernel.org/all/CAO84+x+P2_xyHP89+nGVSMV6bL+dy0P9=vyEEfZGnFhv0hBNWw@mail.gmail.com Reported-by: Hugo Baigue Tested-by: Hugo Baigue Cc: stable@vger.kernel.org Assisted-by: zcode:glm-5.3-flash Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260915172608.14234-1-denis.benato@linux.dev Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index c2af1f049b67..0eb43ca44119 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2088,6 +2088,18 @@ static void asus_wmi_led_exit(struct asus_wmi *asus) if (asus->led_workqueue) destroy_workqueue(asus->led_workqueue); + + /* + * kbd_led is registered lazily by kbd_led_work: now that the + * workqueue is destroyed and asus_ref.asus is NULL, the work can + * neither run nor be queued anymore, furthermore leaving it to + * devres would run the unregister from devres_release_all(), + * after .remove() returned and the struct asus_wmi embedding + * kbd_led has been freed. + */ + if (asus->kbd_led_registered) + devm_led_classdev_unregister(&asus->platform_device->dev, + &asus->kbd_led); } static int asus_wmi_led_init(struct asus_wmi *asus) -- cgit v1.2.3 From c3e23729224dbf177185e99b61b4ca39046e9b12 Mon Sep 17 00:00:00 2001 From: Alex Yeo Date: Fri, 28 Aug 2026 08:34:31 +0800 Subject: platform/x86: panasonic-laptop: register FN key up/down events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For Let's Note laptops, pressing the FN key sends a signal for both keyup and keydown. FN key down: 0x0030 FN key up: 0x0031 Currently, pressing the FN key causes the following output in dmesg: [ +0.322901] Unknown hotkey event: 0x0030 [ +1.101006] Unknown hotkey event: 0x0031 This has a tendency to spam dmesg as this is printed every time the FN key is pressed regardless of whether or not it was used as a modifier to activate functions like brightness, etc. Map both the keyup/down events so it is no longer reported as unknown hotkeys. Once the patch has been applied, evtest shows the following when the FN key is pressed (and released): Event: time 1787875023.918171, type 4 (EV_MSC), code 4 (MSC_SCAN), value 30 Event: time 1787875023.918171, type 1 (EV_KEY), code 464 (KEY_FN), value 1 Event: time 1787875023.918171, -------------- SYN_REPORT ------------ Event: time 1787875024.038462, type 4 (EV_MSC), code 4 (MSC_SCAN), value 30 Event: time 1787875024.038462, type 1 (EV_KEY), code 464 (KEY_FN), value 0 Event: time 1787875024.038462, -------------- SYN_REPORT ------------ Tested on the following Let's Note models: - CF-SX1 - CF-RZ6 - CF-SV8 - CF-QV9 - CF-SR4 Signed-off-by: Alex Yeo Link: https://patch.msgid.link/20260828003432.88748-1-alexyeo362@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/panasonic-laptop.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index a3597344edef..2b1d3c2eb2e5 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -232,6 +232,7 @@ static const struct key_entry panasonic_keymap[] = { { KE_KEY, 41, { KEY_MACRO9 } }, { KE_KEY, 42, { KEY_MACRO10 } }, { KE_KEY, 43, { KEY_MACRO11 } }, + { KE_KEY, 48, { KEY_FN } }, { KE_END, 0 } }; @@ -863,6 +864,17 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) key, 0x80, false); } + /* + * Let's Note models report both FN key down and key up + * without the expected 0x80 key up/down bit + */ + if (key == 48) { + updown = 1; + } else if (key == 49) { + key = 48; + updown = 0; + } + /* * Don't report brightness key-presses if they are also reported * by the ACPI video bus. -- cgit v1.2.3 From d3b84e9c1a63c3ef6ea89276b7162ccb52b2ffd8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 2 Aug 2026 14:45:30 +0200 Subject: platform/x86: dell-wmi-aio: Convert to use the bus-based WMI API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It turns out that some devices like the Dell Inspiron One 2310 contain multiple WMI event devices with a matching GUID of 284A0E6B-380E-472A-921F-E52786257FB4, each handling a separate hotkey (volume up, volume down, ...). The dell-wmi-aio driver however still uses the legacy GUID-based WMI API and can thus only see the first of those WMI event devices, preventing the remaining buttons from working. Fix this by converting the driver to use the modern bus-based WMI API. This also includes replacing the usage of union acpi_object with struct wmi_buffer. Tested using the ACPI-WMI ASL code from the Dell Inspiron One 2310 together with some changes to allow injecting events. Link: https://linux-hardware.org/?probe=e86d77e44d Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260802124530.7116-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-aio.c | 198 ++++++++++++------------------- 1 file changed, 78 insertions(+), 120 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c index 54096495719b..8849ad93480b 100644 --- a/drivers/platform/x86/dell/dell-wmi-aio.c +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -5,14 +5,20 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include #include #include +#include +#include #include #include #include #include -#include #include +#include MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); MODULE_LICENSE("GPL"); @@ -20,22 +26,19 @@ MODULE_LICENSE("GPL"); #define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" #define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" +struct dell_wmi_aio_data { + struct input_dev *input_device; + /* Protects the input sequence */ + struct mutex input_lock; +}; + struct dell_wmi_event { - u16 length; + __le16 length; /* 0x000: A hot key pressed or an event occurred * 0x00F: A sequence of hot keys are pressed */ - u16 type; - u16 event[]; -}; - -static const char *dell_wmi_aio_guids[] = { - EVENT_GUID1, - EVENT_GUID2, - NULL -}; - -MODULE_ALIAS("wmi:"EVENT_GUID1); -MODULE_ALIAS("wmi:"EVENT_GUID2); + __le16 type; + __le16 event[]; +} __packed; static const struct key_entry dell_wmi_aio_keymap[] = { { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, @@ -50,137 +53,92 @@ static const struct key_entry dell_wmi_aio_keymap[] = { { KE_END, 0 } }; -static struct input_dev *dell_wmi_aio_input_dev; - /* * The new WMI event data format will follow the dell_wmi_event structure * So, we will check if the buffer matches the format */ -static bool dell_wmi_aio_event_check(u8 *buffer, int length) +static bool dell_wmi_aio_event_check(const struct wmi_buffer *buffer) { - struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; + struct dell_wmi_event *event; + u16 length, type; - if (event == NULL || length < 6) + if (buffer->length < struct_size(event, event, 1)) return false; - if ((event->type == 0 || event->type == 0xf) && - event->length >= 2) + event = buffer->data; + length = le16_to_cpu(event->length); + type = le16_to_cpu(event->type); + if ((type == 0 || type == 0xf) && length >= 2) return true; return false; } -static void dell_wmi_aio_notify(union acpi_object *obj, void *context) +static void dell_wmi_aio_notify(struct wmi_device *wdev, const struct wmi_buffer *data) { - struct dell_wmi_event *event; - - if (obj) { - unsigned int scancode = 0; - - switch (obj->type) { - case ACPI_TYPE_INTEGER: - /* Most All-In-One correctly return integer scancode */ - scancode = obj->integer.value; - sparse_keymap_report_event(dell_wmi_aio_input_dev, - scancode, 1, true); - break; - case ACPI_TYPE_BUFFER: - if (dell_wmi_aio_event_check(obj->buffer.pointer, - obj->buffer.length)) { - event = (struct dell_wmi_event *) - obj->buffer.pointer; - scancode = event->event[0]; - } else { - /* Broken machines return the scancode in a - buffer */ - if (obj->buffer.pointer && - obj->buffer.length > 0) - scancode = obj->buffer.pointer[0]; - } - if (scancode) - sparse_keymap_report_event( - dell_wmi_aio_input_dev, - scancode, 1, true); - break; - } - } -} - -static int __init dell_wmi_aio_input_setup(void) -{ - int err; - - dell_wmi_aio_input_dev = input_allocate_device(); - - if (!dell_wmi_aio_input_dev) - return -ENOMEM; - - dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; - dell_wmi_aio_input_dev->phys = "wmi/input0"; - dell_wmi_aio_input_dev->id.bustype = BUS_HOST; - - err = sparse_keymap_setup(dell_wmi_aio_input_dev, - dell_wmi_aio_keymap, NULL); - if (err) { - pr_err("Unable to setup input device keymap\n"); - goto err_free_dev; - } - err = input_register_device(dell_wmi_aio_input_dev); - if (err) { - pr_info("Unable to register input device\n"); - goto err_free_dev; + struct dell_wmi_aio_data *drvdata = dev_get_drvdata(&wdev->dev); + const struct dell_wmi_event *new_event; + unsigned int scancode; + const u8 *old_event; + + if (dell_wmi_aio_event_check(data)) { + new_event = data->data; + scancode = le16_to_cpu(new_event->event[0]); + } else { + old_event = data->data; + scancode = old_event[0]; } - return 0; - -err_free_dev: - input_free_device(dell_wmi_aio_input_dev); - return err; -} - -static const char *dell_wmi_aio_find(void) -{ - int i; - for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) - if (wmi_has_guid(dell_wmi_aio_guids[i])) - return dell_wmi_aio_guids[i]; + guard(mutex)(&drvdata->input_lock); - return NULL; + sparse_keymap_report_event(drvdata->input_device, scancode, 1, true); } -static int __init dell_wmi_aio_init(void) +static int dell_wmi_aio_probe(struct wmi_device *wdev, const void *context) { - int err; - const char *guid; + struct dell_wmi_aio_data *data; + int ret; - guid = dell_wmi_aio_find(); - if (!guid) { - pr_warn("No known WMI GUID found\n"); - return -ENXIO; - } + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; - err = dell_wmi_aio_input_setup(); - if (err) - return err; + dev_set_drvdata(&wdev->dev, data); + ret = devm_mutex_init(&wdev->dev, &data->input_lock); + if (ret < 0) + return ret; - err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); - if (err) { - pr_err("Unable to register notify handler - %d\n", err); - input_unregister_device(dell_wmi_aio_input_dev); - return err; - } + data->input_device = devm_input_allocate_device(&wdev->dev); + if (!data->input_device) + return -ENOMEM; - return 0; -} + data->input_device->name = "Dell AIO WMI hotkeys"; + data->input_device->phys = "wmi/input0"; + data->input_device->id.bustype = BUS_HOST; -static void __exit dell_wmi_aio_exit(void) -{ - const char *guid; + ret = sparse_keymap_setup(data->input_device, dell_wmi_aio_keymap, NULL); + if (ret < 0) + return ret; - guid = dell_wmi_aio_find(); - wmi_remove_notify_handler(guid); - input_unregister_device(dell_wmi_aio_input_dev); + return input_register_device(data->input_device); } -module_init(dell_wmi_aio_init); -module_exit(dell_wmi_aio_exit); +static const struct wmi_device_id dell_wmi_aio_id_table[] = { + { EVENT_GUID1, NULL }, + { EVENT_GUID2, NULL }, + { } +}; +MODULE_DEVICE_TABLE(wmi, dell_wmi_aio_id_table); + +static struct wmi_driver dell_wmi_aio_driver = { + .driver = { + .name = "dell-wmi-aio", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = dell_wmi_aio_id_table, + .probe = dell_wmi_aio_probe, + .notify_new = dell_wmi_aio_notify, + .min_event_size = sizeof(u8), + .no_singleton = true, +}; +module_wmi_driver(dell_wmi_aio_driver); -- cgit v1.2.3 From 185c1646f5dc3a0a525837da08b9e4c7404b19c6 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 11:21:39 +0200 Subject: platform/x86/intel/vsec: Use bool for run_once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use bool for run_once in intel_vsec_pci_init(). Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260815092138.91072-3-thorsten.blum@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 5ee379715068..f86149e9ba16 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -676,8 +676,8 @@ static int intel_vsec_pci_init(struct pci_dev *pdev) { struct vsec_priv *priv = pci_get_drvdata(pdev); const struct intel_vsec_platform_info *info = priv->info; - int run_once = 0; bool found_any = false; + bool run_once = false; int num_caps; num_caps = hweight_long(info->caps); @@ -689,7 +689,7 @@ static int intel_vsec_pci_init(struct pci_dev *pdev) if (!run_once) { intel_vsec_skip_missing_dependencies(pdev); - run_once = 1; + run_once = true; } } -- cgit v1.2.3 From a33d2364dd4b555340bb9ff5f346697c93124ee8 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 11:34:32 +0200 Subject: platform/x86: think-lmi: Use ARRAY_SIZE() in tlmi_errstr_to_err() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the ARRAY_SIZE() helper to simplify tlmi_errstr_to_err(). Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260815093431.98950-2-thorsten.blum@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index a0e3fa766e37..bd8973c1369d 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -249,7 +249,7 @@ static int tlmi_errstr_to_err(const char *errstr) { int i; - for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) { + for (i = 0; i < ARRAY_SIZE(tlmi_errs); i++) { if (!strcmp(tlmi_errs[i].err_str, errstr)) return tlmi_errs[i].err_code; } -- cgit v1.2.3 From 3de2a51d29b132866b8a023241eb96e6439a47ce Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 18 Aug 2026 13:34:27 -0700 Subject: platform/x86: ISST: Avoid model check for recent servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To enable SST functions, a CPU model entry is required. This causes unnecessary delays in deploying new servers on older kernels. To avoid this, if no CPU model match is found, allow the SST common driver to load, when all of the following conditions are met: - Not running as a guest. - The platform is identified as a server via ACPI PM profile. - The CPU belongs to the Intel family 19 (decimal), which starts with Diamond Rapids. All recent servers are capable of enumerating SST features. - MSR_PM_LOGICAL_ID (0x54) is present to retrieve the PM logical ID. Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260818203427.2738209-1-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/intel/speed_select_if/isst_if_common.c | 57 +++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c index 1c48bf6d5457..4c04f0bc9b47 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -7,6 +7,7 @@ * Author: Srinivas Pandruvada */ +#include #include #include #include @@ -774,6 +775,54 @@ void isst_if_cdev_unregister(int device_type) } EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); +#ifdef CONFIG_ACPI + +static bool isst_acpi_pm_profile_server(void) +{ + if (acpi_gbl_FADT.preferred_profile == PM_ENTERPRISE_SERVER || + acpi_gbl_FADT.preferred_profile == PM_PERFORMANCE_SERVER) + return true; + + return false; +} + +#else + +static bool isst_acpi_pm_profile_server(void) +{ + return false; +} + +#endif + +static const struct x86_cpu_id isst_allowed_families[] = { + X86_MATCH_VENDOR_FAM(INTEL, 19, NULL), + {} +}; + +static bool isst_features_allowed(void) +{ + u64 data; + + /* For hypervisors, explicit model addition is required */ + if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + return false; + + /* SST is a server only feature */ + if (!isst_acpi_pm_profile_server()) + return false; + + /* + * Family 19 and the checks above guarantee the SST enumeration + * interfaces are present + */ + if (!x86_match_cpu(isst_allowed_families)) + return false; + + /* MSR_PM_LOGICAL_ID is required to get PM logical CPU ID */ + return !rdmsrq_safe(MSR_PM_LOGICAL_ID, &data); +} + #define SST_HPM_SUPPORTED 0x01 #define SST_MBOX_SUPPORTED 0x02 @@ -798,8 +847,13 @@ static int __init isst_if_common_init(void) const struct x86_cpu_id *id; id = x86_match_cpu(isst_cpu_ids); - if (!id) + if (!id) { + if (isst_features_allowed()) { + isst_hpm_support = true; + goto misc_reg; + } return -ENODEV; + } if (id->driver_data == SST_HPM_SUPPORTED) { isst_hpm_support = true; @@ -812,6 +866,7 @@ static int __init isst_if_common_init(void) return -ENODEV; } +misc_reg: return isst_misc_reg(); } module_init(isst_if_common_init) -- cgit v1.2.3 From 93f8ae8a26ba5c9bb43c207384db36756c7f357f Mon Sep 17 00:00:00 2001 From: Mohsen Tahmasebi Date: Wed, 19 Aug 2026 18:31:56 +0330 Subject: platform/x86: acer-wmi: use bool for boolean quirk fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use bool for quirk fields that only represent boolean values. Signed-off-by: Mohsen Tahmasebi Link: https://patch.msgid.link/20260819150205.19453-2-moh53n@moh53n.net Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 61ae622c93d9..fd011dda0fb1 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -400,11 +400,11 @@ struct quirk_entry { u8 mailled; s8 brightness; u8 bluetooth; - u8 turbo; + bool turbo; u8 cpu_fans; u8 gpu_fans; - u8 predator_v4; - u8 pwm; + bool predator_v4; + bool pwm; }; static struct quirk_entry *quirks; @@ -456,34 +456,34 @@ static struct quirk_entry quirk_acer_travelmate_2490 = { }; static struct quirk_entry quirk_acer_nitro_an515_58 = { - .predator_v4 = 1, - .pwm = 1, + .predator_v4 = true, + .pwm = true, }; static struct quirk_entry quirk_acer_predator_ph315_53 = { - .turbo = 1, + .turbo = true, .cpu_fans = 1, .gpu_fans = 1, }; static struct quirk_entry quirk_acer_predator_ph16_72 = { - .turbo = 1, + .turbo = true, .cpu_fans = 1, .gpu_fans = 1, - .predator_v4 = 1, - .pwm = 1, + .predator_v4 = true, + .pwm = true, }; static struct quirk_entry quirk_acer_predator_pt14_51 = { - .turbo = 1, + .turbo = true, .cpu_fans = 1, .gpu_fans = 1, - .predator_v4 = 1, - .pwm = 1, + .predator_v4 = true, + .pwm = true, }; static struct quirk_entry quirk_acer_predator_v4 = { - .predator_v4 = 1, + .predator_v4 = true, }; /* This AMW0 laptop has no bluetooth */ -- cgit v1.2.3 From 4bee415640e9fcb4fb0655a7ba3ea96eff424ac9 Mon Sep 17 00:00:00 2001 From: Mohsen Tahmasebi Date: Wed, 19 Aug 2026 18:31:57 +0330 Subject: platform/x86: acer-wmi: split hwmon from platform profile quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Acer laptops expose the same WMI sensor interface without necessarily supporting platform profile functionality. Currently, ACER_CAP_HWMON is enabled only through predator_v4 quirk, but this quirk enables both ACER_CAP_PLATFORM_PROFILE and ACER_CAP_HWMON. This commit splits hwmon support into its own quirk field, renames predator_v4 to platform_profile, and updates existing predator_v4 quirks to enable both capabilities. Signed-off-by: Mohsen Tahmasebi Link: https://patch.msgid.link/20260819150205.19453-3-moh53n@moh53n.net Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index fd011dda0fb1..e551110dc811 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -403,7 +403,8 @@ struct quirk_entry { bool turbo; u8 cpu_fans; u8 gpu_fans; - bool predator_v4; + bool hwmon; + bool platform_profile; bool pwm; }; @@ -421,9 +422,11 @@ static void __init set_quirks(void) interface->capability |= ACER_CAP_TURBO_OC | ACER_CAP_TURBO_LED | ACER_CAP_TURBO_FAN; - if (quirks->predator_v4) - interface->capability |= ACER_CAP_PLATFORM_PROFILE | - ACER_CAP_HWMON; + if (quirks->hwmon) + interface->capability |= ACER_CAP_HWMON; + + if (quirks->platform_profile) + interface->capability |= ACER_CAP_PLATFORM_PROFILE; if (quirks->pwm) interface->capability |= ACER_CAP_PWM; @@ -456,7 +459,8 @@ static struct quirk_entry quirk_acer_travelmate_2490 = { }; static struct quirk_entry quirk_acer_nitro_an515_58 = { - .predator_v4 = true, + .hwmon = true, + .platform_profile = true, .pwm = true, }; @@ -470,7 +474,8 @@ static struct quirk_entry quirk_acer_predator_ph16_72 = { .turbo = true, .cpu_fans = 1, .gpu_fans = 1, - .predator_v4 = true, + .hwmon = true, + .platform_profile = true, .pwm = true, }; @@ -478,12 +483,14 @@ static struct quirk_entry quirk_acer_predator_pt14_51 = { .turbo = true, .cpu_fans = 1, .gpu_fans = 1, - .predator_v4 = true, + .hwmon = true, + .platform_profile = true, .pwm = true, }; static struct quirk_entry quirk_acer_predator_v4 = { - .predator_v4 = true, + .hwmon = true, + .platform_profile = true, }; /* This AMW0 laptop has no bluetooth */ @@ -2194,7 +2201,7 @@ static const struct platform_profile_ops acer_predator_v4_platform_profile_ops = static int acer_platform_profile_setup(struct platform_device *device) { - if (quirks->predator_v4) { + if (quirks->platform_profile) { platform_profile_device = devm_platform_profile_register( &device->dev, "acer-wmi", NULL, &acer_predator_v4_platform_profile_ops); if (IS_ERR(platform_profile_device)) @@ -2211,7 +2218,7 @@ static int acer_thermal_profile_change(void) * This mode key will either cycle through each mode or toggle the * most performant profile. */ - if (quirks->predator_v4) { + if (quirks->platform_profile) { u8 current_tp; int err, tp; -- cgit v1.2.3 From 3bd07ec2893bef0220cd63589c19e5be354fe835 Mon Sep 17 00:00:00 2001 From: Mohsen Tahmasebi Date: Wed, 19 Aug 2026 18:31:58 +0330 Subject: platform/x86: acer-wmi: enable hwmon on Aspire A315-58G MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Acer Aspire A315-58G supports the Acer hwmon interface and reports CPU, GPU and external temperatures. Note that the GPU temperature reports 0 when the Nvidia MX350 GPU is in D3cold, but reports a valid value when the GPU is in D3hot/D0. This commit enables hwmon for this model. Signed-off-by: Mohsen Tahmasebi Link: https://patch.msgid.link/20260819150205.19453-4-moh53n@moh53n.net Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index e551110dc811..d3d741aad556 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -454,6 +454,10 @@ static struct quirk_entry quirk_acer_aspire_1520 = { .brightness = -1, }; +static struct quirk_entry quirk_acer_aspire_a315_58g = { + .hwmon = true, +}; + static struct quirk_entry quirk_acer_travelmate_2490 = { .mailled = 1, }; @@ -642,6 +646,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, .driver_data = &quirk_acer_travelmate_2490, }, + { + .callback = dmi_matched, + .ident = "Acer Aspire A315-58G", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"), + }, + .driver_data = &quirk_acer_aspire_a315_58g, + }, { .callback = dmi_matched, .ident = "Acer TravelMate 2490", -- cgit v1.2.3 From 62df6f88a76727af69f5f9b4543539b1854fdb7d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 19 Aug 2026 10:38:04 -0700 Subject: platform/x86/intel/pmc: Add rugged Panther Lake support to intel_pmc_core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add rugged Panther Lake support to intel_pmc_core driver. Signed-off-by: Srinivas Pandruvada Reviewed-by: Xi Pardee Link: https://patch.msgid.link/20260819173804.1184250-1-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmc/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 9f77c0716e59..71be313bdf0e 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -1849,6 +1849,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev), X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, &ptl_pmc_dev), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev), X86_MATCH_VFM(INTEL_NOVALAKE, &nvl_s_pmc_dev), X86_MATCH_VFM(INTEL_NOVALAKE_L, &nvl_h_pmc_dev), -- cgit v1.2.3 From aa36a3f35df8475c8e3a3a104a789cfdbc9cad71 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 19 Aug 2026 10:39:33 -0700 Subject: platform/x86: intel-uncore-freq: Add support for rugged Panther Lake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add uncore frequency support for rugged Panther Lake. Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260819173933.1184291-1-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 667f2c8b9594..3bc57b553eff 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -257,6 +257,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_LUNARLAKE_M, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL), -- cgit v1.2.3 From 885b55bcd2336c578821b0788aac95c4679d68e3 Mon Sep 17 00:00:00 2001 From: Ruzal Daminov Date: Wed, 19 Aug 2026 11:38:36 +0300 Subject: platform/x86: huawei-wmi: add camera toggle keycode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Honor MagicBook laptops emit WMI event code 0x288 when pressing the camera e-shutter / privacy toggle key (Fn+F8 on Honor). Add the missing 0x288 keycode to huawei_wmi_keymap[] in correct numerical order and map it to KEY_CAMERA_ACCESS_TOGGLE. Tested on Honor MagicBook X14 Plus (FMI-76, AMD Ryzen 8845HS). Signed-off-by: Ruzal Daminov Link: https://patch.msgid.link/20260819083836.1410-1-daminovruzal7@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/huawei-wmi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index d6aaf14d66a5..96ee9a97c9b8 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -79,6 +79,7 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY, 0x285, { KEY_VOLUMEDOWN } }, { KE_KEY, 0x286, { KEY_VOLUMEUP } }, { KE_KEY, 0x287, { KEY_MICMUTE } }, + { KE_KEY, 0x288, { KEY_CAMERA_ACCESS_TOGGLE } }, { KE_KEY, 0x289, { KEY_WLAN } }, // Huawei |M| key { KE_KEY, 0x28a, { KEY_CONFIG } }, -- cgit v1.2.3 From 6241e07ab561fa017b0eb63f785b98aefcfb2477 Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Sun, 23 Aug 2026 12:54:07 +0530 Subject: platform/x86: hp-wmi: Add OMEN board 8A18 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 17-ck1000nw (board ID: 8A18) supports the existing OMEN thermal profile handling. Add the DMI board name to omen_thermal_profile_boards[] so that the existing thermal profile support is enabled for this board. This enables the existing fan control and platform profile handling for 8A18. The board has been reported as working with this configuration in OmenCtl. Link: https://github.com/yunusemreyl/OmenCtl/commit/ea60e1c52859f0f299e092ae0478cdc7de82122f Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260823072407.74512-1-technosfan14@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index b2773fc1aca4..68ea52045a5d 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -207,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", - "8A15", "8A42", "8A43", + "8A15", "8A18", "8A42", "8A43", "8BAD", "8C58", "8E41", -- cgit v1.2.3 From 84192e252ebeffa973bb6900c60500bd88adf3fe Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Sun, 23 Aug 2026 13:39:46 +0530 Subject: platform/x86: hp-wmi: Add OMEN board 8D42 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN MAX 16-ah0xxx (board ID: 8D42) uses the existing OMEN v1 WMI interface but does not use the standard EC thermal profile parameters. Furthermore, it is the closest sibling of board 8D41, which is already included in the table with omen_v1_no_ec_board_params. Add the DMI board name to hp_wmi_feature_boards[] and map it to omen_v1_no_ec_board_params. This enables the existing board-specific handling for 8D42, including platform profile and fan control support. Link: https://github.com/yunusemreyl/OmenCtl/issues/93 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260823080946.103082-1-technosfan14@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 68ea52045a5d..6ecc033e5af3 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -327,6 +327,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, .driver_data = (void *)&omen_v1_no_ec_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D42") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, .driver_data = (void *)&omen_v1_no_ec_board_params, -- cgit v1.2.3 From 0d4cd92b41dd33a3112959f9be86002ff309de73 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Thu, 10 Sep 2026 06:59:55 -0700 Subject: platform/x86/intel/pmt: Block mmap when callback is present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the introduction of the .read_telem() callback, control for access to a devices data area must be done by the callback owner. This means that an area cannot be mmap-ed with the default PMT handler. If the .read_telem() callback is present, do not support mmap. Fixes: 045a513040cc ("platform/x86/intel/pmt: Use PMT callbacks") Cc: stable@vger.kernel.org Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260910135954.1586360-2-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/class.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index d0ab8e33c62a..c25892a51180 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -417,7 +417,14 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, sysfs_bin_attr_init(&entry->pmt_bin_attr); entry->pmt_bin_attr.attr.name = ns->name; entry->pmt_bin_attr.attr.mode = 0440; - entry->pmt_bin_attr.mmap = intel_pmt_mmap; + + /* + * The read_telem callback controls access to the memory area. Block + * usage if the read_telem callback is present. + */ + if (!(entry->cb && entry->cb->read_telem)) + entry->pmt_bin_attr.mmap = intel_pmt_mmap; + entry->pmt_bin_attr.read = intel_pmt_read; entry->pmt_bin_attr.size = entry->size; -- cgit v1.2.3 From b86e5062cb881f6e5c1ddb8913c1357a9508f6db Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Wed, 23 Sep 2026 11:11:16 -0700 Subject: platform/x86/intel/pmt: complete pcidev to device update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The update that moved struct pci_dev usage to struct device is incomplete. Only telemetry endpoints are covered. Other PMT features (crashlog) are now blocked from using the callback mechanism. Change struct intel_pmt_entry pci_dev member to device. Update callback usage to use the intel_pmt_entry rather than the telemetry endpoint. Reviewed-by: Rodrigo Vivi Fixes: 353042d54d82 ("platform/x86/intel/vsec: Switch exported helpers from pci_dev to device") Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260923181115.2514193-23-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/class.c | 5 ++--- drivers/platform/x86/intel/pmt/class.h | 3 +-- drivers/platform/x86/intel/pmt/discovery.c | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index c25892a51180..c4f73e4e126a 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -100,7 +100,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj, if (count > entry->size - off) count = entry->size - off; - count = pmt_telem_read_mmio(entry->ep->dev, entry->cb, entry->header.guid, buf, + count = pmt_telem_read_mmio(entry->dev, entry->cb, entry->header.guid, buf, entry->base, off, count); return count; @@ -286,8 +286,6 @@ static int pmt_resolve_access_pci(struct intel_pmt_entry *entry, return -EINVAL; } - entry->pcidev = pci_dev; - return 0; } @@ -365,6 +363,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, entry->guid = header->guid; entry->size = header->size; entry->cb = ivdev->priv_data; + entry->dev = ivdev->dev; return 0; } diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index a0ece4fc3837..258cb460e61c 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -20,7 +20,6 @@ #define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) struct device; -struct pci_dev; extern struct class intel_pmt_class; struct telem_endpoint { @@ -42,7 +41,7 @@ struct intel_pmt_header { struct intel_pmt_entry { struct telem_endpoint *ep; - struct pci_dev *pcidev; + struct device *dev; struct intel_pmt_header header; u32 disc_header[PMT_DISC_DWORDS]; struct bin_attribute pmt_bin_attr; diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index c482368bfaae..f4203d240f54 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -609,7 +609,7 @@ void intel_pmt_get_features(struct intel_pmt_entry *entry) mutex_lock(&feature_list_lock); list_for_each_entry(feature, &pmt_feature_list, list) { - if (feature->priv->parent != entry->ep->dev) + if (feature->priv->parent != entry->dev) continue; pmt_get_features(entry, feature); -- cgit v1.2.3 From 5724621ad5b8083b3fdd3fd38599d26d98a26bbb Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Wed, 23 Sep 2026 11:11:17 -0700 Subject: platform/x86/intel/pmt: refactor rmw with a return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upcoming changes will include possible failures from HW accesses. Refactor pmt_crashlog_rwm() with a return value. Update all necessary usage to use the return code. Reviewed-by: Rodrigo Vivi Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260923181115.2514193-24-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 90 ++++++++++++++++--------------- 1 file changed, 46 insertions(+), 44 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index f936daf99e4d..831cff743803 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -125,7 +125,7 @@ struct pmt_crashlog_priv { */ /* Read, modify, write the control register, setting or clearing @bit based on @set */ -static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) +static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) { const struct crashlog_control *control = &crashlog->info->control; struct intel_pmt_entry *entry = &crashlog->entry; @@ -139,6 +139,8 @@ static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) reg &= ~bit; writel(reg, entry->disc_table + control->offset); + + return 0; } /* Read the status register and see if the specified @bit is set */ @@ -181,20 +183,20 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_typ return false; } -static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog, - bool disable) +static int pmt_crashlog_set_disable(struct crashlog_entry *crashlog, + bool disable) { - pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable); + return pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable); } -static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog) +static int pmt_crashlog_set_clear(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true); + return pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true); } -static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog) +static int pmt_crashlog_set_execute(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true); + return pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true); } static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog) @@ -207,9 +209,9 @@ static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog) return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed); } -static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog) +static int pmt_crashlog_set_consumed(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true); + return pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true); } static bool pmt_crashlog_error(struct crashlog_entry *crashlog) @@ -222,9 +224,9 @@ static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog) return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed); } -static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog) +static int pmt_crashlog_set_rearm(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true); + return pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true); } /* @@ -245,13 +247,13 @@ clear_store(struct device *dev, struct device_attribute *attr, { struct crashlog_entry *crashlog; bool clear; - int result; + int ret; crashlog = dev_get_drvdata(dev); - result = kstrtobool(buf, &clear); - if (result) - return result; + ret = kstrtobool(buf, &clear); + if (ret) + return ret; /* set bit only */ if (!clear) @@ -259,9 +261,9 @@ clear_store(struct device *dev, struct device_attribute *attr, guard(mutex)(&crashlog->control_mutex); - pmt_crashlog_set_clear(crashlog); + ret = pmt_crashlog_set_clear(crashlog); - return count; + return ret ?: count; } static DEVICE_ATTR_RW(clear); @@ -280,13 +282,13 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu { struct crashlog_entry *crashlog; bool consumed; - int result; + int ret; crashlog = dev_get_drvdata(dev); - result = kstrtobool(buf, &consumed); - if (result) - return result; + ret = kstrtobool(buf, &consumed); + if (ret) + return ret; /* set bit only */ if (!consumed) @@ -300,9 +302,9 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu if (!pmt_crashlog_complete(crashlog)) return -EEXIST; - pmt_crashlog_set_consumed(crashlog); + ret = pmt_crashlog_set_consumed(crashlog); - return count; + return ret ?: count; } static DEVICE_ATTR_RW(consumed); @@ -321,19 +323,19 @@ enable_store(struct device *dev, struct device_attribute *attr, { struct crashlog_entry *crashlog; bool enabled; - int result; + int ret; crashlog = dev_get_drvdata(dev); - result = kstrtobool(buf, &enabled); - if (result) - return result; + ret = kstrtobool(buf, &enabled); + if (ret) + return ret; guard(mutex)(&crashlog->control_mutex); - pmt_crashlog_set_disable(crashlog, !enabled); + ret = pmt_crashlog_set_disable(crashlog, !enabled); - return count; + return ret ?: count; } static DEVICE_ATTR_RW(enable); @@ -362,13 +364,13 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf, { struct crashlog_entry *crashlog; bool rearm; - int result; + int ret; crashlog = dev_get_drvdata(dev); - result = kstrtobool(buf, &rearm); - if (result) - return result; + ret = kstrtobool(buf, &rearm); + if (ret) + return ret; /* set only */ if (!rearm) @@ -376,9 +378,9 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf, guard(mutex)(&crashlog->control_mutex); - pmt_crashlog_set_rearm(crashlog); + ret = pmt_crashlog_set_rearm(crashlog); - return count; + return ret ?: count; } static DEVICE_ATTR_RW(rearm); @@ -400,13 +402,13 @@ trigger_store(struct device *dev, struct device_attribute *attr, { struct crashlog_entry *crashlog; bool trigger; - int result; + int ret; crashlog = dev_get_drvdata(dev); - result = kstrtobool(buf, &trigger); - if (result) - return result; + ret = kstrtobool(buf, &trigger); + if (ret) + return ret; guard(mutex)(&crashlog->control_mutex); @@ -415,17 +417,17 @@ trigger_store(struct device *dev, struct device_attribute *attr, return -EBUSY; if (!trigger) { - pmt_crashlog_set_clear(crashlog); - return count; + ret = pmt_crashlog_set_clear(crashlog); + return ret ?: count; } /* we cannot trigger a new crash if one is still pending */ if (pmt_crashlog_complete(crashlog)) return -EEXIST; - pmt_crashlog_set_execute(crashlog); + ret = pmt_crashlog_set_execute(crashlog); - return count; + return ret ?: count; } static DEVICE_ATTR_RW(trigger); -- cgit v1.2.3 From 8ee5b655e0a6fcfe65519d942114c4736bf601f3 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Wed, 23 Sep 2026 11:11:18 -0700 Subject: platform/x86/intel/pmt: refactor rc with a return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upcoming changes will include possible failures from HW accesses. Refactor pmt_crashlog_rc() with a return value. Update all necessary usage to use the return code. Reviewed-by: Rodrigo Vivi Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260923181115.2514193-25-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 99 +++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 831cff743803..200373cbb020 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -144,24 +144,26 @@ static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) } /* Read the status register and see if the specified @bit is set */ -static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit) +static int pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit, bool *state) { const struct crashlog_status *status = &crashlog->info->status; u32 reg = readl(crashlog->entry.disc_table + status->offset); - return !!(reg & bit); + *state = !!(reg & bit); + + return 0; } -static bool pmt_crashlog_complete(struct crashlog_entry *crashlog) +static int pmt_crashlog_complete(struct crashlog_entry *crashlog, bool *state) { /* return current value of the crashlog complete flag */ - return pmt_crashlog_rc(crashlog, crashlog->info->status.complete); + return pmt_crashlog_rc(crashlog, crashlog->info->status.complete, state); } -static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog) +static int pmt_crashlog_disabled(struct crashlog_entry *crashlog, bool *state) { /* return current value of the crashlog disabled flag */ - return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled); + return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled, state); } static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version) @@ -199,14 +201,14 @@ static int pmt_crashlog_set_execute(struct crashlog_entry *crashlog) return pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true); } -static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog) +static int pmt_crashlog_cleared(struct crashlog_entry *crashlog, bool *state) { - return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared); + return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared, state); } -static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog) +static int pmt_crashlog_consumed(struct crashlog_entry *crashlog, bool *state) { - return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed); + return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed, state); } static int pmt_crashlog_set_consumed(struct crashlog_entry *crashlog) @@ -214,14 +216,14 @@ static int pmt_crashlog_set_consumed(struct crashlog_entry *crashlog) return pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true); } -static bool pmt_crashlog_error(struct crashlog_entry *crashlog) +static int pmt_crashlog_error(struct crashlog_entry *crashlog, bool *state) { - return pmt_crashlog_rc(crashlog, crashlog->info->status.error); + return pmt_crashlog_rc(crashlog, crashlog->info->status.error, state); } -static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog) +static int pmt_crashlog_rearm(struct crashlog_entry *crashlog, bool *state) { - return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed); + return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed, state); } static int pmt_crashlog_set_rearm(struct crashlog_entry *crashlog) @@ -236,7 +238,12 @@ static ssize_t clear_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog = dev_get_drvdata(dev); - bool cleared = pmt_crashlog_cleared(crashlog); + bool cleared; + int ret; + + ret = pmt_crashlog_cleared(crashlog, &cleared); + if (ret) + return ret; return sysfs_emit(buf, "%d\n", cleared); } @@ -271,7 +278,12 @@ static ssize_t consumed_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog = dev_get_drvdata(dev); - bool consumed = pmt_crashlog_consumed(crashlog); + bool consumed; + int ret; + + ret = pmt_crashlog_consumed(crashlog, &consumed); + if (ret) + return ret; return sysfs_emit(buf, "%d\n", consumed); } @@ -281,7 +293,9 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu size_t count) { struct crashlog_entry *crashlog; + bool complete; bool consumed; + bool disabled; int ret; crashlog = dev_get_drvdata(dev); @@ -296,10 +310,16 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu guard(mutex)(&crashlog->control_mutex); - if (pmt_crashlog_disabled(crashlog)) + ret = pmt_crashlog_disabled(crashlog, &disabled); + if (ret) + return ret; + if (disabled) return -EBUSY; - if (!pmt_crashlog_complete(crashlog)) + ret = pmt_crashlog_complete(crashlog, &complete); + if (ret) + return ret; + if (!complete) return -EEXIST; ret = pmt_crashlog_set_consumed(crashlog); @@ -312,9 +332,14 @@ static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog = dev_get_drvdata(dev); - bool enabled = !pmt_crashlog_disabled(crashlog); + bool disabled; + int ret; - return sprintf(buf, "%d\n", enabled); + ret = pmt_crashlog_disabled(crashlog, &disabled); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", !disabled); } static ssize_t @@ -343,7 +368,12 @@ static ssize_t error_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog = dev_get_drvdata(dev); - bool error = pmt_crashlog_error(crashlog); + bool error; + int ret; + + ret = pmt_crashlog_error(crashlog, &error); + if (ret) + return ret; return sysfs_emit(buf, "%d\n", error); } @@ -353,7 +383,12 @@ static ssize_t rearm_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog = dev_get_drvdata(dev); - int rearmed = pmt_crashlog_rearm(crashlog); + bool rearmed; + int ret; + + ret = pmt_crashlog_rearm(crashlog, &rearmed); + if (ret) + return ret; return sysfs_emit(buf, "%d\n", rearmed); } @@ -389,11 +424,15 @@ trigger_show(struct device *dev, struct device_attribute *attr, char *buf) { struct crashlog_entry *crashlog; bool trigger; + int ret; crashlog = dev_get_drvdata(dev); - trigger = pmt_crashlog_complete(crashlog); - return sprintf(buf, "%d\n", trigger); + ret = pmt_crashlog_complete(crashlog, &trigger); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", trigger); } static ssize_t @@ -401,6 +440,8 @@ trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct crashlog_entry *crashlog; + bool complete; + bool disabled; bool trigger; int ret; @@ -413,7 +454,10 @@ trigger_store(struct device *dev, struct device_attribute *attr, guard(mutex)(&crashlog->control_mutex); /* if device is currently disabled, return busy */ - if (pmt_crashlog_disabled(crashlog)) + ret = pmt_crashlog_disabled(crashlog, &disabled); + if (ret) + return ret; + if (disabled) return -EBUSY; if (!trigger) { @@ -422,7 +466,10 @@ trigger_store(struct device *dev, struct device_attribute *attr, } /* we cannot trigger a new crash if one is still pending */ - if (pmt_crashlog_complete(crashlog)) + ret = pmt_crashlog_complete(crashlog, &complete); + if (ret) + return ret; + if (complete) return -EEXIST; ret = pmt_crashlog_set_execute(crashlog); -- cgit v1.2.3 From 7fdfccf87676a17ab7a24b67595c701dd8d913a5 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Wed, 23 Sep 2026 11:11:19 -0700 Subject: platform/x86/intel/pmt: Add register access callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some HW does not have direct MMIO access to PMT control and data features. Augment the current callback infrastructure (data access) to allow a registered driver to customize read/write access to the control paths for PMT usage. Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260923181115.2514193-26-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 46 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 200373cbb020..d7018b495217 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -124,12 +126,36 @@ struct pmt_crashlog_priv { * I/O */ +static int pmt_crashlog_read_reg(struct intel_pmt_entry *entry, u32 *reg, u32 offset) +{ + int ret; + + *reg = 0; + + if (entry->cb && entry->cb->read_reg) { + ret = entry->cb->read_reg(entry->dev, entry->header.guid, reg, offset); + if (ret) { + dev_err_ratelimited(entry->dev, "failed to read reg: %d\n", ret); + return ret; + } + } else { + *reg = readl(entry->disc_table + offset); + } + + return 0; +} + /* Read, modify, write the control register, setting or clearing @bit based on @set */ static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) { const struct crashlog_control *control = &crashlog->info->control; struct intel_pmt_entry *entry = &crashlog->entry; - u32 reg = readl(entry->disc_table + control->offset); + u32 reg; + int ret; + + ret = pmt_crashlog_read_reg(entry, ®, control->offset); + if (ret) + return ret; reg &= ~control->trigger_mask; @@ -138,7 +164,15 @@ static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) else reg &= ~bit; - writel(reg, entry->disc_table + control->offset); + if (entry->cb && entry->cb->write_reg) { + ret = entry->cb->write_reg(entry->dev, entry->header.guid, reg, control->offset); + if (ret) { + dev_err_ratelimited(entry->dev, "failed to write reg: %d\n", ret); + return ret; + } + } else { + writel(reg, entry->disc_table + control->offset); + } return 0; } @@ -147,7 +181,13 @@ static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) static int pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit, bool *state) { const struct crashlog_status *status = &crashlog->info->status; - u32 reg = readl(crashlog->entry.disc_table + status->offset); + struct intel_pmt_entry *entry = &crashlog->entry; + u32 reg; + int ret; + + ret = pmt_crashlog_read_reg(entry, ®, status->offset); + if (ret) + return ret; *state = !!(reg & bit); -- cgit v1.2.3 From 5871df43fccf1e0cdcba873396b7951f31d5b86e Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Wed, 23 Sep 2026 11:11:20 -0700 Subject: platform/x86/intel/pmt: Do not remap when using callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A callback indicates that the parent driver is responsible for accessing the data area. Creating a PMT memory remap is redundant. If a read_telem callback has been provided, do not create a remap. Enforce API usage where necessary. Clean up open coded resource usage. Signed-off-by: Michael J. Ruhl Link: https://patch.msgid.link/20260923181115.2514193-27-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/class.c | 21 +++++++++++++-------- drivers/platform/x86/intel/pmt/class.h | 2 +- drivers/platform/x86/intel/pmt/telemetry.c | 3 +++ 3 files changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index c4f73e4e126a..2b11c4012f6a 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -373,7 +374,6 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, struct device *parent) { struct intel_vsec_device *ivdev = dev_to_ivdev(parent); - struct resource res = {0}; struct device *dev; int ret; @@ -403,14 +403,19 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, if (!entry->size) return 0; - res.start = entry->base_addr; - res.end = res.start + entry->size - 1; - res.flags = IORESOURCE_MEM; + /* + * The read_telem callback is responsible for this mapping, and may have + * different requirements for use. If the callback is present do not + * create the map. + */ + if (!(entry->cb && entry->cb->read_telem)) { + struct resource res = DEFINE_RES_MEM(entry->base_addr, entry->size); - entry->base = devm_ioremap_resource(dev, &res); - if (IS_ERR(entry->base)) { - ret = PTR_ERR(entry->base); - goto fail_ioremap; + entry->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(entry->base)) { + ret = PTR_ERR(entry->base); + goto fail_ioremap; + } } sysfs_bin_attr_init(&entry->pmt_bin_attr); diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index 258cb460e61c..aeb04afb67cf 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -48,7 +48,7 @@ struct intel_pmt_entry { const struct attribute_group *attr_grp; struct kobject *kobj; void __iomem *disc_table; - void __iomem *base; + void __iomem *base; /* unused if cb->read_telem is in use */ struct pmt_callbacks *cb; unsigned long base_addr; size_t size; diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index 953f35b6daec..5423a4dfce08 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -329,6 +329,9 @@ int pmt_telem_read32(struct telem_endpoint *ep, u32 id, u32 *data, u32 count) if (!ep->present) return -ENODEV; + if (!ep->base) + return -EIO; + offset = SAMPLE_ID_OFFSET32(id); size = ep->header.size; -- cgit v1.2.3 From 7de070313d444e37d2e0e2c3d026b887e69f1558 Mon Sep 17 00:00:00 2001 From: David Stephenson Date: Tue, 25 Aug 2026 19:36:21 +0000 Subject: platform: arm64: thinkpad-t14s-ec: don't send KEY_KBDILLUMTOGGLE on Fn+Space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the T14s, the EC already steps the keyboard backlight when you press Fn+Space: off, then low, then high. We report that change with t14s_kbd_bl_update() (brightness_hw_changed). We then also send KEY_KBDILLUMTOGGLE. The extra key is the problem. Desktops treat it as "please toggle the light." GNOME calls Keyboard.Toggle() and changes the LED itself. That fights the EC's three steps. The on-screen bar also stays empty, because Toggle() always returns 0%. thinkpad_acpi on x86 already skips the key when the firmware changed the light. It only uses brightness_hw_changed. Do the same here: keep the LED update, do not send KEY_KBDILLUMTOGGLE. Tested on a Lenovo ThinkPad T14s Gen 6 (21N1, Snapdragon X Elite) with GNOME. After this change the OSD bar tracks off / 50% / 100%. Signed-off-by: David Stephenson Reviewed-by: Sebastian Reichel Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260825193556.133277-1-git@davidstephenson.net Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/lenovo-thinkpad-t14s.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c index 191ecd7f85b5..7a875877c8f3 100644 --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c @@ -488,8 +488,14 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data) case T14S_EC_EVT_NONE: break; case T14S_EC_EVT_KEY_FN_SPACE: + /* + * Firmware already cycles the keyboard backlight + * (off / low / high). Notify userspace through the LED + * class only. Emitting KEY_KBDILLUMTOGGLE as well makes + * desktop environments toggle the LED a second time. + */ t14s_kbd_bl_update(ec); - fallthrough; + break; case T14S_EC_EVT_KEY_FN_F4: case T14S_EC_EVT_KEY_FN_F7: case T14S_EC_EVT_KEY_FN_4: -- cgit v1.2.3 From 212309c7453ce14793041424a25f950fa64bf516 Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Sat, 19 Sep 2026 00:15:39 +0800 Subject: platform/x86: thinkpad_acpi: Add X9-14 Gen 1 to non-standard thermal/fan quirk tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo ThinkPad X9-14 Gen 1 uses a non-standard Embedded Controller firmware (ECFW) whose thermal and fan registers are not located at the classic addresses. On this model the thermal registers sit at 0xA8-0xAF / 0xB8-0xBF and the fan registers use the non-standard offsets, instead of the legacy 0x78-0x7F / 0xC0-0xC7 (thermal) and 0x2f / 0x84 (fan). Because the model is not covered by the existing quirk tables, the driver probes the legacy thermal addresses during init, reads back 0x00 from every register, concludes the EC is "misbehaving" and disables all thermal sensor access: thinkpad_acpi: ThinkPad ACPI EC access misbehaving, disabling thermal sensors access Fan access is affected the same way, leaving fan1/fan2 reporting 0 RPM. The infrastructure for these non-standard ECFW models is already in place (commit 301c1904d638 ("platform/x86: thinkpad_acpi: Fix to correct wrong temp reporting on some ThinkPads")); the X9-14 Gen 1 simply was not added to the model lists yet. Add its BIOS model code N4D to both the thermal and fan quirk tables so that: - thermal_read_mode_check() selects TPACPI_THERMAL_TPEC_12 and reads the 0xA8/0xB8 registers, and - fan_init() selects the non-standard fan register addresses. Reported-by: thisisamirv Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221228 Signed-off-by: Huang Wei Link: https://patch.msgid.link/20260918161539.2676619-1-huangwei@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index e2fe7a6a9d56..a6a83f7ba643 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -6083,6 +6083,7 @@ static const struct tpacpi_quirk thermal_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'T', true), /* 11e Gen5 GL*/ TPACPI_Q_LNV3('R', '1', 'D', true), /* 11e Gen5 GL-R*/ TPACPI_Q_LNV3('R', '0', 'V', true), /* 11e Gen5 KL-Y*/ + TPACPI_Q_LNV3('N', '4', 'D', true), /* X9-14 Gen 1 */ }; static enum thermal_access_mode thermal_read_mode; @@ -8907,6 +8908,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ + TPACPI_Q_LNV3('N', '4', 'D', TPACPI_FAN_NS), /* X9-14 Gen 1 */ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ -- cgit v1.2.3 From 325bcc126cf7a512a0e2da0efddad665f69badac Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 26 Aug 2026 11:36:11 -0500 Subject: platform/x86/amd/pmf: Add AMDI0112 ACPI ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AMDI0112 ID is used to bind the PMF driver to some platforms. It doesn't have any Linux side code changes relative to the most similar AMDI0105. Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260826163611.2268377-1-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index b4eae65e675b..5966190d1496 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -454,6 +454,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0107", 0}, {"AMDI0108", 0}, {"AMDI0109", 0}, + {"AMDI0112", 0}, { } }; MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); -- cgit v1.2.3 From 73acb8e40710f5b9e06b5d221e6208b279eab5fe Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Thu, 27 Aug 2026 20:52:26 +0530 Subject: platform/x86: hp-wmi: Add OMEN board 8D40 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN Slim 16t-an000 (board ID: 8D40) uses the existing OMEN v1 WMI interface but does not use the standard EC thermal profile parameters. Furthermore, it is the closest sibling of board 8D41 and 8D42, which are already included in the table with omen_v1_no_ec_board_params. Add the DMI board name to hp_wmi_feature_boards[] and map it to omen_v1_no_ec_board_params. This enables the existing board-specific handling for 8D40, including platform profile and fan control support. (Tested-by tag not included due to invalid email address, see the github link below.) Link: https://github.com/CachyOS/kernel-patches/issues/157 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260827152226.35186-1-technosfan14@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 6ecc033e5af3..fba6d202672a 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -323,6 +323,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D40") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, .driver_data = (void *)&omen_v1_no_ec_board_params, -- cgit v1.2.3 From fee599880ac2d75117910e3741ff7426e4104735 Mon Sep 17 00:00:00 2001 From: Rickey Bartlett Date: Fri, 28 Aug 2026 07:57:56 -0700 Subject: platform/x86/dell: alienware-wmi-wmax: add Alienware Area-51 AAT2250 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the Alienware Area-51 (2025 desktop, model AAT2250) to the AWCC DMI table. G-Mode is not applicable to this desktop, so generic_quirks is used. The full product name is matched to avoid colliding with the "Alienware Area-51m" and "Alienware 16/18 Area-51" laptop entries. Tested on hardware with BIOS 1.6.0: loading with force_platform_profile=1 force_hwmon=1 exposes a working platform profile (quiet/balanced/balanced-performance/custom) and a fully functional hwmon interface (6 fans - CPU, 3 top, 2 front - with fan boost control and temperature sensors). Signed-off-by: Rickey Bartlett Reviewed-by: Kurt Borja Link: https://patch.msgid.link/20260828145756.341733-1-subtexel@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/alienware-wmi-wmax.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index d1b4df91401b..2b0ffcb56a95 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -121,6 +121,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = { }, .driver_data = &g_series_quirks, }, + { + .ident = "Alienware Area-51 AAT2250", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51 AAT2250"), + }, + .driver_data = &generic_quirks, + }, { .ident = "Alienware Area-51m", .matches = { -- cgit v1.2.3 From cde132040fcbcad19f7b6a1988873513f5679eeb Mon Sep 17 00:00:00 2001 From: Krishna Chomal Date: Sat, 29 Aug 2026 12:48:43 +0530 Subject: platform/x86: hp-wmi: Add support for Omen 16-am0xxx (8D3F) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP Omen 16-am0xxx (board ID: 8D3F) has the same WMI interface as other Victus S boards, but requires quirks for correctly switching thermal profile. Add the DMI board name to hp_wmi_feature_boards[] table and map it to omen_v1_legacy_board_params. Testing on board 8D3F confirmed that platform profile is registered successfully and fan RPMs are readable and controllable. Tested-by: Jan Ziobro Signed-off-by: Krishna Chomal Link: https://patch.msgid.link/20260829071843.62902-1-krishna.chomal108@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index fba6d202672a..a3488e7da89e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -323,6 +323,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D3F") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D40") }, .driver_data = (void *)&omen_v1_no_ec_board_params, -- cgit v1.2.3 From 192dddd5aa45166450820cef16521bdf23976a73 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 3 Sep 2026 13:29:26 -0500 Subject: platform/x86/amd/pmf: Validate the APTS state index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The static slider APTS state index is supplied by the BIOS as an 8-bit value, but apts_config_store contains only APTS_MAX_STATES entries. An invalid firmware value can therefore cause amd_pmf_update_slider_v2() to read beyond the end of the array. Validate the selected index before applying the slider values and return an error when it is out of range. Fixes: 8362e862fb87 ("platform/x86/amd/pmf: Update sps power thermals according to the platform-profiles") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260903182927.3556274-1-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/sps.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c index 0b70a5153f46..0918fb388fde 100644 --- a/drivers/platform/x86/amd/pmf/sps.c +++ b/drivers/platform/x86/amd/pmf/sps.c @@ -248,20 +248,24 @@ static int amd_pmf_update_sps_power_limits_v2(struct amd_pmf_dev *pdev, int pwr_ switch (pwr_mode) { case POWER_MODE_PERFORMANCE: index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_PERFORMANCE]; - amd_pmf_update_slider_v2(pdev, index); break; case POWER_MODE_BALANCED_POWER: index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BALANCED]; - amd_pmf_update_slider_v2(pdev, index); break; case POWER_MODE_POWER_SAVER: index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_POWER_EFFICIENCY]; - amd_pmf_update_slider_v2(pdev, index); break; default: return -EINVAL; } + if (index >= APTS_MAX_STATES) { + dev_err(pdev->dev, "Invalid APTS index: %d\n", index); + return -EINVAL; + } + + amd_pmf_update_slider_v2(pdev, index); + return 0; } -- cgit v1.2.3 From 8252a2da810f876b4145aae967b84b234809b4a5 Mon Sep 17 00:00:00 2001 From: Jack Wu Date: Fri, 4 Sep 2026 14:46:48 +0800 Subject: platform/x86: dell-dw5826e: Extend _DSM support to revision 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver evaluates the PALC0001 _DSM with revision 1 only. Extend it to also support firmware implementing revision 0, as found on Slate PTL CS 14, Yukon PTL CSLW 14 and Yukon PTL 360 14. The supported revision is detected once during probe. Revision 1 is checked first so that existing systems keep their current behaviour, and the accepted revision is then used for evaluating the reset _DSM. Signed-off-by: Jack Wu Link: https://patch.msgid.link/20260904-dw5826e-acpi-reset-add-revision-v1-1-1be7153c3453@compal.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-dw5826e-reset.c | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/dell/dell-dw5826e-reset.c b/drivers/platform/x86/dell/dell-dw5826e-reset.c index 64e6e9b1d7a0..c72c243b460e 100644 --- a/drivers/platform/x86/dell/dell-dw5826e-reset.c +++ b/drivers/platform/x86/dell/dell-dw5826e-reset.c @@ -6,8 +6,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -15,15 +17,23 @@ #define PALC_DSM_FN_TRIGGER_PLDR 1 +static const u64 palc_dsm_revs[] = { 1, 0 }; + static guid_t palc_dsm_guid = GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59); +struct palc_data { + u64 dsm_rev; +}; + static int trigger_palc_pldr(struct device *dev, acpi_handle handle) { + struct palc_data *data = dev_get_drvdata(dev); union acpi_object *obj; int ret = 0; - obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR, NULL); + obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, data->dsm_rev, + PALC_DSM_FN_TRIGGER_PLDR, NULL); if (!obj) { dev_err(dev, "Failed to evaluate _DSM\n"); return -EIO; @@ -60,16 +70,28 @@ ATTRIBUTE_GROUPS(palc); static int palc_probe(struct platform_device *pdev) { + struct palc_data *data; acpi_handle handle; + unsigned int i; handle = ACPI_HANDLE(&pdev->dev); if (!handle) return -ENODEV; - if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, BIT(PALC_DSM_FN_TRIGGER_PLDR))) - return -ENODEV; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(palc_dsm_revs); i++) { + if (acpi_check_dsm(handle, &palc_dsm_guid, palc_dsm_revs[i], + BIT(PALC_DSM_FN_TRIGGER_PLDR))) { + data->dsm_rev = palc_dsm_revs[i]; + platform_set_drvdata(pdev, data); + return 0; + } + } - return 0; + return -ENODEV; } static const struct acpi_device_id palc_acpi_ids[] = { -- cgit v1.2.3 From 4981fb7fe9eb1af526236384e8e0505d8c18928e Mon Sep 17 00:00:00 2001 From: Murat Bayraktar Date: Sun, 6 Sep 2026 13:47:30 -0400 Subject: platform/x86/amd/pmc: Add HP Laptop 14-fq1xxx to s2idle quirk list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP Laptop 14-fq1xxx (board 887C, BIOS F.38) takes about 10.2 seconds in noirq device resume from s2idle. Its firmware uses the NVMe SMI path already handled by amd_pmc_skip_nvme_smi_handler(). Add the system to fwbug_list with the combined Cezanne quirk so the existing spurious 8042 workaround remains enabled. This reduced noirq device resume from 10195/10242 ms to 27 ms in testing. Signed-off-by: Murat Bayraktar Link: https://patch.msgid.link/20260906-hp-887c-s2idle-quirk-v1-1-e470e36fb01d@proton.me Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc-quirks.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 09a30f333759..e662b123ecee 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -280,6 +280,15 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83N1"), } }, + { + .ident = "HP Laptop 14-fq1xxx", + .driver_data = &quirk_s2idle_spurious_8042, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Laptop 14-fq1xxx"), + DMI_MATCH(DMI_BOARD_NAME, "887C"), + } + }, /* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */ { .ident = "HP Laptop 15s-eq2xxx", -- cgit v1.2.3 From c95872f979f5a1691aa717056b4c8045f9d6c0b0 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 7 Sep 2026 10:43:21 +0200 Subject: platform/x86: thinkpad_acpi: Use str_supported_unsupported() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the local str_supported() function and use the common str_supported_unsupported() helper. Debug messages now use "unsupported" instead of "not supported". Signed-off-by: Thorsten Blum Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260907084319.351688-6-blum@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 42 +++++++++++------------------ 1 file changed, 16 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index a6a83f7ba643..04ce174b4001 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -459,9 +459,7 @@ do { \ #ifdef CONFIG_THINKPAD_ACPI_DEBUG #define vdbg_printk dbg_printk -static const char *str_supported(int is_supported); #else -static inline const char *str_supported(int is_supported) { return ""; } #define vdbg_printk(a_dbg_level, format, arg...) \ do { if (0) no_printk(format, ##arg); } while (0) #endif @@ -3389,7 +3387,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "hotkeys are %s\n", - str_supported(tp_features.hotkey)); + str_supported_unsupported(tp_features.hotkey)); if (!tp_features.hotkey) return -ENODEV; @@ -3466,7 +3464,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "hotkey masks are %s\n", - str_supported(tp_features.hotkey_mask)); + str_supported_unsupported(tp_features.hotkey_mask)); /* Init hotkey_all_mask if not initialized yet */ if (!tp_features.hotkey_mask && !hotkey_all_mask && @@ -4394,7 +4392,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "bluetooth is %s, status 0x%02x\n", - str_supported(tp_features.bluetooth), + str_supported_unsupported(tp_features.bluetooth), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4573,7 +4571,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "wan is %s, status 0x%02x\n", - str_supported(tp_features.wan), + str_supported_unsupported(tp_features.wan), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4701,7 +4699,7 @@ static int __init uwb_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "uwb is %s, status 0x%02x\n", - str_supported(tp_features.uwb), + str_supported_unsupported(tp_features.uwb), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4809,7 +4807,7 @@ static int __init video_init(struct ibm_init_struct *iibm) video_supported = TPACPI_VIDEO_NEW; vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n", - str_supported(video_supported != TPACPI_VIDEO_NONE), + str_supported_unsupported(video_supported != TPACPI_VIDEO_NONE), video_supported); return (video_supported != TPACPI_VIDEO_NONE) ? 0 : -ENODEV; @@ -5402,8 +5400,8 @@ static int __init light_init(struct ibm_init_struct *iibm) acpi_evalf(ec_handle, NULL, "KBLT", "qv"); vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n", - str_supported(tp_features.light), - str_supported(tp_features.light_status)); + str_supported_unsupported(tp_features.light), + str_supported_unsupported(tp_features.light_status)); if (!tp_features.light) return -ENODEV; @@ -5520,7 +5518,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(cmos); vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", - str_supported(cmos_handle != NULL)); + str_supported_unsupported(cmos_handle)); return cmos_handle ? 0 : -ENODEV; } @@ -5865,7 +5863,8 @@ static int __init led_init(struct ibm_init_struct *iibm) } vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", - str_supported(led_supported), led_supported); + str_supported_unsupported(led_supported != TPACPI_LED_NONE), + led_supported); if (led_supported == TPACPI_LED_NONE) return -ENODEV; @@ -5986,7 +5985,7 @@ static int __init beep_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(beep); vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", - str_supported(beep_handle != NULL)); + str_supported_unsupported(beep_handle)); quirks = tpacpi_check_quirks(beep_quirk_table, ARRAY_SIZE(beep_quirk_table)); @@ -6437,7 +6436,7 @@ static int __init thermal_init(struct ibm_init_struct *iibm) thermal_read_mode = thermal_read_mode_check(); vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n", - str_supported(thermal_read_mode != TPACPI_THERMAL_NONE), + str_supported_unsupported(thermal_read_mode != TPACPI_THERMAL_NONE), thermal_read_mode); return thermal_read_mode != TPACPI_THERMAL_NONE ? 0 : -ENODEV; @@ -7659,7 +7658,7 @@ static int __init volume_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, "mute is supported, volume control is %s\n", - str_supported(!tp_features.mixer_no_level_control)); + str_supported_unsupported(!tp_features.mixer_no_level_control)); if (software_mute_requested && volume_set_software_mute(true) == 0) { software_mute_active = true; @@ -9057,8 +9056,8 @@ static int __init fan_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, "fan is %s, modes %d, %d\n", - str_supported(fan_status_access_mode != TPACPI_FAN_NONE || - fan_control_access_mode != TPACPI_FAN_WR_NONE), + str_supported_unsupported(fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE), fan_status_access_mode, fan_control_access_mode); /* fan control master switch */ @@ -11690,15 +11689,6 @@ static struct proc_dir_entry *proc_dir; static bool force_load; -#ifdef CONFIG_THINKPAD_ACPI_DEBUG -static const char * __init str_supported(int is_supported) -{ - static char text_unsupported[] __initdata = "not supported"; - - return (is_supported) ? &text_unsupported[4] : &text_unsupported[0]; -} -#endif /* CONFIG_THINKPAD_ACPI_DEBUG */ - static struct dentry *tpacpi_dbg; static void tpacpi_debugfs_init(void) { -- cgit v1.2.3 From c58bd28e12ee1398cd44134d8b24ddbd69fc66e3 Mon Sep 17 00:00:00 2001 From: Cymirk Date: Thu, 10 Sep 2026 20:50:32 +0000 Subject: platform/x86: asus-armoury: add support for GX651AR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add power limit data for the ASUS ROG Zephyrus Duo GX651AR. The GX651AR uses the following limits: - CPU PL1: 30-75 W - CPU PL2: 38-80 W - NVIDIA Dynamic Boost: 5-25 W - NVIDIA TGP: 80-115 W - NVIDIA temperature target: 75-87 C The same CPU limits and temperature target are available on DC power. Signed-off-by: Cymirk Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260910205032.153165-1-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 6dfe5ddfe4ac..d605af2fdaa1 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1895,6 +1895,33 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GX651AR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 38, + .ppt_pl2_sppt_max = 80, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 80, + .nv_tgp_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 38, + .ppt_pl2_sppt_max = 80, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"), -- cgit v1.2.3 From f7e9ca1f7e1a0ff2d6d8a0b20d52c9383cd0829d Mon Sep 17 00:00:00 2001 From: Walid Elonok Date: Sun, 13 Sep 2026 06:46:45 +0100 Subject: platform/x86: hp-wmi: Add support for HP Laptop 15-fd1xxx (board 8DE1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the HP Laptop 15-fd1xxx (motherboard ID 8DE1), hp-wmi currently falls back to the legacy HPWMI_FAN_SPEED_GET_QUERY (0x11) command because the board is not listed in hp_wmi_feature_boards[]. In HP BIOS DSDT table (e.g. BIOS F.26), method GM11 (query 0x11) is a dummy stub that returns 4 bytes of 0x00 with return code 0 (success), causing the driver to report 0 RPM via hwmon even while the fan is actively spinning. However, the DSDT implements method GM2D (query 0x2D / Victus S fan interface), which reads the active fan tachometer directly from the Embedded Controller (EC0) registers RPM1/RPM2 and reports accurate fan speed. Add board ID "8DE1" to hp_wmi_feature_boards[] mapped to victus_s_board_params so hp-wmi uses the working query 0x2D interface. Signed-off-by: Walid Elonok Link: https://patch.msgid.link/20260913054645.58692-1-walidelonk@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index a3488e7da89e..73fe864b3392 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -351,6 +351,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, .driver_data = (void *)&omen_v1_no_ec_thermal_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DE1") }, + .driver_data = (void *)&victus_s_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, .driver_data = (void *)&omen_v1_legacy_board_params, -- cgit v1.2.3 From 0c2c928ea7d33c8131b0785bda4e26a32dce5caa Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 14 Sep 2026 09:17:32 -0500 Subject: platform/x86/amd: Stop counting intermediate cycles with metrics table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metrics table doesn't increment for all cases that can cause an intermediate wakeup if the cycle is very fast. This can make the intent from commit 9f5595d5f03fd ("platform/x86/amd: pmc: Require at least 2.5 seconds between HW sleep cycles") not work properly. That commit originally was measured to work properly, but it was accidental due to a logic error. The logic error was fixed in commit 4dbd11796f3a8 ("platform/x86/amd: pmc: Clear metrics table at start of cycle") but this now masked the table doesn't always update. Since that code was introduced, Daniel Gibson added a number of changes to the logic which includes a state variable introduced in commit 037f0b03c663a ("platform/x86/amd/pmc: Don't log during intermediate wakeups"). Utilize that state variable to instead decide if a single sequence has started. Cc: Daniel Gibson Cc: stable@vger.kernel.org # 7.1.y: 037f0b03c663a platform/x86/amd/pmc: Don't log during intermediate wakeups Cc: stable@vger.kernel.org # 6.18.y: 037f0b03c663a platform/x86/amd/pmc: Don't log during intermediate wakeups Fixes: 4dbd11796f3a8 ("platform/x86/amd: pmc: Clear metrics table at start of cycle") Signed-off-by: Mario Limonciello Tested-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20260914141734.2703545-1-mario.limonciello@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 6792aa2c6187..e384b56d6755 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -701,19 +701,6 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) return rc; } -static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev) -{ - /* - * Starting a new HW sleep cycle right after waking from one - * can cause electrical problems triggering the over voltage protection. - * That is avoided by delaying the next suspend a bit, see also - * https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/ - */ - struct smu_metrics table; - - return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status; -} - static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev) { /* @@ -804,7 +791,7 @@ static void amd_pmc_s2idle_check(void) struct amd_pmc_dev *pdev = &pmc; int rc; - if (amd_pmc_intermediate_wakeup_need_delay(pdev) || + if (!pdev->is_first_check_after_suspend || amd_pmc_want_suspend_delay(pdev)) msleep(2500); -- cgit v1.2.3 From ee9d80d1cc4a7d76b5bdfab2494a512fca31447e Mon Sep 17 00:00:00 2001 From: Daniel Salmun Date: Mon, 14 Sep 2026 23:11:54 -0300 Subject: platform/x86: thinkpad_acpi: Fix fan speed reporting on ThinkPad L440 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The L440 (BIOS J4ET93WW) reports its fan as stopped: /proc/acpi/ibm/fan shows "status: disabled" and "speed: 0", and hwmon fan1_input is 0 under any load. EC registers 0x2F and 0x84/0x85 always read 0 on this model. The EC uses the 0x93/0x95 layout handled by TPACPI_FAN_NS. 0x93 reads 4 (FAN_NS_CTRL_STATUS) and 0x95 goes from 255 at idle to about 111 at full speed. The DSDT has no fan methods. Add the J4 BIOS family to fan_quirk_table. The fan then reads about 1900 RPM at idle and 2900 RPM under load. The L540 shares the J4 BIOS and the same EC firmware, so it is covered by the same entry. Tested on a ThinkPad L440 20ASS20200 with BIOS J4ET93WW and EC J4HT30WW. Link: https://github.com/lm-sensors/lm-sensors/issues/510 Signed-off-by: Daniel Salmun Link: https://patch.msgid.link/20260915021154.23252-1-salmundani@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 04ce174b4001..1225b01ed14d 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -8909,6 +8909,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ TPACPI_Q_LNV3('N', '4', 'D', TPACPI_FAN_NS), /* X9-14 Gen 1 */ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV('J', '4', TPACPI_FAN_NS), /* L440 / L540 */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ -- cgit v1.2.3 From 39150ae3cb062395b6632e4f4f326361c267a962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Da=C5=84k=C3=B3w?= Date: Tue, 15 Sep 2026 17:41:28 +0000 Subject: platform/x86: asus-wmi: Add mute LED support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for WMI device 0x0004001c and register it as the platform::mute LED using the audio-mute trigger. The device ID is present in the PM3406CKA DSDT and is also used by GHelper for controlling the same LED. Tested on ASUS ExpertBook PM3406CKA. Signed-off-by: Patryk Dańków Reviewed-by: Denis Benato Link: https://patch.msgid.link/20260915174110.67871-2-patryk.dankow@protonmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 0eb43ca44119..fbd887e8d5fa 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -262,6 +262,7 @@ struct asus_wmi { struct led_classdev lightbar_led; int lightbar_led_wk; struct led_classdev micmute_led; + struct led_classdev mute_led; struct led_classdev camera_led; struct workqueue_struct *led_workqueue; struct work_struct tpd_led_work; @@ -2054,6 +2055,16 @@ static int micmute_led_set(struct led_classdev *led_cdev, return err < 0 ? err : 0; } +static int mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int state = brightness != LED_OFF; + int err; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MUTE_LED, state, NULL); + return err < 0 ? err : 0; +} + static enum led_brightness camera_led_get(struct led_classdev *led_cdev) { struct asus_wmi *asus; @@ -2084,6 +2095,7 @@ static void asus_wmi_led_exit(struct asus_wmi *asus) led_classdev_unregister(&asus->wlan_led); led_classdev_unregister(&asus->lightbar_led); led_classdev_unregister(&asus->micmute_led); + led_classdev_unregister(&asus->mute_led); led_classdev_unregister(&asus->camera_led); if (asus->led_workqueue) @@ -2188,7 +2200,19 @@ static int asus_wmi_led_init(struct asus_wmi *asus) asus->micmute_led.default_trigger = "audio-micmute"; rv = led_classdev_register(&asus->platform_device->dev, - &asus->micmute_led); + &asus->micmute_led); + if (rv) + goto error; + } + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MUTE_LED)) { + asus->mute_led.name = "platform::mute"; + asus->mute_led.max_brightness = 1; + asus->mute_led.brightness_set_blocking = mute_led_set; + asus->mute_led.default_trigger = "audio-mute"; + + rv = led_classdev_register(&asus->platform_device->dev, + &asus->mute_led); if (rv) goto error; } -- cgit v1.2.3 From 655d731c391ec3912503b91ecf6269883b13e0c2 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:25:20 +0200 Subject: platform/x86/amd/pmf: fix compat ioctl handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build testing with -Wpadded shows a structure that is incompatible between 32-bit and 64-bit x86 userspace: ./usr/include/linux/amd-pmf.h:202:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] Use the 64-bit padded on all architectures here to avoid both the warning and allow compat ioctl handling. Fixes: 98432eec3180 ("platform/x86/amd/pmf: Add util layer and userspace character device interface") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260915202535.3689880-1-arnd@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/util.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c index 4d1a61e4f1b9..2befc1b14842 100644 --- a/drivers/platform/x86/amd/pmf/util.c +++ b/drivers/platform/x86/amd/pmf/util.c @@ -123,6 +123,7 @@ static const struct file_operations pmf_if_ops = { .owner = THIS_MODULE, .open = amd_pmf_open, .unlocked_ioctl = amd_pmf_set_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; static struct miscdevice amd_pmf_util_if = { -- cgit v1.2.3 From ccc8c8481c1fce1ec092a91883f3a7604f70fc06 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Wed, 16 Sep 2026 15:47:51 +0200 Subject: platform/x86: ideapad-laptop: Map Smart Connect hotkey to KEY_LINK_PHONE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On certain 2025 Lenovo laptops, such as the Yoga Pro 7 14ASP10 and Legion Pro 7 16AFR10H, the F11 key includes a symbol showing a laptop, a tablet and a smartphone. According to the user manual, the key starts the Lenovo Smart Connect app, which connects the laptop to phones and tablets. These laptops send WMI hotkey event 0x48 (scancode 0x148) when the key is pressed, which is currently reported as KEY_UNKNOWN. Map it to KEY_LINK_PHONE as the closest match, as thinkpad_acpi does for the Microsoft Phone Link and Intel Unison keys, see commit 7ba618e893a4 ("platform/x86: thinkpad_acpi: Add support for new phone link hotkey") and commit b511bbfe4242 ("platform/x86: thinkpad_acpi: handle HKEY 0x1402 event"). Signed-off-by: Marco Giunta Link: https://patch.msgid.link/SN6PR19MB2303EBD5288E51BA50D904EFFCB92@SN6PR19MB2303.namprd19.prod.outlook.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ideapad-laptop.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 35c13d98b989..207368f5d489 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -1358,6 +1358,8 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 0x44 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, /* Eye */ { KE_KEY, 0x45 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, + /* Lenovo Smart Connect */ + { KE_KEY, 0x48 | IDEAPAD_WMI_KEY, { KEY_LINK_PHONE } }, /* Performance toggle also Fn+Q, handled inside ideapad_wmi_notify() */ { KE_KEY, 0x3d | IDEAPAD_WMI_KEY, { KEY_PROG4 } }, /* Mode, opens a profile chooser; the Fn+Q button cycles the profile */ -- cgit v1.2.3 From fd83d81ddf058d487e6d6489b18094f068e66a59 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Wed, 16 Sep 2026 14:38:25 +0000 Subject: platform/x86: asus-wmi: fix unclear usage of bd->props.power MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bd->props.power is checked in parts of the driver correctly comparing with BACKLIGHT_POWER_ON, while in others with a raw usage of bd->props.power and !bd->props.power, moreover in certain checks the logic has been inverted due to BACKLIGHT_POWER_ON being defined as 0: fix both the wrong usage and the inconsistencies by using proper comparisons. Fixes: 130d29c5627c ("platform/x86: asus-wmi: adjust screenpad power/brightness handling") Closes: https://lore.kernel.org/all/178362762638.911488.8564892548331679884@eldamar.lan/ Closes: https://lore.kernel.org/all/ea9c63d1-4776-49d5-9dc4-6c09498f99c9@linux.dev/ Tested-by: Hugo Baigue Tested-by: Ponali Cc: stable@vger.kernel.org Assisted-by: zcode:glm-5.3-flash Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260916143838.170950-2-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index fbd887e8d5fa..de04c8e1795d 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4556,7 +4556,8 @@ static int update_screenpad_bl_status(struct backlight_device *bd) u32 ctrl_param = bd->props.brightness; int err = 0; - if (bd->props.power) { + switch (bd->props.power) { + case BACKLIGHT_POWER_ON: err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL); if (err < 0) return err; @@ -4564,12 +4565,17 @@ static int update_screenpad_bl_status(struct backlight_device *bd) err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); if (err < 0) return err; - } + break; - if (!bd->props.power) { + case BACKLIGHT_POWER_OFF: err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); if (err < 0) return err; + break; + + default: + pr_warn("Invalid screenpad backlight power state: %d\n", bd->props.power); + return -EINVAL; } return err; -- cgit v1.2.3 From f9a1cd14b392a82fdc73474894bf8b198757dfb2 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Wed, 16 Sep 2026 14:38:26 +0000 Subject: platform/x86: asus-wmi: fix screenpad power state detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some models, e.g. the UX5400EA, the screenpad power state cannot be read back: DSTS(ASUS_WMI_DEVID_SCREENPAD_POWER) never sets ASUS_WMI_DSTS_STATUS_BIT, so asus_wmi_get_devstate_simple() reports the panel as powered off regardless of its real state. The DSDT shows DSTS 0x00050031 (POWER) and DSTS 0x00050032 (LIGHT) both read the same two-byte EC register, each returning a different byte: byte 0 is a raw EC status byte, which is 0xa0 when the panel is powered and 0x00 when it is off, and byte 1 is the brightness. Read the power state from the low byte instead of ASUS_WMI_DSTS_STATUS_BIT: firmware reporting the power through the status bit is covered too, since ASUS_WMI_DSTS_STATUS_BIT lies inside ASUS_WMI_DSTS_BRIGHTNESS_MASK. Reuse read_screenpad_backlight_power() in asus_screenpad_init() in place of the raw devstate read. This makes the brightness read reachable on models like the UX5400EA, so mask the SCREENPAD_LIGHT value with ASUS_WMI_DSTS_BRIGHTNESS_MASK before storing it: the unmasked devstate (0x0001ffa0 when the panel is on) would otherwise be exposed to userspace as an out-of-range brightness (max_brightness is 255) which systemd-backlight then persists. While at it, pass asus_wmi_get_devstate() the u32 it expects. Fixes: 130d29c5627c ("platform/x86: asus-wmi: adjust screenpad power/brightness handling") Reported-by: Hugo Baigue Closes: https://lore.kernel.org/all/CAO84+x+P2_xyHP89+nGVSMV6bL+dy0P9=vyEEfZGnFhv0hBNWw@mail.gmail.com/ Suggested-by: Hugo Baigue Tested-by: Hugo Baigue Cc: stable@vger.kernel.org Assisted-by: zcode:glm-5.3-flash Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260916143838.170950-3-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index de04c8e1795d..a6bfecf6b9f9 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4522,13 +4522,20 @@ static int is_display_toggle(int code) static int read_screenpad_backlight_power(struct asus_wmi *asus) { - int ret; + int ret, retval; - ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); + ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_POWER, &retval); if (ret < 0) return ret; - /* 1 == powered */ - return ret ? BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF; + + /* + * The firmware reports the panel power in the low byte of the + * devstate as a raw EC status byte that is non-zero when the + * panel is powered; other models report it through + * ASUS_WMI_DSTS_STATUS_BIT, which lies inside the same mask. + */ + return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) ? + BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF; } static int read_screenpad_brightness(struct backlight_device *bd) @@ -4592,16 +4599,17 @@ static int asus_screenpad_init(struct asus_wmi *asus) struct backlight_device *bd; struct backlight_properties props; int err, power; - int brightness = 0; + u32 brightness = 0; - power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER); + power = read_screenpad_backlight_power(asus); if (power < 0) return power; - if (power) { + if (power == BACKLIGHT_POWER_ON) { err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness); if (err < 0) return err; + brightness &= ASUS_WMI_DSTS_BRIGHTNESS_MASK; } memset(&props, 0, sizeof(struct backlight_properties)); -- cgit v1.2.3 From ad10eecfe8166606fe2b5ea7a20c2e4433f252bf Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Wed, 16 Sep 2026 14:38:27 +0000 Subject: platform/x86: asus-wmi: use backlight_is_blank() for screenpad power MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The screenpad backlight sets BL_CORE_SUSPENDRESUME, so the backlight core calls update_status() on suspend, resume and fb blanking with BL_CORE_SUSPENDED or BL_CORE_FBBLANK set in bd->props.state while bd->props.power keeps its value: the switch on bd->props.power alone ignored those flags, repowering the panel at suspend entry instead of turning it off, and ignoring fb blank requests. Replace the switch with backlight_is_blank(), which accounts for both bd->props.power and bd->props.state: the panel is powered off whenever the backlight is blank and powered on with backlight_get_brightness() otherwise. Writing a power state other than BACKLIGHT_POWER_ON or BACKLIGHT_POWER_OFF to bl_power now blanks the panel following the core convention instead of warning and failing with -EINVAL. The visible change is that the panel now actually powers off on suspend and fb blank, and is restored on unblank and resume. Suggested-by: Hugo Baigue Closes: https://lore.kernel.org/all/CAO84+xJ9aW3pj3x8e9b5biWtnNd4EyH7A4Uy7aqBR4qZMDcVvg@mail.gmail.com/ Assisted-by: zcode:glm-5.3-flash Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260916143838.170950-4-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a6bfecf6b9f9..491715c16139 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4560,32 +4560,18 @@ static int read_screenpad_brightness(struct backlight_device *bd) static int update_screenpad_bl_status(struct backlight_device *bd) { - u32 ctrl_param = bd->props.brightness; - int err = 0; - - switch (bd->props.power) { - case BACKLIGHT_POWER_ON: - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL); - if (err < 0) - return err; - - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL); - if (err < 0) - return err; - break; + int err; - case BACKLIGHT_POWER_OFF: - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); - if (err < 0) - return err; - break; + if (backlight_is_blank(bd)) + return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, + 0, NULL); - default: - pr_warn("Invalid screenpad backlight power state: %d\n", bd->props.power); - return -EINVAL; - } + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL); + if (err < 0) + return err; - return err; + return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, + backlight_get_brightness(bd), NULL); } static const struct backlight_ops asus_screenpad_bl_ops = { -- cgit v1.2.3 From ab46e684ea334cd01646206a7075b2ef9845839c Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Wed, 16 Sep 2026 14:38:28 +0000 Subject: platform/x86: asus-wmi: remove unused screenpad_brightness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing reads asus->driver->screenpad_brightness anymore: the last reader went away when the screenpad update path stopped relying on a driver-side copy of the brightness, leaving only the write in asus_screenpad_init(). ASUS_SCREENPAD_BRIGHT_DEFAULT became unused in the same rework. Drop the field from struct asus_wmi_driver, the write and the unused define. Nothing in the current code depends on remembering the last brightness while the panel is off; should a model turn up that needs it (the original screenpad implementation did, and older DUO models may behave differently from the hardware tested so far), the field can be reintroduced then. Assisted-by: zcode:glm-5.3-flash Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260916143838.170950-5-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 2 -- drivers/platform/x86/asus-wmi.h | 1 - 2 files changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 491715c16139..b234734b6c56 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -127,7 +127,6 @@ module_param(fnlock_default, bool, 0444); #define NVIDIA_TEMP_MAX 87 #define ASUS_SCREENPAD_BRIGHT_MAX 255 -#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60 #define ASUS_MINI_LED_MODE_MASK 0x03 /* Standard modes for devices with only on/off */ @@ -4610,7 +4609,6 @@ static int asus_screenpad_init(struct asus_wmi *asus) } asus->screenpad_backlight_device = bd; - asus->driver->screenpad_brightness = brightness; bd->props.brightness = brightness; bd->props.power = power; backlight_update_status(bd); diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 5cd4392b964e..b45f81b38a47 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -57,7 +57,6 @@ struct quirk_entry { struct asus_wmi_driver { int brightness; int panel_power; - int screenpad_brightness; int wlan_ctrl_by_user; const char *name; -- cgit v1.2.3 From 93f2e0df286698612b1a629589394b29b03fa45d Mon Sep 17 00:00:00 2001 From: BurningHoryd Date: Wed, 16 Sep 2026 21:19:09 -0400 Subject: platform/x86: hp-wmi: Add board 8E41 (OMEN Transcend 14) to feature board list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN Transcend 14-fb1xxx (DMI board name "8E41") is recognized as an Omen board by omen_thermal_profile_boards[] (so platform_profile switching works via the generic path), but it has no entry in hp_wmi_feature_boards[]. As a result active_board_params stays NULL for this board, is_victus_s_board is never set, and none of the board-specific paths that depend on a real ModelCapabilities entry (EC thermal-profile readback, the Victus-S GPU CTGP/PPAB WMI calls, the AC/battery power-source re-apply handler) ever run for it. Confirmed by direct EC readback (CONFIG_ACPI_EC_DEBUGFS, reading /sys/kernel/debug/ec/ec0/io) while cycling /sys/firmware/acpi/ platform_profile through performance/balanced/cool: profile offset 0x95 performance 0x31 balanced 0x30 cool 0x50 These values are an exact match for hp_thermal_profile_omen_v1's HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE (0x31), _DEFAULT (0x30) and _COOL (0x50), read back at exactly HP_OMEN_EC_THERMAL_PROFILE_OFFSET (0x95) -- the ec_tp_offset already used by omen_v1_legacy_board_params, and identical to sibling boards already in the table (8902, 8A44, 8A4D, 8D26, 8E35). Two other candidate offsets from the same struct (0x59, 0x62) were also probed and did not change with the profile; 0x63 changed by +/-1 opportunistically across reads and looks like the unrelated timer byte, not a profile encoding. System tested on: DMI board_name: 8E41 DMI product_name: OMEN Transcend Gaming Laptop 14-fb1xxx BIOS version: F.05 Kernel: 7.1.9-arch1-2 Signed-off-by: BurningHoryd Link: https://patch.msgid.link/20260917011909.75399-1-sjunhyuk1@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 73fe864b3392..bc2ada5dfa63 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -359,6 +359,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E41") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, {}, }; -- cgit v1.2.3 From f1db1b80190b6a9f96e1171e2d7eac51ed4e28ca Mon Sep 17 00:00:00 2001 From: Dale Whinham Date: Fri, 18 Sep 2026 18:49:29 +0100 Subject: platform/surface: aggregator_registry: Remove sensors device for Surface Pro 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove ssam_node_hid_sam_sensors as this device is not present on Surface Pro 11. This fixes a probe failure in dmesg: surface_hid 01:15:01:06:00: unexpected descriptor length: got 0, expected 9 surface_hid 01:15:01:06:00: probe with driver surface_hid failed with error -71 Fixes: c4a069095395 ("platform/surface: aggregator_registry: Add Surface Pro 11 (QCOM)") Signed-off-by: Dale Whinham Link: https://patch.msgid.link/20260918-surface-pro-11-aggregator-registry-fixes-v2-1-c4ed9335814a@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_registry.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index d37a7e4c6b02..82c2144e10ec 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -415,7 +415,6 @@ static const struct software_node *ssam_node_group_sp11[] = { &ssam_node_hid_kip_penstash, &ssam_node_hid_kip_touchpad, &ssam_node_hid_kip_fwupd, - &ssam_node_hid_sam_sensors, &ssam_node_kip_tablet_switch, NULL, }; -- cgit v1.2.3 From ee2e7315dedcdd934139237f820582f9b6ce7842 Mon Sep 17 00:00:00 2001 From: Dale Whinham Date: Fri, 18 Sep 2026 18:49:30 +0100 Subject: platform/surface: aggregator_registry: Fix tablet-mode detection on Surface Pro 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace ssam_node_kip_tablet_switch with ssam_node_pos_tablet_switch, as Surface Pro 11 reports tablet-mode via the POS ('posture') subsystem and not the KIP subsystem. The KIP-based driver would remain stuck in a 'folded-back' state after suspend/resume, whereas the POS-based device reports its state correctly even after suspend/resume. This fixes the type cover keyboard/mouse being ignored by desktop environments after resume from sleep due to the erroneous 'folded-back' state. Fixes: c4a069095395 ("platform/surface: aggregator_registry: Add Surface Pro 11 (QCOM)") Signed-off-by: Dale Whinham Link: https://patch.msgid.link/20260918-surface-pro-11-aggregator-registry-fixes-v2-2-c4ed9335814a@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_registry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 82c2144e10ec..2cb8278d98ea 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -415,7 +415,7 @@ static const struct software_node *ssam_node_group_sp11[] = { &ssam_node_hid_kip_penstash, &ssam_node_hid_kip_touchpad, &ssam_node_hid_kip_fwupd, - &ssam_node_kip_tablet_switch, + &ssam_node_pos_tablet_switch, NULL, }; -- cgit v1.2.3 From 6a0380e794ad71126518ea5a4ad17d2e0814209a Mon Sep 17 00:00:00 2001 From: Dale Whinham Date: Fri, 18 Sep 2026 18:49:31 +0100 Subject: platform/surface: aggregator_registry: Add performance profile and fan support for Surface Pro 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EC on the Surface Pro 11 exposes the performance profile and fan interfaces. Enable their associated drivers. Signed-off-by: Dale Whinham Link: https://patch.msgid.link/20260918-surface-pro-11-aggregator-registry-fixes-v2-3-c4ed9335814a@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_registry.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 2cb8278d98ea..061d4a567a06 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -410,7 +410,9 @@ static const struct software_node *ssam_node_group_sp11[] = { &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, + &ssam_node_tmp_perf_profile_with_fan, &ssam_node_tmp_sensors, + &ssam_node_fan_speed, &ssam_node_hid_kip_keyboard, &ssam_node_hid_kip_penstash, &ssam_node_hid_kip_touchpad, -- cgit v1.2.3 From 884d53763d304af64a9b795161f9fc3b83a5e3d8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 24 Jul 2026 04:59:57 +0200 Subject: platform/wmi: Remove dependency on CONFIG_X86 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The aarch64-laptops project found out that some Windows-on-Arm notebooks contain WMI-ACPI mapping devices (PNP0C14) that work exactly like on x86. Remove the dependency on CONFIG_X86 to make it possible to build the ACPI-WMI driver on arm64. Link: https://lists.linaro.org/archives/list/aarch64-laptops@lists.linaro.org/thread/U2IZENSIOOHZHGXVN7GM5HX5O3FD5KCF/ Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260724025959.142826-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/wmi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig index d62f51ff3b7f..36934b5f0194 100644 --- a/drivers/platform/wmi/Kconfig +++ b/drivers/platform/wmi/Kconfig @@ -5,7 +5,7 @@ menuconfig ACPI_WMI tristate "ACPI-WMI support" - depends on ACPI && X86 + depends on ACPI select NLS help This option enables support for the ACPI-WMI driver core. -- cgit v1.2.3 From 4ea18e604272fdaa605c06548608dd996230c9cc Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 24 Jul 2026 04:59:58 +0200 Subject: ACPI: video: Remove CONFIG_X86 handling from nvidia_wmi_ec_supported() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WMI-ACPI driver now supports architectures other than x86. Remove the CONFIG_X86 handling from video-detect and the associated drivers to simplify the resulting code. Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Armin Wolf Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/20260724025959.142826-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/acpi/Kconfig | 2 +- drivers/acpi/video_detect.c | 8 -------- drivers/gpu/drm/amd/amdgpu/Kconfig | 4 +--- drivers/gpu/drm/gma500/Kconfig | 1 - drivers/gpu/drm/i915/Kconfig | 1 - drivers/gpu/drm/radeon/Kconfig | 4 +--- drivers/gpu/drm/xe/Kconfig | 5 ++--- drivers/platform/loongarch/Kconfig | 1 + 8 files changed, 6 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index f165d14cf61a..cc1e58fa664b 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -224,7 +224,7 @@ config ACPI_VIDEO tristate "Video" depends on BACKLIGHT_CLASS_DEVICE depends on INPUT - depends on ACPI_WMI || !X86 + depends on ACPI_WMI select THERMAL help This driver implements the ACPI Extensions For Display Adapters diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index e3b69f876d24..07b5aac43377 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -77,8 +77,6 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) return AE_OK; } -/* This depends on ACPI_WMI which is X86 only */ -#ifdef CONFIG_X86 static bool nvidia_wmi_ec_supported(void) { struct wmi_brightness_args args = { @@ -100,12 +98,6 @@ static bool nvidia_wmi_ec_supported(void) */ return args.ret == WMI_BRIGHTNESS_SOURCE_EC; } -#else -static bool nvidia_wmi_ec_supported(void) -{ - return false; -} -#endif /* Force to use vendor driver when the ACPI device is known to be * buggy */ diff --git a/drivers/gpu/drm/amd/amdgpu/Kconfig b/drivers/gpu/drm/amd/amdgpu/Kconfig index 12e4a41bf1f0..50305630f533 100644 --- a/drivers/gpu/drm/amd/amdgpu/Kconfig +++ b/drivers/gpu/drm/amd/amdgpu/Kconfig @@ -30,9 +30,7 @@ config DRM_AMDGPU # ACPI_VIDEO's dependencies must also be selected. select INPUT if ACPI select ACPI_VIDEO if ACPI - # On x86 ACPI_VIDEO also needs ACPI_WMI - select X86_PLATFORM_DEVICES if ACPI && X86 - select ACPI_WMI if ACPI && X86 + select ACPI_WMI if ACPI help Choose this option if you have a recent AMD Radeon graphics card. diff --git a/drivers/gpu/drm/gma500/Kconfig b/drivers/gpu/drm/gma500/Kconfig index a2acaa699dd5..dc4c56638819 100644 --- a/drivers/gpu/drm/gma500/Kconfig +++ b/drivers/gpu/drm/gma500/Kconfig @@ -11,7 +11,6 @@ config DRM_GMA500 select ACPI_VIDEO if ACPI select BACKLIGHT_CLASS_DEVICE if ACPI select INPUT if ACPI - select X86_PLATFORM_DEVICES if ACPI select ACPI_WMI if ACPI help Say yes for an experimental 2D KMS framebuffer driver for the diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig index 5e939004b646..71426b9c91f0 100644 --- a/drivers/gpu/drm/i915/Kconfig +++ b/drivers/gpu/drm/i915/Kconfig @@ -28,7 +28,6 @@ config DRM_I915 # but for select to work, need to select ACPI_VIDEO's dependencies, ick select BACKLIGHT_CLASS_DEVICE if ACPI select INPUT if ACPI - select X86_PLATFORM_DEVICES if ACPI select ACPI_WMI if ACPI select ACPI_VIDEO if ACPI select SYNC_FILE diff --git a/drivers/gpu/drm/radeon/Kconfig b/drivers/gpu/drm/radeon/Kconfig index c479f0c0dd5c..3cd70aef770e 100644 --- a/drivers/gpu/drm/radeon/Kconfig +++ b/drivers/gpu/drm/radeon/Kconfig @@ -25,9 +25,7 @@ config DRM_RADEON # ACPI_VIDEO's dependencies must also be selected. select INPUT if ACPI select ACPI_VIDEO if ACPI - # On x86 ACPI_VIDEO also needs ACPI_WMI - select X86_PLATFORM_DEVICES if ACPI && X86 - select ACPI_WMI if ACPI && X86 + select ACPI_WMI if ACPI help Choose this option if you have an ATI Radeon graphics card. There are both PCI and AGP versions. You don't need to choose this to diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig index 4d7dcaff2b91..502c7b3c1f16 100644 --- a/drivers/gpu/drm/xe/Kconfig +++ b/drivers/gpu/drm/xe/Kconfig @@ -4,7 +4,6 @@ config DRM_XE depends on DRM && PCI depends on KUNIT || !KUNIT depends on INTEL_VSEC || !INTEL_VSEC - depends on X86_PLATFORM_DEVICES || !(X86 && ACPI) depends on PAGE_SIZE_4KB || COMPILE_TEST || BROKEN select INTERVAL_TREE # we need shmfs for the swappable backing store, and in particular @@ -29,8 +28,8 @@ config DRM_XE # but for select to work, need to select ACPI_VIDEO's dependencies, ick select BACKLIGHT_CLASS_DEVICE if ACPI select INPUT if ACPI - select ACPI_VIDEO if X86 && ACPI - select ACPI_WMI if X86 && ACPI + select ACPI_VIDEO if ACPI + select ACPI_WMI if ACPI select SYNC_FILE select CRC32 select SND_HDA_I915 if SND_HDA_CORE diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig index 447528797d07..db7833842142 100644 --- a/drivers/platform/loongarch/Kconfig +++ b/drivers/platform/loongarch/Kconfig @@ -23,6 +23,7 @@ config LOONGSON_LAPTOP depends on INPUT depends on MACH_LOONGSON64 select ACPI_VIDEO + select ACPI_WMI select INPUT_SPARSEKMAP default y help -- cgit v1.2.3 From 07c95d2020205b09c03c187ed60f5a1d3fcf9499 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 24 Jul 2026 04:59:59 +0200 Subject: platform/x86: wmi-bmof: Move to generic WMI code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Windows-on-Arm notebooks contain embedded Binary MOF data, accessible using the WMI-ACPI interface. Move the associated driver to the generic WMI code to make it possible to build it on arm64. Also rework the Kconfig help text a bit. Link: https://lists.linaro.org/archives/list/aarch64-laptops@lists.linaro.org/thread/U2IZENSIOOHZHGXVN7GM5HX5O3FD5KCF/ Reviewed-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260724025959.142826-4-W_Armin@gmx.de Signed-off-by: Ilpo Järvinen --- drivers/platform/wmi/Kconfig | 12 +++++ drivers/platform/wmi/Makefile | 4 ++ drivers/platform/wmi/bmof.c | 102 ++++++++++++++++++++++++++++++++++++++++ drivers/platform/x86/Kconfig | 12 ----- drivers/platform/x86/Makefile | 3 -- drivers/platform/x86/wmi-bmof.c | 102 ---------------------------------------- 6 files changed, 118 insertions(+), 117 deletions(-) create mode 100644 drivers/platform/wmi/bmof.c delete mode 100644 drivers/platform/x86/wmi-bmof.c (limited to 'drivers') diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig index 36934b5f0194..11c221a58e04 100644 --- a/drivers/platform/wmi/Kconfig +++ b/drivers/platform/wmi/Kconfig @@ -34,4 +34,16 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES source "drivers/platform/wmi/tests/Kconfig" +config WMI_BMOF + tristate "WMI embedded Binary MOF driver" + default ACPI_WMI + help + Say Y here if you want to be able to read the firmware-embedded + WMI Binary MOF (Managed Object Format) data. Using this requires + userspace tools, see Documentation/wmi/devices/wmi-bmof.rst for + details. + + To compile this driver as a module, choose M here: the module will + be called wmi-bmof. + endif # ACPI_WMI diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile index 2feff94a5594..ee2f27ea20d1 100644 --- a/drivers/platform/wmi/Makefile +++ b/drivers/platform/wmi/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o # Unit tests obj-y += tests/ + +# Binary MOF driver +wmi-bmof-y := bmof.o +obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o diff --git a/drivers/platform/wmi/bmof.c b/drivers/platform/wmi/bmof.c new file mode 100644 index 000000000000..6623cf60e4b4 --- /dev/null +++ b/drivers/platform/wmi/bmof.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * WMI embedded Binary MOF driver + * + * Copyright (c) 2015 Andrew Lutomirski + * Copyright (C) 2017 VMware, Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define WMI_BMOF_GUID "05901221-D566-11D1-B2F0-00A0C9062910" + +static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct wmi_buffer *buffer = dev_get_drvdata(dev); + + return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length); +} + +static const BIN_ATTR_ADMIN_RO(bmof, 0); + +static const struct bin_attribute * const bmof_attrs[] = { + &bin_attr_bmof, + NULL +}; + +static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct wmi_buffer *buffer = dev_get_drvdata(dev); + + return buffer->length; +} + +static const struct attribute_group bmof_group = { + .bin_size = bmof_bin_size, + .bin_attrs = bmof_attrs, +}; + +static const struct attribute_group *bmof_groups[] = { + &bmof_group, + NULL +}; + +static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) +{ + struct wmi_buffer *buffer; + int ret; + + buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + ret = wmidev_query_block(wdev, 0, buffer, 0); + if (ret < 0) + return ret; + + dev_set_drvdata(&wdev->dev, buffer); + + return 0; +} + +static void wmi_bmof_remove(struct wmi_device *wdev) +{ + struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev); + + kfree(buffer->data); +} + +static const struct wmi_device_id wmi_bmof_id_table[] = { + { .guid_string = WMI_BMOF_GUID }, + { }, +}; + +static struct wmi_driver wmi_bmof_driver = { + .driver = { + .name = "wmi-bmof", + .dev_groups = bmof_groups, + }, + .probe = wmi_bmof_probe, + .remove = wmi_bmof_remove, + .id_table = wmi_bmof_id_table, + .no_singleton = true, +}; + +module_wmi_driver(wmi_bmof_driver); + +MODULE_DEVICE_TABLE(wmi, wmi_bmof_id_table); +MODULE_AUTHOR("Andrew Lutomirski "); +MODULE_DESCRIPTION("WMI embedded Binary MOF driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 45f15f93afa7..7ee9ff3879c9 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -16,18 +16,6 @@ menuconfig X86_PLATFORM_DEVICES if X86_PLATFORM_DEVICES -config WMI_BMOF - tristate "WMI embedded Binary MOF driver" - depends on ACPI_WMI - default ACPI_WMI - help - Say Y here if you want to be able to read a firmware-embedded - WMI Binary MOF (Managed Object Format) data. Using this requires - userspace tools and may be rather tedious. - - To compile this driver as a module, choose M here: the module will - be called wmi-bmof. - config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" depends on ACPI_BATTERY diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index a877acd937cd..b90c544387be 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -4,9 +4,6 @@ # x86 Platform-Specific Drivers # -# Windows Management Interface -obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o - # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c deleted file mode 100644 index 6623cf60e4b4..000000000000 --- a/drivers/platform/x86/wmi-bmof.c +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * WMI embedded Binary MOF driver - * - * Copyright (c) 2015 Andrew Lutomirski - * Copyright (C) 2017 VMware, Inc. All Rights Reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#define WMI_BMOF_GUID "05901221-D566-11D1-B2F0-00A0C9062910" - -static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct device *dev = kobj_to_dev(kobj); - struct wmi_buffer *buffer = dev_get_drvdata(dev); - - return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length); -} - -static const BIN_ATTR_ADMIN_RO(bmof, 0); - -static const struct bin_attribute * const bmof_attrs[] = { - &bin_attr_bmof, - NULL -}; - -static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n) -{ - struct device *dev = kobj_to_dev(kobj); - struct wmi_buffer *buffer = dev_get_drvdata(dev); - - return buffer->length; -} - -static const struct attribute_group bmof_group = { - .bin_size = bmof_bin_size, - .bin_attrs = bmof_attrs, -}; - -static const struct attribute_group *bmof_groups[] = { - &bmof_group, - NULL -}; - -static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) -{ - struct wmi_buffer *buffer; - int ret; - - buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - ret = wmidev_query_block(wdev, 0, buffer, 0); - if (ret < 0) - return ret; - - dev_set_drvdata(&wdev->dev, buffer); - - return 0; -} - -static void wmi_bmof_remove(struct wmi_device *wdev) -{ - struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev); - - kfree(buffer->data); -} - -static const struct wmi_device_id wmi_bmof_id_table[] = { - { .guid_string = WMI_BMOF_GUID }, - { }, -}; - -static struct wmi_driver wmi_bmof_driver = { - .driver = { - .name = "wmi-bmof", - .dev_groups = bmof_groups, - }, - .probe = wmi_bmof_probe, - .remove = wmi_bmof_remove, - .id_table = wmi_bmof_id_table, - .no_singleton = true, -}; - -module_wmi_driver(wmi_bmof_driver); - -MODULE_DEVICE_TABLE(wmi, wmi_bmof_id_table); -MODULE_AUTHOR("Andrew Lutomirski "); -MODULE_DESCRIPTION("WMI embedded Binary MOF driver"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From da7d021af43d696d7d645a008a259713c775c82b Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Sat, 22 Aug 2026 02:28:39 +0200 Subject: platform/x86: uniwill-laptop: Fix brightness notify for 3 level keyboards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The devices with 3 brightness level keyboards send UNIWILL_OSD_KB_LED_LEVEL0, UNIWILL_OSD_KB_LED_LEVEL2, and UNIWILL_OSD_KB_LED_LEVEL4 and not UNIWILL_OSD_KB_LED_LEVEL0, UNIWILL_OSD_KB_LED_LEVEL1, and UNIWILL_OSD_KB_LED_LEVEL2. This patch maps the events correctly to brightness levels 0, 1, and 2. Signed-off-by: Werner Sembach Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260822003012.851574-1-wse@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index eeead0159828..f3e7750f4d9d 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1631,6 +1631,32 @@ static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) struct led_classdev *led_cdev; int ret; + /* + * The devices with 3 brightness level keyboards send + * UNIWILL_OSD_KB_LED_LEVEL0, UNIWILL_OSD_KB_LED_LEVEL2, and + * UNIWILL_OSD_KB_LED_LEVEL4 which need to be mapped to 0, 1, and 2. + */ + switch (data->kbd_led_max_brightness) { + case 4: + break; + case 2: + switch (brightness) { + case 0: + break; + case 2: + brightness = 1; + break; + case 4: + brightness = 2; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (data->kbd_led_single_color) led_cdev = &data->kbd_led_cdev; else -- cgit v1.2.3 From 81655643e6cbfd6a961503cdd49c0012bc9dbf0d Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Sat, 22 Aug 2026 02:28:40 +0200 Subject: platform/x86: uniwill-laptop: Enable kb backlight and lightbar for TUXEDO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable EC controlled keyboard backlight across the board for TUXEDO devices. This has split up the device descriptors even more, so the "featureset" naming scheme was dropped in favor of a "generalized board_name" naming scheme. Also Stellaris and Polaris Gen3 are now identified by product_sku to split them up. Also some TUXEDO devices with lightbar still had the support missing. This patch completes the list in this point. Signed-off-by: Werner Sembach Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260822003012.851574-2-wse@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 309 +++++++++++++++++++++------- 1 file changed, 233 insertions(+), 76 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index f3e7750f4d9d..07b69629bb5e 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -645,6 +645,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_1: case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: @@ -684,6 +685,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_1: case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: @@ -2738,35 +2740,47 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { .lightbar_max_brightness = 200, }; -/* - * The featuresets below reflect somewhat chronological changes: - * 1 -> 2: UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL is added to the EC firmware. - * 2 -> 3: UNIWILL_FEATURE_USB_C_POWER_PRIORITY is removed from the EC firmware. - * Some devices might divert from this timeline. - */ +static struct uniwill_device_descriptor pfxnuxx_pfxluxx_phxprxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, +}; -static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = { +static struct uniwill_device_descriptor gkxmrxx_gkxnpxx_gkxnrxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, }; -static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor gmxmpxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + .lightbar_max_brightness = 36, }; -static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2775,10 +2789,109 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, +}; + +static struct uniwill_device_descriptor gmxmgxx_gmxngxx_gmxxgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_pol_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_ste_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + .lightbar_max_brightness = 36, }; -static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { +static int gmxrgxx_probe(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_1, &value); + if (ret < 0) + return ret; + + if (value & LIGHTBAR) { + data->features |= UNIWILL_FEATURE_LIGHTBAR; + data->lightbar_max_brightness = 36; + } + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &value); + if (ret < 0) + return ret; + + if (value & RGB_KEYBOARD) { + data->features |= UNIWILL_FEATURE_KEYBOARD_BACKLIGHT; + data->kbd_led_single_color = false; + data->kbd_led_max_brightness = 4; + } + + return 0; +}; + +static struct uniwill_device_descriptor gmxrgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .probe = gmxrgxx_probe, +}; + +static struct uniwill_device_descriptor gmxagxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .lightbar_max_brightness = 36, +}; + +static struct uniwill_device_descriptor gmxxgxx_gmxpxxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2789,7 +2902,7 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, }; -static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { +static struct uniwill_device_descriptor gxxhrxx_gxxmrxx_xxxx4nxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2797,10 +2910,29 @@ static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_AC_AUTO_BOOT | - UNIWILL_FEATURE_USB_POWERSHARE, + UNIWILL_FEATURE_USB_POWERSHARE | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 4, }; -static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor gmxhgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2813,7 +2945,7 @@ static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __ini UNIWILL_FEATURE_USB_POWERSHARE, }; -static int phxtxx1_probe(struct uniwill_data *data) +static int phxtuxx_phxtrxx_probe(struct uniwill_data *data) { unsigned int value; int ret; @@ -2828,17 +2960,20 @@ static int phxtxx1_probe(struct uniwill_data *data) return 0; }; -static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = { +static struct uniwill_device_descriptor phxtuxx_phxtrxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .probe = phxtxx1_probe, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, + .probe = phxtuxx_phxtrxx_probe, }; -static int phxarx1_phxaqf1_probe(struct uniwill_data *data) +static int phxarxx_phxaqxx_probe(struct uniwill_data *data) { unsigned int value; int ret; @@ -2854,22 +2989,28 @@ static int phxarx1_phxaqf1_probe(struct uniwill_data *data) return 0; }; -static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = { +static struct uniwill_device_descriptor phxarxx_phxaqxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .probe = phxarx1_phxaqf1_probe, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, + .probe = phxarxx_phxaqxx_probe, }; -static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { +static struct uniwill_device_descriptor pfxpuxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_CPU_TEMP | - UNIWILL_FEATURE_PRIMARY_FAN, + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, }; static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { @@ -2964,7 +3105,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), }, - .driver_data = &phxtxx1_descriptor, + .driver_data = &phxtuxx_phxtrxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", @@ -2972,7 +3113,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", @@ -2980,7 +3121,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), }, - .driver_data = &phxarx1_phxaqf1_descriptor, + .driver_data = &phxarxx_phxaqxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7", @@ -2988,7 +3129,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -2996,7 +3137,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -3004,7 +3145,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", @@ -3012,7 +3153,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", @@ -3020,7 +3161,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", @@ -3028,7 +3169,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -3036,7 +3177,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -3044,7 +3185,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", @@ -3052,7 +3193,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", @@ -3060,7 +3201,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3068,7 +3209,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3076,7 +3217,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", @@ -3084,7 +3225,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", @@ -3092,7 +3233,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -3100,7 +3241,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -3108,7 +3249,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -3116,7 +3257,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -3124,7 +3265,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -3132,7 +3273,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -3140,7 +3281,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -3148,7 +3289,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -3156,7 +3297,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Trinity 15 Intel Gen1", @@ -3164,7 +3305,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gmxmpxx_descriptor, }, { .ident = "TUXEDO Trinity 17 Intel Gen1", @@ -3172,7 +3313,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gmxmpxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 AMD", @@ -3180,7 +3321,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 Intel", @@ -3188,23 +3329,39 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, + }, + { + .ident = "TUXEDO Polaris 15/17 Gen3 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XA03"), + }, + .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor, + }, + { + .ident = "TUXEDO Polaris 15/17 Gen3 Intel", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XI03"), + }, + .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor, }, { - .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", + .ident = "TUXEDO Stellaris 15/17 Gen3 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XA03"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor, }, { - .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", + .ident = "TUXEDO Stellaris 15/17 Gen3 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XI03"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", @@ -3212,7 +3369,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxrgxx_descriptor, }, { .ident = "TUXEDO Stellaris 15 Gen4 Intel", @@ -3220,7 +3377,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxagxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen5 AMD", @@ -3228,7 +3385,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen5 AMD", @@ -3236,7 +3393,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxxgxx_gmxpxxx_descriptor, }, { .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", @@ -3244,7 +3401,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxxgxx_gmxpxxx_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", @@ -3252,7 +3409,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxhgxx_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", @@ -3260,7 +3417,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3268,7 +3425,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3276,7 +3433,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", @@ -3284,7 +3441,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 AMD", @@ -3292,7 +3449,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3300,7 +3457,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3308,7 +3465,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Book BA15 Gen10 AMD", @@ -3316,7 +3473,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), }, - .driver_data = &pf5pu1g_descriptor, + .driver_data = &pfxpuxx_descriptor, }, { .ident = "TUXEDO Pulse 14 Gen1 AMD", @@ -3324,7 +3481,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen1 AMD", @@ -3332,7 +3489,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen2 AMD", @@ -3340,7 +3497,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { } }; -- cgit v1.2.3 From 540b8ae192afb5566c1b13b6884aee87107a26b9 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sat, 19 Sep 2026 11:00:37 +0500 Subject: platform/x86: hp-bioscfg: fix 16-byte heap overflow for empty auth token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_calculate_security_buffer() special-cases an empty authentication string and returns a fixed 4 bytes (sizeof(u16) * 2). But hp_populate_security_buffer() does not special-case that same input: for any authentication string that does not start with BEAM_PREFIX, including the empty string, it always builds "UTF_PREFIX + authentication" and converts the result to UTF-16, writing a 2-byte length header plus 2 bytes per character of "" (9 characters), 20 bytes total, regardless of how long "authentication" itself is. The caller, hp_set_attribute(), sizes its kmalloc() buffer using hp_calculate_security_buffer()'s return value, so for an empty authentication token it allocates 4 bytes for the security area but hp_populate_security_buffer() then writes 20 bytes into it, causing a 16-byte heap buffer overflow. The authentication token used here is the current admin/setup password, which is an empty string by default until one is configured. Any write to a writable BIOS attribute while no admin password has been set reaches this path. Fix by removing the special-case early return for an empty string in hp_calculate_security_buffer(). The generic calculation that follows already accounts for the UTF_PREFIX correctly, which naturally yields the same 20 bytes that hp_populate_security_buffer() writes for an empty string, avoiding duplicate logic for special cases. Fixes: b2715aa2e135 ("platform/x86: hp-bioscfg: spmobj-attributes") Reported-by: Josh Snyder Closes: https://lore.kernel.org/platform-driver-x86/20260402-hp-bioscfg-overflow-v1-1-6985f8c9e67c@code406.com/ Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260919060037.84602-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index 4d94e48c1a4c..abf8ce20fe15 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -47,9 +47,6 @@ size_t hp_calculate_security_buffer(const char *authentication) return sizeof(u16) * 2; authlen = strlen(authentication); - if (!authlen) - return sizeof(u16) * 2; - size = sizeof(u16) + authlen * sizeof(u16); if (!strstarts(authentication, BEAM_PREFIX)) size += strlen(UTF_PREFIX) * sizeof(u16); -- cgit v1.2.3 From 517df3809e577e317cebf06db789fa4ec63ea7fc Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sat, 19 Sep 2026 11:11:08 +0500 Subject: platform/x86: hp-bioscfg: remove dead bounds check in audit_log_entries_show MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audit_log_entries_show() checks "count * LOG_ENTRY_SIZE > PAGE_SIZE" prior to entering the loop. Because "count" is already bounded to fit within PAGE_SIZE, the inner loop check "(LOG_ENTRY_SIZE * i) > PAGE_SIZE" can never be reached and is dead code. Remove the redundant inner bounds check and eliminate the unnecessary "else" block after "break" to clean up the loop control flow. Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260919061108.87240-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/hp/hp-bioscfg/surestart-attributes.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c index b57e42f29282..d689ee844be2 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c @@ -90,17 +90,15 @@ static ssize_t audit_log_entries_show(struct kobject *kobj, HPWMI_SURESTART, audit_log_buffer, 1, 128); - if (ret < 0 || (LOG_ENTRY_SIZE * i) > PAGE_SIZE) { - /* - * Encountered a failure while reading - * individual logs. Only a partial list of - * audit log will be returned. - */ + /* + * Encountered a failure while reading individual logs. + * Only a partial list of audit log will be returned. + */ + if (ret < 0) break; - } else { - memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE); - buf += LOG_ENTRY_SIZE; - } + + memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE); + buf += LOG_ENTRY_SIZE; } return i * LOG_ENTRY_SIZE; -- cgit v1.2.3 From 0e0619a13da77cb2977bddf86057c7be9af53c10 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 6 Sep 2026 02:48:01 +0500 Subject: platform/x86: hp-bioscfg: consolidate common package-element parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_populate_string_elements_from_package(), hp_populate_integer_elements_from_package(), hp_populate_enumeration_elements_from_package(), hp_populate_ordered_list_elements_from_package(), and hp_populate_password_elements_from_package() each parse the same PATH..SECURITY_LEVEL range into their own struct common_data sub-object, copy-pasted five times. Factor it into hp_get_common_data_from_package(), called from all five, mirroring hp_get_common_data_from_buffer()'s existing split for the buffer side. The five PREREQUISITES loops weren't identical: on a bad prerequisite string, string/integer/ordered_list continue, enumeration returns -EINVAL, password breaks. The shared helper continues, matching the majority and hp_get_common_data_from_buffer()'s own handling of the equivalent failure. This changes enumeration (no longer aborts the package) and password (no longer leaves later prerequisites unfilled). The expected_*_types[eloc] != obj[elem].type check above this switch has the same split -- enumeration warns and continues, the rest pr_err() and return -EIO -- left alone here; consolidating it needs each type's expected_*_types[] table passed into a shared checker, not just struct common_data. Suggested-by: Ilpo Järvinen Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260905214801.76265-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 97 ++++++++++++++++++++++ drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 3 + .../platform/x86/hp/hp-bioscfg/enum-attributes.c | 57 ++----------- .../platform/x86/hp/hp-bioscfg/int-attributes.c | 57 ++----------- .../x86/hp/hp-bioscfg/order-list-attributes.c | 58 ++----------- .../x86/hp/hp-bioscfg/passwdobj-attributes.c | 57 ++----------- .../platform/x86/hp/hp-bioscfg/string-attributes.c | 58 ++----------- 7 files changed, 125 insertions(+), 262 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 2dab9c0624b2..dd51a4017d8e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -484,6 +484,103 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int * return ret; } +/** + * hp_get_common_data_from_package() - Parse the package elements shared + * by every attribute type (PATH..SECURITY_LEVEL) + * @obj: ACPI object package for this attribute instance + * @obj_count: Number of elements in @obj + * @elem: Current position in @obj; advanced in place when PREREQUISITES + * consumes additional array elements + * @eloc: Current common-element locator; advanced in place when + * PREREQUISITES_SIZE is zero, since PREREQUISITES is then omitted + * by BIOS + * @int_value: Integer value already decoded by the caller for *@eloc, + * when applicable + * @str_value: String value already decoded by the caller for *@eloc, when + * applicable; reused and reset to NULL while walking + * PREREQUISITES, same as the caller does with its own copy + * @common: Destination common_data struct for this attribute instance + * + * Called by each type's own hp_populate_*_elements_from_package() + * instead of duplicating this parsing per type, since every BIOS + * attribute package uses the same layout for these elements (see + * struct common_data). + * + * Return: 0 on success, or -EINVAL if @obj is too small for the + * declared PREREQUISITES_SIZE. + */ +int hp_get_common_data_from_package(union acpi_object *obj, int obj_count, + int *elem, int *eloc, u32 int_value, + char **str_value, struct common_data *common) +{ + int value_len; + int reqs; + int size; + + switch (*eloc) { + case PATH: + strscpy(common->path, *str_value); + break; + case IS_READONLY: + common->is_readonly = int_value; + break; + case DISPLAY_IN_UI: + common->display_in_ui = int_value; + break; + case REQUIRES_PHYSICAL_PRESENCE: + common->requires_physical_presence = int_value; + break; + case SEQUENCE: + common->sequence = int_value; + break; + case PREREQUISITES_SIZE: + if (int_value > MAX_PREREQUISITES_SIZE) { + pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); + int_value = MAX_PREREQUISITES_SIZE; + } + common->prerequisites_size = int_value; + + /* + * This step is needed to keep the expected + * element list pointing to the right obj[elem].type + * when the size is zero. PREREQUISITES + * object is omitted by BIOS when the size is + * zero. + */ + if (common->prerequisites_size == 0) + (*eloc)++; + break; + case PREREQUISITES: + size = min_t(u32, common->prerequisites_size, MAX_PREREQUISITES_SIZE); + + for (reqs = 0; reqs < size; reqs++) { + if (*elem + reqs >= obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + + if (hp_convert_hexstr_to_str(obj[*elem + reqs].string.pointer, + obj[*elem + reqs].string.length, + str_value, &value_len)) + continue; + + strscpy(common->prerequisites[reqs], *str_value); + kfree(*str_value); + *str_value = NULL; + } + if (size) + *elem += size - 1; + break; + case SECURITY_LEVEL: + common->security_level = int_value; + break; + default: + break; + } + + return 0; +} + /* map output size to the corresponding WMI method id */ int hp_encode_outsize_for_pvsz(int outsize) { diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index ac57d6eab4c3..94619c034187 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -493,5 +493,8 @@ void hp_friendly_user_name_update(char *path, const char *attr_name, char *attr_display, int attr_size); int hp_wmi_error_and_message(int error_code); int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, struct common_data *common); +int hp_get_common_data_from_package(union acpi_object *obj, int obj_count, + int *elem, int *eloc, u32 int_value, + char **str_value, struct common_data *common); #endif diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index 446dd18d2cee..8f1ecbf127bc 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -132,7 +132,6 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum u32 size = 0; u32 int_value = 0; int elem = 0; - int reqs; int pos_values; int ret; int eloc; @@ -176,64 +175,18 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum case VALUE: break; case PATH: - strscpy(enum_data->common.path, str_value); - break; case IS_READONLY: - enum_data->common.is_readonly = int_value; - break; case DISPLAY_IN_UI: - enum_data->common.display_in_ui = int_value; - break; case REQUIRES_PHYSICAL_PRESENCE: - enum_data->common.requires_physical_presence = int_value; - break; case SEQUENCE: - enum_data->common.sequence = int_value; - break; case PREREQUISITES_SIZE: - if (int_value > MAX_PREREQUISITES_SIZE) { - pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); - int_value = MAX_PREREQUISITES_SIZE; - } - enum_data->common.prerequisites_size = int_value; - - /* - * This step is needed to keep the expected - * element list pointing to the right obj[elem].type - * when the size is zero. PREREQUISITES - * object is omitted by BIOS when the size is - * zero. - */ - if (int_value == 0) - eloc++; - break; - case PREREQUISITES: - size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); - for (reqs = 0; reqs < size; reqs++) { - if (elem + reqs >= enum_obj_count) { - pr_err("Error enum-objects package is too small\n"); - return -EINVAL; - } - - ret = hp_convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer, - enum_obj[elem + reqs].string.length, - &str_value, &value_len); - - if (ret) - return -EINVAL; - - strscpy(enum_data->common.prerequisites[reqs], str_value); - - kfree(str_value); - str_value = NULL; - } - if (size) - elem += size - 1; - break; - case SECURITY_LEVEL: - enum_data->common.security_level = int_value; + ret = hp_get_common_data_from_package(enum_obj, enum_obj_count, + &elem, &eloc, int_value, &str_value, + &enum_data->common); + if (ret) + return ret; break; case ENUM_CURRENT_VALUE: diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index a27907066448..f2774eff613b 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -145,9 +145,7 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ int ret; u32 int_value = 0; int elem; - int reqs; int eloc; - int size; struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id]; if (!integer_obj) @@ -192,63 +190,18 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ integer_data->current_value = int_value; break; case PATH: - strscpy(integer_data->common.path, str_value); - break; case IS_READONLY: - integer_data->common.is_readonly = int_value; - break; case DISPLAY_IN_UI: - integer_data->common.display_in_ui = int_value; - break; case REQUIRES_PHYSICAL_PRESENCE: - integer_data->common.requires_physical_presence = int_value; - break; case SEQUENCE: - integer_data->common.sequence = int_value; - break; case PREREQUISITES_SIZE: - if (int_value > MAX_PREREQUISITES_SIZE) { - pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); - int_value = MAX_PREREQUISITES_SIZE; - } - integer_data->common.prerequisites_size = int_value; - - /* - * This step is needed to keep the expected - * element list pointing to the right obj[elem].type - * when the size is zero. PREREQUISITES - * object is omitted by BIOS when the size is - * zero. - */ - if (integer_data->common.prerequisites_size == 0) - eloc++; - break; case PREREQUISITES: - size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE); - - for (reqs = 0; reqs < size; reqs++) { - if (elem + reqs >= integer_obj_count) { - pr_err("Error elem-objects package is too small\n"); - return -EINVAL; - } - - ret = hp_convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer, - integer_obj[elem + reqs].string.length, - &str_value, &value_len); - - if (ret) - continue; - - strscpy(integer_data->common.prerequisites[reqs], str_value); - kfree(str_value); - str_value = NULL; - } - if (size) - elem += size - 1; - break; - case SECURITY_LEVEL: - integer_data->common.security_level = int_value; + ret = hp_get_common_data_from_package(integer_obj, integer_obj_count, + &elem, &eloc, int_value, &str_value, + &integer_data->common); + if (ret) + return ret; break; case INT_LOWER_BOUND: integer_data->lower_bound = int_value; diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c index 5bf8d40bdf81..95fca0919f1f 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -130,11 +130,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord char *str_value = NULL; int value_len = 0; int ret; - u32 size; u32 int_value = 0; int elem; int olist_elem; - int reqs; int eloc; char *tmpstr = NULL; char *part_tmp = NULL; @@ -180,64 +178,18 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP); break; case PATH: - strscpy(ordered_list_data->common.path, str_value); - break; case IS_READONLY: - ordered_list_data->common.is_readonly = int_value; - break; case DISPLAY_IN_UI: - ordered_list_data->common.display_in_ui = int_value; - break; case REQUIRES_PHYSICAL_PRESENCE: - ordered_list_data->common.requires_physical_presence = int_value; - break; case SEQUENCE: - ordered_list_data->common.sequence = int_value; - break; case PREREQUISITES_SIZE: - if (int_value > MAX_PREREQUISITES_SIZE) { - pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); - int_value = MAX_PREREQUISITES_SIZE; - } - ordered_list_data->common.prerequisites_size = int_value; - - /* - * This step is needed to keep the expected - * element list pointing to the right obj[elem].type - * when the size is zero. PREREQUISITES - * object is omitted by BIOS when the size is - * zero. - */ - if (int_value == 0) - eloc++; - break; case PREREQUISITES: - size = min_t(u32, ordered_list_data->common.prerequisites_size, - MAX_PREREQUISITES_SIZE); - for (reqs = 0; reqs < size; reqs++) { - if (elem + reqs >= order_obj_count) { - pr_err("Error elem-objects package is too small\n"); - return -EINVAL; - } - - ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer, - order_obj[elem + reqs].string.length, - &str_value, &value_len); - - if (ret) - continue; - - strscpy(ordered_list_data->common.prerequisites[reqs], str_value); - - kfree(str_value); - str_value = NULL; - } - if (size) - elem += size - 1; - break; - case SECURITY_LEVEL: - ordered_list_data->common.security_level = int_value; + ret = hp_get_common_data_from_package(order_obj, order_obj_count, + &elem, &eloc, int_value, &str_value, + &ordered_list_data->common); + if (ret) + return ret; break; case ORD_LIST_SIZE: diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index a9e178637416..95fa0aeba920 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -224,7 +224,6 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor u32 size; u32 int_value = 0; int elem; - int reqs; int eloc; int pos_values; struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; @@ -268,64 +267,18 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor case VALUE: break; case PATH: - strscpy(password_data->common.path, str_value); - break; case IS_READONLY: - password_data->common.is_readonly = int_value; - break; case DISPLAY_IN_UI: - password_data->common.display_in_ui = int_value; - break; case REQUIRES_PHYSICAL_PRESENCE: - password_data->common.requires_physical_presence = int_value; - break; case SEQUENCE: - password_data->common.sequence = int_value; - break; case PREREQUISITES_SIZE: - if (int_value > MAX_PREREQUISITES_SIZE) { - pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); - int_value = MAX_PREREQUISITES_SIZE; - } - password_data->common.prerequisites_size = int_value; - - /* This step is needed to keep the expected - * element list pointing to the right obj[elem].type - * when the size is zero. PREREQUISITES - * object is omitted by BIOS when the size is - * zero. - */ - if (int_value == 0) - eloc++; - break; case PREREQUISITES: - size = min_t(u32, password_data->common.prerequisites_size, - MAX_PREREQUISITES_SIZE); - - for (reqs = 0; reqs < size; reqs++) { - if (elem + reqs >= password_obj_count) { - pr_err("Error elem-objects package is too small\n"); - return -EINVAL; - } - - ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer, - password_obj[elem + reqs].string.length, - &str_value, &value_len); - - if (ret) - break; - - strscpy(password_data->common.prerequisites[reqs], str_value); - - kfree(str_value); - str_value = NULL; - - } - if (size) - elem += size - 1; - break; case SECURITY_LEVEL: - password_data->common.security_level = int_value; + ret = hp_get_common_data_from_package(password_obj, password_obj_count, + &elem, &eloc, int_value, &str_value, + &password_data->common); + if (ret) + return ret; break; case PSWD_MIN_LENGTH: password_data->min_password_length = int_value; diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index f3dfba270f7e..e9bfbef8a3a4 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -135,9 +135,7 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob int ret = 0; u32 int_value = 0; int elem; - int reqs; int eloc; - int size; struct string_data *string_data = &bioscfg_drv.string_data[instance_id]; if (!string_obj) @@ -181,64 +179,18 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob strscpy(string_data->current_value, str_value); break; case PATH: - strscpy(string_data->common.path, str_value); - break; case IS_READONLY: - string_data->common.is_readonly = int_value; - break; case DISPLAY_IN_UI: - string_data->common.display_in_ui = int_value; - break; case REQUIRES_PHYSICAL_PRESENCE: - string_data->common.requires_physical_presence = int_value; - break; case SEQUENCE: - string_data->common.sequence = int_value; - break; case PREREQUISITES_SIZE: - if (int_value > MAX_PREREQUISITES_SIZE) { - pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n"); - int_value = MAX_PREREQUISITES_SIZE; - } - string_data->common.prerequisites_size = int_value; - - /* - * This step is needed to keep the expected - * element list pointing to the right obj[elem].type - * when the size is zero. PREREQUISITES - * object is omitted by BIOS when the size is - * zero. - */ - if (string_data->common.prerequisites_size == 0) - eloc++; - break; case PREREQUISITES: - size = min_t(u32, string_data->common.prerequisites_size, - MAX_PREREQUISITES_SIZE); - - for (reqs = 0; reqs < size; reqs++) { - if (elem + reqs >= string_obj_count) { - pr_err("Error elem-objects package is too small\n"); - return -EINVAL; - } - - ret = hp_convert_hexstr_to_str(string_obj[elem + reqs].string.pointer, - string_obj[elem + reqs].string.length, - &str_value, &value_len); - - if (ret) - continue; - - strscpy(string_data->common.prerequisites[reqs], str_value); - kfree(str_value); - str_value = NULL; - } - if (size) - elem += size - 1; - break; - case SECURITY_LEVEL: - string_data->common.security_level = int_value; + ret = hp_get_common_data_from_package(string_obj, string_obj_count, + &elem, &eloc, int_value, &str_value, + &string_data->common); + if (ret) + return ret; break; case STR_MIN_LENGTH: string_data->min_length = int_value; -- cgit v1.2.3 From 6dea092b261f04d632411c2be52d13190f5f67b1 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 26 Aug 2026 15:30:12 +0500 Subject: platform/x86: hp-wmi: fix heap OOB read and memory corruption in hp_wmi_perform_query() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_wmi_perform_query() evaluates an ACPI WMI method and expects a struct bios_return header in the returned buffer. However, it only checks that the ACPI object type is ACPI_TYPE_BUFFER without verifying that obj->buffer.length is at least sizeof(*bios_return) (8 bytes). If the firmware or ACPI method returns a buffer shorter than 8 bytes: 1. bios_return->return_code at offset 4 reads out of bounds from the allocated ACPI buffer slab. 2. If return_code evaluates to 0 and outsize is non-zero, (int)(obj->buffer.length - sizeof(*bios_return)) evaluates to a negative integer (e.g. -8 for a 0-length buffer). actual_outsize is set to this negative value and passed to memcpy(buffer, ..., actual_outsize), where the signed negative value is cast to size_t (e.g. 0xfffffffffffffff8), attempting to copy ~18 Exabytes and triggering an immediate kernel crash. Additionally, memset(buffer + actual_outsize, ...) performs an out-of-bounds write before buffer. Fix this by ensuring obj->buffer.length is at least sizeof(*bios_return) before accessing the return code, and compute actual_outsize safely using min_t(u32, ...), which is sound specifically because the guard above already rules out obj->buffer.length being smaller than sizeof(*bios_return) by the time that subtraction runs. Fixes: c3021ea1beee ("hp-wmi: allow setting input and output buffer sizes separately") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260826103012.240453-1-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index bc2ada5dfa63..eeb66a7db346 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -711,8 +711,9 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, goto out_free; } - if (obj->type != ACPI_TYPE_BUFFER) { - pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); + if (obj->type != ACPI_TYPE_BUFFER || + obj->buffer.length < sizeof(*bios_return)) { + pr_warn("query 0x%x returned wrong type or too small buffer\n", query); ret = -EINVAL; goto out_free; } @@ -731,7 +732,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, if (!outsize) goto out_free; - actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return))); + actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return)); memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize); memset(buffer + actual_outsize, 0, outsize - actual_outsize); -- cgit v1.2.3 From 497fa09961b40081c64853c5b81b4c8d967738c6 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 1 Aug 2026 00:00:07 +0900 Subject: platform/x86: toshiba_haps: Remove unnecessary ret in toshiba_haps_resume() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The return value of toshiba_haps_protection_level() is ignored because ret is immediately overwritten by the toshiba_haps_reset_protection() call. And ret is only used by the check, which has no effect because both branches return the same value. So remove the check and ret, and return the value directly. No functional change. Found by the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260731150010.1025556-1-ekffu200098@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_haps.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index c6633b74029f..b2eb91929d1d 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -244,18 +244,13 @@ static int toshiba_haps_suspend(struct device *device) static int toshiba_haps_resume(struct device *device) { struct toshiba_haps_dev *haps = dev_get_drvdata(device); - int ret; /* Set the stored protection level */ - ret = toshiba_haps_protection_level(haps->acpi_dev->handle, - haps->protection_level); + toshiba_haps_protection_level(haps->acpi_dev->handle, + haps->protection_level); /* Reset the protection on resume */ - ret = toshiba_haps_reset_protection(haps->acpi_dev->handle); - if (ret != 0) - return ret; - - return ret; + return toshiba_haps_reset_protection(haps->acpi_dev->handle); } #endif -- cgit v1.2.3 From bcbdfe03566ad0d01c6af3b5476e8cadffe4d3df Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Mon, 31 Aug 2026 18:48:08 +0000 Subject: platform/x86: asus-wmi: validate custom fan curves on enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Custom fan curves reach firmware unchecked, so a curve whose temperature or PWM decreases from one point to the next is accepted. The firmware does not respect such a curve: a first point of 100% followed by 50% leaves every point running at 100%. The kernel keeps reporting the points that were written, so sysfs stops describing what the fans are actually doing. Check the curve when enabling and reject it if it is invalid. Validation is done on enable rather than on write because fan curves are written one point at a time and are inconsistent mid-write. Fixes: 0f0ac158d28f ("platform/x86: asus-wmi: Add support for custom fan curves") Reported-by: Nir Yehoshua Signed-off-by: Ahmed Yaseen Link: https://patch.msgid.link/20260831-fan-curve-validation-v2-1-7274ca16c8fe@ghoul.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index b234734b6c56..a2e552b207fb 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -3749,6 +3749,33 @@ static int fan_curve_write(struct asus_wmi *asus, arg1, arg2, arg3, arg4, &ret); } +/* + * A fan curve is a set of points the firmware interpolates between, so it + * only makes sense if neither temperature nor PWM ever decreases along it. + */ +static int fan_curve_validate(struct device *dev, struct fan_curve_data *data) +{ + u8 *percents = data->percents; + u8 *temps = data->temps; + int i; + + for (i = 1; i < FAN_CURVE_POINTS; i++) { + if (temps[i] < temps[i - 1]) { + dev_warn(dev, "fan curve: temperature decreases at point %d (%u < %u)\n", + i, temps[i], temps[i - 1]); + return -EINVAL; + } + + if (percents[i] < percents[i - 1]) { + dev_warn(dev, "fan curve: pwm decreases at point %d (%u < %u)\n", + i, percents[i], percents[i - 1]); + return -EINVAL; + } + } + + return 0; +} + static ssize_t fan_curve_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -3833,6 +3860,11 @@ static ssize_t fan_curve_enable_store(struct device *dev, } if (data->enabled) { + err = fan_curve_validate(dev, data); + if (err) { + data->enabled = false; + return err; + } err = fan_curve_write(asus, data); if (err) return err; -- cgit v1.2.3 From 9f3eebbbd1033de0abcc2694ba4b455d30fe33f9 Mon Sep 17 00:00:00 2001 From: Jack Boykin Date: Wed, 2 Sep 2026 16:02:12 -0500 Subject: platform/x86/amd/pmf: Fix power_supply refcount leak in amd_pmf_get_battery_prop() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit power_supply_get_by_name() takes a reference that is only dropped on the error path. On success it leaks, five times per policy evaluation. Unregistering the battery later trips the use_cnt WARN_ON in power_supply_unregister() and never frees it. While here, return -ENODEV instead of an uninitialised 'value' when no battery matches, and stop at the first supply found rather than letting the last one in the table win. Found by reading the code. Fix and changelog drafted with Claude Code (Fable 5.1) and reviewed by hand. Compile-tested only (allmodconfig, W=1, sparse); not run on Smart PC hardware. Fixes: f4627dfd0e19 ("platform/x86/amd/pmf: Add support to get inputs from other subsystems") Cc: stable@vger.kernel.org Assisted-by: LLM sparse Signed-off-by: Jack Boykin Link: https://patch.msgid.link/20260902210217.69483-1-jtboykin.jb@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/spc.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 94355b435a66..ba852397cba5 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -199,13 +199,11 @@ static int amd_pmf_get_battery_prop(enum power_supply_property prop) continue; ret = power_supply_get_property(psy, prop, &value); - if (ret) { - power_supply_put(psy); - return ret; - } + power_supply_put(psy); + return ret ? ret : value.intval; } - return value.intval; + return -ENODEV; } static int amd_pmf_get_battery_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) -- cgit v1.2.3 From 2b89719f55f42ccbed219c4906e8c4ddfb117058 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 7 Sep 2026 11:01:05 +0200 Subject: platform/x86: thinkpad_acpi: Remove unreachable code in beep_read() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The procfs entry /proc/acpi/ibm/beep is only created if beep_init() succeeds, which requires beep_handle to be non-NULL. Remove the unreachable code branch from beep_read(). Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260907090103.352995-3-blum@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 1225b01ed14d..33a424b63abd 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -5997,12 +5997,8 @@ static int __init beep_init(struct ibm_init_struct *iibm) static int beep_read(struct seq_file *m) { - if (!beep_handle) - seq_puts(m, "status:\t\tnot supported\n"); - else { - seq_puts(m, "status:\t\tsupported\n"); - seq_puts(m, "commands:\t ( is 0-17)\n"); - } + seq_puts(m, "status:\t\tsupported\n"); + seq_puts(m, "commands:\t ( is 0-17)\n"); return 0; } -- cgit v1.2.3 From a102207eaf4a18c60c2ffbdc421ad3d30bfed591 Mon Sep 17 00:00:00 2001 From: Daasaradhi Mannava Date: Wed, 16 Sep 2026 17:48:12 +0000 Subject: platform/x86: msi-ec: Report valid charge thresholds when unset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The charge thresholds are computed by subtracting a fixed offset from the raw EC value. When no charge limit has been set, the EC can hold a value below range_min (0x80 on an MSI GL65 Leopard 9SCXK), and reading the sysfs attributes returns nonsense: charge_control_start_threshold: -10 charge_control_end_threshold: 0 Both attributes are documented to be within 0 - 100, and an end threshold of 0 wrongly suggests that charging is disabled. The store path already rejects values outside range_min..range_max. Apply the same range to the show path and report the maximum threshold for any value outside of it. Tested on an MSI GL65 Leopard 9SCXK (EC firmware 16U8EMS2.100) with the EC holding 0x80: the attributes now read 90 and 100. Fixes: 392cacf2aa10 ("platform/x86: Add new msi-ec driver") Assisted-by: LLM Signed-off-by: Daasaradhi Mannava Link: https://patch.msgid.link/20260916174812.11496-1-daasaradhimannava@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 566dfc73cb67..1762551e072a 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1279,6 +1279,15 @@ static ssize_t charge_control_threshold_show(u8 offset, if (result < 0) return result; + /* + * The EC may hold an out-of-range value (e.g. 0x80) when no charge + * limit has been set. Report the maximum threshold instead of a + * meaningless (possibly negative) percentage. + */ + if (rdata < conf.charge_control.range_min || + rdata > conf.charge_control.range_max) + rdata = conf.charge_control.range_max; + return sysfs_emit(buf, "%i\n", rdata - offset); } -- cgit v1.2.3 From 81eb2b361ebf86463a0829d569b046219db5985f Mon Sep 17 00:00:00 2001 From: Daasaradhi Mannava Date: Wed, 16 Sep 2026 17:48:30 +0000 Subject: platform/x86: msi-ec: Add MSI GL65 Leopard 9SCXK EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the firmware string '16U8EMS2.100' to the ALLOWED_FW_1 array. This enables Embedded Controller support, including battery charge thresholds, for the MSI GL65 Leopard 9SCXK (MS-16U8) laptop. The EC memory layout of this firmware matches the existing config: the CPU and GPU temperatures at 0x68 and 0x80 agree with coretemp and the NVIDIA driver, the fan mode at 0xf4 reads 0x0d (auto), and the charge control register is at 0xef. Tested on MSI GL65 Leopard 9SCXK with EC firmware 16U8EMS2.100 and BIOS E16U8IMS.504: with AC connected and the battery at 18%, writing 10 to charge_control_end_threshold stops charging ("Not charging", 0 A), and writing 100 resumes it. Assisted-by: LLM Signed-off-by: Daasaradhi Mannava Link: https://patch.msgid.link/20260916174830.11620-1-daasaradhimannava@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 1762551e072a..268b24c781c9 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -122,6 +122,7 @@ static const char * const ALLOWED_FW_1[] __initconst = { "17F2EMS1.104", "17F2EMS1.106", "17F2EMS1.107", + "16U8EMS2.100", // MSI GL65 Leopard 9SCXK NULL }; -- cgit v1.2.3 From b9428edaf4109e86a97254228d831767d0acc1f8 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Fri, 18 Sep 2026 11:14:59 +0200 Subject: platform/x86: asus-wmi: Serialize WMI method evaluations with a mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Concurrent evaluations of ASUS WMI management methods (from ACPI notify, HID, userspace daemons, and debugfs) enter the BIOS ACPI/SMM interface simultaneously, triggering re-entrant SMIs or EC mailbox buffer corruption. Fix this at the root by introducing a centralized evaluation helper (asus_wmi_evaluate_method_locked()) protected by a global mutex (asus_wmi_eval_lock) using guard(mutex). Route all evaluations of ASUS_WMI_MGMT_GUID (method3, method5, method_buf, and show_call) through this helper. A static mutex is required because asus_wmi_evaluate_method() is an exported symbol used by external modules (such as hid-asus and asus-armoury) that lack access to struct asus_wmi drvdata, and the underlying ASUS ACPI/EC management method is a single physical platform resource. The mutex is non-recursive: nested ACPI/WMI notify handlers must not call back into evaluate on the same task (defer via workqueue, as asus_rfkill_notify already does). The sysfs-versus-notify race predates the later export of asus_wmi_evaluate_method(), so Fixes points at the original shared evaluate helper rather than that export. Link: https://github.com/OpenGamingCollective/asusctl/issues/328 Fixes: d33da3b68669 ("asus-wmi: factorise wmi_evaluate_method call") Cc: stable@vger.kernel.org Signed-off-by: Marco Scardovi Reviewed-by: Denis Benato Reviewed-by: Armin Wolf Link: https://patch.msgid.link/b6a1981777e63f0c28bd719d8203ebbda57d6957.1789722252.git.scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a2e552b207fb..60fbe13e100f 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -353,6 +355,20 @@ static void asus_wmi_show_deprecated(void) /* WMI ************************************************************************/ +/* + * Concurrent evaluations of ASUS WMI methods can re-enter firmware + * SMI/EC mailbox handling and corrupt the mailbox. + */ +static DEFINE_MUTEX(asus_wmi_eval_lock); + +static acpi_status asus_wmi_evaluate_method_locked(u32 method_id, + struct acpi_buffer *input, + struct acpi_buffer *output) +{ + guard(mutex)(&asus_wmi_eval_lock); + return wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, input, output); +} + static int asus_wmi_evaluate_method3(u32 method_id, u32 arg0, u32 arg1, u32 arg2, u32 *retval) { @@ -367,8 +383,7 @@ static int asus_wmi_evaluate_method3(u32 method_id, union acpi_object *obj; u32 tmp = 0; - status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, - &input, &output); + status = asus_wmi_evaluate_method_locked(method_id, &input, &output); pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x\n", __func__, method_id, arg0, arg1, arg2); @@ -419,8 +434,7 @@ static int asus_wmi_evaluate_method5(u32 method_id, union acpi_object *obj; u32 tmp = 0; - status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, - &input, &output); + status = asus_wmi_evaluate_method_locked(method_id, &input, &output); pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", __func__, method_id, arg0, arg1, arg2, arg3, arg4); @@ -467,8 +481,7 @@ static int asus_wmi_evaluate_method_buf(u32 method_id, union acpi_object *obj; int err = 0; - status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, - &input, &output); + status = asus_wmi_evaluate_method_locked(method_id, &input, &output); pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x\n", __func__, method_id, arg0, arg1); @@ -5092,9 +5105,8 @@ static int show_call(struct seq_file *m, void *data) union acpi_object *obj; acpi_status status; - status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, - 0, asus->debug.method_id, - &input, &output); + status = asus_wmi_evaluate_method_locked(asus->debug.method_id, + &input, &output); if (ACPI_FAILURE(status)) return -EIO; -- cgit v1.2.3 From 03bc5385e58b0dd25bfa6a9fd80caaec86e135d5 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Fri, 18 Sep 2026 11:15:00 +0200 Subject: platform/x86: asus-wmi: Remove redundant per-device wmi_lock and duplicate rfkill ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With all WMI method evaluations serialized globally by asus_wmi_eval_lock in asus_wmi_evaluate_method_locked(), the per-device wmi_lock in struct asus_wmi is completely redundant. Remove wmi_lock from struct asus_wmi, its initialization in asus_wmi_rfkill_init(), and its manual locking in asus_rfkill_hotplug(). Consequently, asus_rfkill_wlan_set() becomes a simple pass-through to asus_rfkill_set(), rendering asus_rfkill_wlan_ops identical to asus_rfkill_ops. Drop asus_rfkill_wlan_set() and asus_rfkill_wlan_ops, allocating WLAN rfkill devices with &asus_rfkill_ops directly. Signed-off-by: Marco Scardovi Reviewed-by: Denis Benato Reviewed-by: Armin Wolf Link: https://patch.msgid.link/035f8cc544ebedc31f29bf1918ee54c623147717.1789722252.git.scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 60fbe13e100f..34c0c6226c48 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -332,7 +332,6 @@ struct asus_wmi { struct hotplug_slot hotplug_slot; struct mutex hotplug_lock; - struct mutex wmi_lock; struct workqueue_struct *hotplug_workqueue; struct work_struct hotplug_work; @@ -2280,9 +2279,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus) bool absent; u32 l; - mutex_lock(&asus->wmi_lock); blocked = asus_wlan_rfkill_blocked(asus); - mutex_unlock(&asus->wmi_lock); mutex_lock(&asus->hotplug_lock); pci_lock_rescan_remove(); @@ -2484,30 +2481,6 @@ static void asus_rfkill_query(struct rfkill *rfkill, void *data) rfkill_set_sw_state(priv->rfkill, !result); } -static int asus_rfkill_wlan_set(void *data, bool blocked) -{ - struct asus_rfkill *priv = data; - struct asus_wmi *asus = priv->asus; - int ret; - - /* - * This handler is enabled only if hotplug is enabled. - * In this case, the asus_wmi_set_devstate() will - * trigger a wmi notification and we need to wait - * this call to finish before being able to call - * any wmi method - */ - mutex_lock(&asus->wmi_lock); - ret = asus_rfkill_set(data, blocked); - mutex_unlock(&asus->wmi_lock); - return ret; -} - -static const struct rfkill_ops asus_rfkill_wlan_ops = { - .set_block = asus_rfkill_wlan_set, - .query = asus_rfkill_query, -}; - static const struct rfkill_ops asus_rfkill_ops = { .set_block = asus_rfkill_set, .query = asus_rfkill_query, @@ -2526,13 +2499,8 @@ static int asus_new_rfkill(struct asus_wmi *asus, arfkill->dev_id = dev_id; arfkill->asus = asus; - if (dev_id == ASUS_WMI_DEVID_WLAN && - asus->driver->quirks->hotplug_wireless) - *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, - &asus_rfkill_wlan_ops, arfkill); - else - *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, - &asus_rfkill_ops, arfkill); + *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, + &asus_rfkill_ops, arfkill); if (!*rfkill) return -EINVAL; @@ -2606,7 +2574,6 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus) int result = 0; mutex_init(&asus->hotplug_lock); - mutex_init(&asus->wmi_lock); result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan", RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN); -- cgit v1.2.3 From 5013243b803410ea75143bf2750747c9de4100ac Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Sep 2026 12:02:15 -0500 Subject: platform/x86/amd: pmc: Only create amd_pmc_idlemask if scratch defined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the scratch register isn't defined for a platform, the debugfs file will always fail. Only create it when declared. Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260918170302.352054-2-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index e384b56d6755..666ecdd8888b 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -517,8 +517,9 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) &smu_fw_info_fops); debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev, &s0ix_stats_fops); - debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev, - &amd_pmc_idlemask_fops); + if (dev->cpu_info->scratch_reg) + debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev, + &amd_pmc_idlemask_fops); } static char *amd_pmc_get_msg_port(struct amd_pmc_dev *dev) -- cgit v1.2.3 From c84f2e1518d052f8699031330f1cc90cbf68740a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Sep 2026 12:02:16 -0500 Subject: platform/x86/amd: pmc: Drop scratch_reg from amd_1ah_m80_cpu_info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This platform does no longer populate this register. Fixes: 2726b5758f80a ("platform/x86: amd-pmc: Use correct IP block table for AMD 1Ah M80H SoC") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260918170302.352054-3-mario.limonciello@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 666ecdd8888b..2bb61a65e1cb 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -182,7 +182,6 @@ static const struct amd_pmc_cpu_info amd_1ah_m80_cpu_info = { .smu_arg = AMD_PMC_REGISTER_ARG_1AH_80H, .smu_rsp = AMD_PMC_REGISTER_RSP_1AH_80H, .num_ips = ARRAY_SIZE(soc15_ip_blk_v3), - .scratch_reg = AMD_PMC_SCRATCH_REG_1AH, .ips_ptr = soc15_ip_blk_v3, .os_hint = MSG_OS_HINT_RN, }; -- cgit v1.2.3 From d7fc7bb66283476ceab2c8e39b03f66842f7d230 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 19 Sep 2026 07:16:17 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for G614FM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS G614FM laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/4d561b599712eeb483c2d1f7c042827c6f8c8719.1789794282.git.scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index d605af2fdaa1..7134f80c0c42 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -2029,6 +2029,38 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614FM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 140, + .ppt_pl2_sppt_max = 165, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 140, + .ppt_pl3_fppt_max = 165, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "G614FP"), -- cgit v1.2.3 From 2ac40383e9b70bae67ff4cdbb2108c006bf1b5e5 Mon Sep 17 00:00:00 2001 From: Xi Pardee Date: Fri, 18 Sep 2026 22:18:28 -0700 Subject: platform/x86/intel/pmc: Fix stale NVL PCD-S S0ix_LPM telemetry constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nova Lake PCD-S S0ix_LPM telemetry layout changed. The PCD-S telemetry GUID was changed to reflect the layout change, per the client GUID revision rule. nvl_pcds_d3_status_1_map[] was correctly updated, but two companion constants were never updated to match: 1. NVL_PCDS_NUM_S0IX_BLOCKER (core.h) is 71 instead of the actual number of blk == 1 entries across nvl_pcds_blk_maps. 2. PCDS_LPM_REQ_GUID (nvl.c) is still 0x01091102. It was never bumped to 0x01091103 to reflect the S0ix_LPM telemetry metadata layout change. Both constants describe the same S0ix_LPM/blocker telemetry region for PCD-S, so fix them together in one change. Fixes: 41354f4c8a79 ("platform/x86/intel/pmc: Add Nova Lake support to intel_pmc_core driver") Signed-off-by: Xi Pardee Link: https://patch.msgid.link/20260919051830.565240-1-xi.pardee@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmc/core.h | 2 +- drivers/platform/x86/intel/pmc/nvl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index b4c7399f8369..e55164e5807f 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -323,7 +323,7 @@ enum ppfear_regs { #define NVL_LPM_LIVE_STATUS_OFFSET 0x1760 #define NVL_LPM_NUM_MAPS 15 #define NVL_PCDH_NUM_S0IX_BLOCKER 107 -#define NVL_PCDS_NUM_S0IX_BLOCKER 71 +#define NVL_PCDS_NUM_S0IX_BLOCKER 70 #define NVL_PCHS_NUM_S0IX_BLOCKER 54 #define NVL_PCDS_PMC_LTR_RESERVED 0x1bac #define NVL_PCDH_BLK_REQ_OFFSET 53 diff --git a/drivers/platform/x86/intel/pmc/nvl.c b/drivers/platform/x86/intel/pmc/nvl.c index 8dabf2511dd9..1ed02332dbfc 100644 --- a/drivers/platform/x86/intel/pmc/nvl.c +++ b/drivers/platform/x86/intel/pmc/nvl.c @@ -14,7 +14,7 @@ /* PMC SSRAM PMT Telemetry GUIDS */ #define PCDH_LPM_REQ_GUID 0x01093101 #define PCHS_LPM_REQ_GUID 0x01092101 -#define PCDS_LPM_REQ_GUID 0x01091102 +#define PCDS_LPM_REQ_GUID 0x01091103 /* * Die Mapping to Product. -- cgit v1.2.3 From 980b75b61778b84da13ccd201eae2f045fce9e94 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sat, 19 Sep 2026 21:08:28 +0800 Subject: platform/x86: yogabook: use assign_bit()/change_bit() where applicable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert open-coded if/else with set_bit/clear_bit to the assign_bit and change_bit APIs. Signed-off-by: Peng Fan Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260919130829.3041568-1-peng.fan@oss.nxp.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/yogabook.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c index 1a4b2ab1f35d..f4c99508c6e7 100644 --- a/drivers/platform/x86/lenovo/yogabook.c +++ b/drivers/platform/x86/lenovo/yogabook.c @@ -120,10 +120,7 @@ static void yogabook_toggle_digitizer_mode(struct yogabook_data *data) if (test_bit(YB_SUSPENDED, &data->flags)) return; - if (test_bit(YB_DIGITIZER_MODE, &data->flags)) - clear_bit(YB_DIGITIZER_MODE, &data->flags); - else - set_bit(YB_DIGITIZER_MODE, &data->flags); + change_bit(YB_DIGITIZER_MODE, &data->flags); /* * We are called from the ACPI core and the driver [un]binding which is @@ -136,10 +133,8 @@ static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data) { struct yogabook_data *data = _data; - if (gpiod_get_value(data->backside_hall_gpio)) - set_bit(YB_TABLET_MODE, &data->flags); - else - clear_bit(YB_TABLET_MODE, &data->flags); + assign_bit(YB_TABLET_MODE, &data->flags, + gpiod_get_value(data->backside_hall_gpio)); schedule_work(&data->work); -- cgit v1.2.3 From 9319f8ff83941d63658d230b3f02138764fceadc Mon Sep 17 00:00:00 2001 From: Justin Vargas Date: Sun, 20 Sep 2026 01:39:18 -0400 Subject: platform/x86: acer-wmi: Add support for Acer Nitro ANV15-51 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Acer Nitro ANV15-51 uses the Predator V4 WMI interface for thermal profile handling, hardware monitoring and fan control, but has no quirk entry, so none of these features are available. Add a quirk entry for this model. Note that this machine does not support the turbo thermal profile: writing PLATFORM_PROFILE_PERFORMANCE is rejected by the firmware with -EIO. The four remaining profiles work as expected. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Vargas Tested-by: Justin Vargas Link: https://patch.msgid.link/20260920053918.1888-1-justinyvar@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index d3d741aad556..51b2ae9e57a5 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -474,6 +474,14 @@ static struct quirk_entry quirk_acer_predator_ph315_53 = { .gpu_fans = 1, }; +static struct quirk_entry quirk_acer_nitro_anv15_51 = { + .cpu_fans = 1, + .gpu_fans = 1, + .hwmon = true, + .platform_profile = true, + .pwm = true, +}; + static struct quirk_entry quirk_acer_predator_ph16_72 = { .turbo = true, .cpu_fans = 1, @@ -682,6 +690,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, .driver_data = &quirk_acer_nitro_an515_58, }, + { + .callback = dmi_matched, + .ident = "Acer Nitro ANV15-51", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"), + }, + .driver_data = &quirk_acer_nitro_anv15_51, + }, { .callback = dmi_matched, .ident = "Acer Predator PH315-53", -- cgit v1.2.3 From 7feafba2f8ab545ae20492b71dac6c3fc781d497 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 20 Sep 2026 12:35:18 +0200 Subject: platform/x86: think-lmi: Use sysfs_emit() in pending_reboot_show() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace sprintf() with sysfs_emit() in pending_reboot_show(). sysfs_emit() is preferred for formatting sysfs output because it provides safer bounds checking. Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260920103518.281843-2-blum@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index bd8973c1369d..9b6ef7c0c781 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -1281,7 +1281,7 @@ static const struct kobj_type tlmi_pwd_setting_ktype = { static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", tlmi_priv.pending_changes); + return sysfs_emit(buf, "%d\n", tlmi_priv.pending_changes); } static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); -- cgit v1.2.3 From 9af0245d04916b5973a68cbd24a61cdcd70a1db7 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 20 Sep 2026 12:30:36 +0200 Subject: platform/x86: think-lmi: Use scnprintf() in new_password_store() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the current code works correctly, replace unbounded sprintf() calls with the safer scnprintf() in new_password_store() to follow secure coding best practices. Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260920103035.281573-2-blum@kernel.org [ij: snprintf() -> scnprintf()] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index 9b6ef7c0c781..54c01a8e7a82 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -487,16 +487,16 @@ static ssize_t new_password_store(struct kobject *kobj, /* Special handling required for HDD and NVMe passwords */ if (setting == tlmi_priv.pwd_hdd) { if (setting->level == TLMI_LEVEL_USER) - sprintf(pwd_type, "uhdp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "uhdp%d", setting->index); else - sprintf(pwd_type, "mhdp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "mhdp%d", setting->index); } else if (setting == tlmi_priv.pwd_nvme) { if (setting->level == TLMI_LEVEL_USER) - sprintf(pwd_type, "udrp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "udrp%d", setting->index); else - sprintf(pwd_type, "adrp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "adrp%d", setting->index); } else { - sprintf(pwd_type, "%s", setting->pwd_type); + scnprintf(pwd_type, sizeof(pwd_type), "%s", setting->pwd_type); } ret = tlmi_opcode_setting("WmiOpcodePasswordType", pwd_type); -- cgit v1.2.3 From 9f7e4762f8200691e50c3fb5f74d06e834bf11e0 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Sun, 20 Sep 2026 20:57:59 +0200 Subject: platform/x86: ayaneo-ec: Add AYANEO 2S charge control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AYANEO 2S uses the same EC fan interface as the AYANEO 2 and additionally supports charge limiting. Add an exact match ahead of the broader AYANEO 2 match so the charge-control quirk is selected. Reported-by: Tiago Silva Signed-off-by: Antheas Kapenekakis Link: https://patch.msgid.link/20260920185759.3426627-1-lkml@antheas.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/ayaneo-ec.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c index 41a24e091248..81dac81262e7 100644 --- a/drivers/platform/x86/ayaneo-ec.c +++ b/drivers/platform/x86/ayaneo-ec.c @@ -76,6 +76,13 @@ static const struct ayaneo_ec_quirk quirk_ayaneo3 = { }; static const struct dmi_system_id dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2S"), + }, + .driver_data = (void *)&quirk_charge_limit, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), -- cgit v1.2.3 From 0ccdf9a1381c7fb6d9e84abeb8e5d6c85dd07059 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Sun, 20 Sep 2026 21:04:37 +0200 Subject: platform/x86: oxpec: Fix Apex charge limit control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OneXPlayer Apex uses E5/E6 for battery bypass / charge limit, making it the first device to deviate. This was verified by a user on Windows with BIOS version 2.17 and later by running this kernel patch. Drop X1_ from the MASK variables as they are common in all devices, introduce a board enum for Apex, and make it use the new E5/E6 registers for charge limit / bypass. Apply the same quirks as oxp_fly, since APEX uses the same registers otherwise. Fixes: 3385ea97c14d ("platform/x86: oxpec: Add support for OneXPlayer APEX") Cc: stable@vger.kernel.org Signed-off-by: Antheas Kapenekakis Reviewed-by: Derek J. Clark Link: https://patch.msgid.link/20260920190438.3444923-1-lkml@antheas.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/oxpec.c | 68 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 34bb17fca148..38c272995437 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -50,6 +50,7 @@ enum oxp_board { oxp_x1, oxp_g1_i, oxp_g1_a, + oxp_apex, }; static enum oxp_board board; @@ -93,11 +94,14 @@ static struct device *oxp_dev; #define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */ #define OXP_X1_CHARGE_INHIBIT_REG 0xA4 /* X1 bypass charging */ -#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01 +#define OXP_APEX_CHARGE_LIMIT_REG 0xE5 /* Apex charge limit (%) */ +#define OXP_APEX_CHARGE_INHIBIT_REG 0xE6 /* Apex bypass charging */ + +#define OXP_CHARGE_INHIBIT_MASK_AWAKE 0x01 /* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */ -#define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02 -#define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \ - OXP_X1_CHARGE_INHIBIT_MASK_OFF) +#define OXP_CHARGE_INHIBIT_MASK_OFF 0x02 +#define OXP_CHARGE_INHIBIT_MASK_ALWAYS (OXP_CHARGE_INHIBIT_MASK_AWAKE | \ + OXP_CHARGE_INHIBIT_MASK_OFF) static const struct dmi_system_id dmi_table[] = { { @@ -154,7 +158,7 @@ static const struct dmi_system_id dmi_table[] = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER APEX"), }, - .driver_data = (void *)oxp_fly, + .driver_data = (void *)oxp_apex, }, { .matches = { @@ -352,6 +356,7 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj, case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return attr->mode; default: break; @@ -381,6 +386,7 @@ static ssize_t tt_toggle_store(struct device *dev, case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -427,6 +433,7 @@ static ssize_t tt_toggle_show(struct device *dev, case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -524,6 +531,7 @@ static bool oxp_psy_ext_supported(void) case oxp_g1_i: case oxp_g1_a: case oxp_fly: + case oxp_apex: return true; default: break; @@ -537,12 +545,24 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + u8 limit_reg, inhibit_reg; long raw_val; int ret; + switch (board) { + case oxp_apex: + limit_reg = OXP_APEX_CHARGE_LIMIT_REG; + inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG; + break; + default: + limit_reg = OXP_X1_CHARGE_LIMIT_REG; + inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG; + break; + } + switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: - ret = read_from_ec(OXP_X1_CHARGE_LIMIT_REG, 1, &raw_val); + ret = read_from_ec(limit_reg, 1, &raw_val); if (ret) return ret; if (raw_val < 0 || raw_val > 100) @@ -550,14 +570,14 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy, val->intval = raw_val; return 0; case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: - ret = read_from_ec(OXP_X1_CHARGE_INHIBIT_REG, 1, &raw_val); + ret = read_from_ec(inhibit_reg, 1, &raw_val); if (ret) return ret; - if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) == - OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) + if ((raw_val & OXP_CHARGE_INHIBIT_MASK_ALWAYS) == + OXP_CHARGE_INHIBIT_MASK_ALWAYS) val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; - else if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) == - OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) + else if ((raw_val & OXP_CHARGE_INHIBIT_MASK_AWAKE) == + OXP_CHARGE_INHIBIT_MASK_AWAKE) val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE; else val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; @@ -573,29 +593,41 @@ static int oxp_psy_ext_set_prop(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { + u8 limit_reg, inhibit_reg; long raw_val; + switch (board) { + case oxp_apex: + limit_reg = OXP_APEX_CHARGE_LIMIT_REG; + inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG; + break; + default: + limit_reg = OXP_X1_CHARGE_LIMIT_REG; + inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG; + break; + } + switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: if (val->intval < 0 || val->intval > 100) return -EINVAL; - return write_to_ec(OXP_X1_CHARGE_LIMIT_REG, val->intval); + return write_to_ec(limit_reg, val->intval); case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: switch (val->intval) { case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: raw_val = 0; break; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE: - raw_val = OXP_X1_CHARGE_INHIBIT_MASK_AWAKE; + raw_val = OXP_CHARGE_INHIBIT_MASK_AWAKE; break; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: - raw_val = OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS; + raw_val = OXP_CHARGE_INHIBIT_MASK_ALWAYS; break; default: return -EINVAL; } - return write_to_ec(OXP_X1_CHARGE_INHIBIT_REG, raw_val); + return write_to_ec(inhibit_reg, raw_val); default: return -EINVAL; } @@ -656,6 +688,7 @@ static int oxp_pwm_enable(void) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); default: return -EINVAL; @@ -676,6 +709,7 @@ static int oxp_pwm_disable(void) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); default: return -EINVAL; @@ -696,6 +730,7 @@ static int oxp_pwm_read(long *val) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); default: return -EOPNOTSUPP; @@ -732,6 +767,7 @@ static int oxp_pwm_fan_speed(long *val) case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); default: return -EOPNOTSUPP; @@ -764,6 +800,7 @@ static int oxp_pwm_input_write(long val) case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_REG, val); default: return -EOPNOTSUPP; @@ -803,6 +840,7 @@ static int oxp_pwm_input_read(long *val) case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: default: ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); if (ret) -- cgit v1.2.3 From 493f34eacd779e6551e5405440008ea5488c22b0 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Sun, 20 Sep 2026 21:04:38 +0200 Subject: platform/x86: oxpec: Add OneXPlayer 3 quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OneXPlayer 3 is a new Intel device by OneXPlayer. It uses the same registers as the G1 Intel edition. So, add a quirk for it. Battery bypass, capacity limits, fan curves, fan tachometer, and turbo button override were confirmed to work. Signed-off-by: Antheas Kapenekakis Reviewed-by: Derek J. Clark Link: https://patch.msgid.link/20260920190438.3444923-2-lkml@antheas.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/oxpec.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 38c272995437..c4c4babe8e30 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -223,6 +223,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_g1_i, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 3"), + }, + .driver_data = (void *)oxp_g1_i, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), -- cgit v1.2.3 From c9efe2acd24daae0f0933052b1d693766c26eb22 Mon Sep 17 00:00:00 2001 From: Justin Vargas Date: Sun, 20 Sep 2026 22:17:18 -0400 Subject: platform/x86: acer-wmi: Remove unused platform_profile_support variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The platform_profile_support variable is assigned during platform profile setup but is never read anywhere in the driver. Remove it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Vargas Link: https://patch.msgid.link/20260921021720.5976-2-justinyvar@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 51b2ae9e57a5..9e3758c570f3 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -876,7 +876,6 @@ static const struct dmi_system_id non_acer_quirks[] __initconst = { }; static struct device *platform_profile_device; -static bool platform_profile_support; /* * The profile used before turbo mode. This variable is needed for @@ -2236,8 +2235,6 @@ static int acer_platform_profile_setup(struct platform_device *device) &device->dev, "acer-wmi", NULL, &acer_predator_v4_platform_profile_ops); if (IS_ERR(platform_profile_device)) return PTR_ERR(platform_profile_device); - - platform_profile_support = true; } return 0; } -- cgit v1.2.3 From 35a43f0ee0660e64dea62dfbe9a59ac6a8ae2c4a Mon Sep 17 00:00:00 2001 From: Justin Vargas Date: Sun, 20 Sep 2026 22:17:19 -0400 Subject: platform/x86: asus-wmi: Remove unused platform_profile_support member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The platform_profile_support member of struct asus_wmi is assigned during platform profile setup but is never read anywhere in the driver. Remove it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Vargas Link: https://patch.msgid.link/20260921021720.5976-3-justinyvar@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 34c0c6226c48..fb1d4a085089 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -322,7 +322,6 @@ struct asus_wmi { struct fan_curve_data custom_fan_curves[3]; struct device *ppdev; - bool platform_profile_support; // The RSOC controls the maximum charging percentage. bool battery_rsoc_available; @@ -4344,7 +4343,6 @@ static int platform_profile_setup(struct asus_wmi *asus) return PTR_ERR(asus->ppdev); } - asus->platform_profile_support = true; return 0; } -- cgit v1.2.3 From 179084f181147b705148cfed56577fe6904a7da8 Mon Sep 17 00:00:00 2001 From: Justin Vargas Date: Sun, 20 Sep 2026 22:20:01 -0400 Subject: platform/x86: hp-wmi: Remove unused platform_profile_support variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The platform_profile_support variable is assigned during platform profile setup but is never read anywhere in the driver. Remove it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Vargas Link: https://patch.msgid.link/20260921022001.6020-1-justinyvar@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index eeb66a7db346..165839330500 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -553,7 +553,6 @@ static struct platform_device *hp_wmi_platform_dev; static struct device *platform_profile_device; static struct notifier_block platform_power_source_nb; static enum platform_profile_option active_platform_profile; -static bool platform_profile_support; static bool zero_insize_support; static struct rfkill *wifi_rfkill; @@ -2529,7 +2528,6 @@ static int thermal_profile_setup(struct platform_device *device) return PTR_ERR(platform_profile_device); pr_info("Registered as platform profile handler\n"); - platform_profile_support = true; return 0; } -- cgit v1.2.3 From d2f62a2487c8dd8671f9c28686f4d2e279bed1c0 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 22 Sep 2026 21:38:04 +0200 Subject: platform/x86: dell-wmi-aio: Use named defines for event types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There exist two types of events recognized by this driver: single hotkey events (0x0000) and sequences of hotkey events (0x000F). Until now both literals are used without a named define, making it harder to understand their meaning. Fix this by using named defines for both event types. Suggested-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260922193809.4801-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-aio.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c index 8849ad93480b..42c0032991d2 100644 --- a/drivers/platform/x86/dell/dell-wmi-aio.c +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -32,10 +32,13 @@ struct dell_wmi_aio_data { struct mutex input_lock; }; +/* A hot key was pressed or an event occurred */ +#define DELL_AIO_EVENT_TYPE_SINGLE 0x0000 +/* A sequence of hot keys was pressed */ +#define DELL_AIO_EVENT_TYPE_SEQUENCE 0x000F + struct dell_wmi_event { __le16 length; - /* 0x000: A hot key pressed or an event occurred - * 0x00F: A sequence of hot keys are pressed */ __le16 type; __le16 event[]; } __packed; @@ -68,10 +71,14 @@ static bool dell_wmi_aio_event_check(const struct wmi_buffer *buffer) event = buffer->data; length = le16_to_cpu(event->length); type = le16_to_cpu(event->type); - if ((type == 0 || type == 0xf) && length >= 2) - return true; - return false; + if (type != DELL_AIO_EVENT_TYPE_SINGLE && type != DELL_AIO_EVENT_TYPE_SEQUENCE) + return false; + + if (length < 2) + return false; + + return true; } static void dell_wmi_aio_notify(struct wmi_device *wdev, const struct wmi_buffer *data) -- cgit v1.2.3 From 3cdbe972bbbb8958602c7a7086264b5610dd4545 Mon Sep 17 00:00:00 2001 From: Arav Verma Date: Tue, 22 Sep 2026 18:59:53 -0500 Subject: platform/x86: intel: wmi: Use sysfs_emit in sbl-fw-update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modernize firmware_update_request_show by replacing legacy sprintf() call with sysfs_emit. When altering string, corrected format specified from %d to %u to match u32 type of target variable. Signed-off-by: Arav Verma Link: https://patch.msgid.link/20260923000002.1799245-1-svav2021@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/wmi/sbl-fw-update.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c index 62c9c7f1842b..cfef71f4f328 100644 --- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c +++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c @@ -61,7 +61,7 @@ static ssize_t firmware_update_request_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", val); + return sysfs_emit(buf, "%u\n", val); } static ssize_t firmware_update_request_store(struct device *dev, -- cgit v1.2.3 From 6523134ed06a7d5f626fdc1b084a61f69b22fdd8 Mon Sep 17 00:00:00 2001 From: Arav Verma Date: Tue, 22 Sep 2026 18:59:54 -0500 Subject: platform/x86: intel: wmi: Remove redundant callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed redundant .probe and .remove callbacks from wmi_driver struct, as they were print-only and unnecessary Signed-off-by: Arav Verma Link: https://patch.msgid.link/20260923000002.1799245-2-svav2021@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/wmi/sbl-fw-update.c | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c index cfef71f4f328..7e07cfab227c 100644 --- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c +++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c @@ -93,18 +93,6 @@ static struct attribute *firmware_update_attrs[] = { }; ATTRIBUTE_GROUPS(firmware_update); -static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, - const void *context) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); - return 0; -} - -static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); -} - static const struct wmi_device_id intel_wmi_sbl_id_table[] = { { .guid_string = INTEL_WMI_SBL_GUID }, {} @@ -116,8 +104,6 @@ static struct wmi_driver intel_wmi_sbl_fw_update_driver = { .name = "intel-wmi-sbl-fw-update", .dev_groups = firmware_update_groups, }, - .probe = intel_wmi_sbl_fw_update_probe, - .remove = intel_wmi_sbl_fw_update_remove, .id_table = intel_wmi_sbl_id_table, .no_singleton = true, }; -- cgit v1.2.3 From 73d69ee10f7ca1609262263c84c80938e9c608e4 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 29 Sep 2026 21:59:10 -0700 Subject: platform/x86: thinkpad_acpi: convert conditional mutex locks to ACQUIRE_ERR() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert conditional mutex_lock_killable() and mutex_lock_interruptible() calls to ACQUIRE() and ACQUIRE_ERR() from linux/cleanup.h. This eliminates explicit mutex_unlock() calls on return paths and simplifies error handling across hotkey, brightness, volume, fan, and dytc functions. Assisted-by: LLM Reviewed-by: Mark Pearson Tested-by: Ali Ahmet Memiş Tested-by: Mark Pearson Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260929-thinkpad-acpi-cleanup-v2-1-88e176953a4b@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 148 ++++++++++++++-------------- 1 file changed, 73 insertions(+), 75 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 33a424b63abd..7408d9dc6cef 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -2676,8 +2676,10 @@ static ssize_t hotkey_mask_store(struct device *dev, if (parse_strtoul(buf, 0xffffffffUL, &t)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; res = hotkey_user_mask_set(t); @@ -2685,8 +2687,6 @@ static ssize_t hotkey_mask_store(struct device *dev, hotkey_poll_setup(true); #endif - mutex_unlock(&hotkey_mutex); - tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t); return (res) ? res : count; @@ -2772,8 +2772,10 @@ static ssize_t hotkey_source_mask_store(struct device *dev, ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; HOTKEY_CONFIG_CRITICAL_START hotkey_source_mask = t; @@ -2787,8 +2789,6 @@ static ssize_t hotkey_source_mask_store(struct device *dev, r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask) & ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK; - mutex_unlock(&hotkey_mutex); - if (rc < 0) pr_err("hotkey_source_mask: failed to update the firmware event mask!\n"); @@ -2816,18 +2816,19 @@ static ssize_t hotkey_poll_freq_store(struct device *dev, const char *buf, size_t count) { unsigned long t; + int err; if (parse_strtoul(buf, 25, &t)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + err = ACQUIRE_ERR(mutex_kill, &guard); + if (err) + return err; hotkey_poll_set_freq(t); hotkey_poll_setup(true); - mutex_unlock(&hotkey_mutex); - tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t); return count; @@ -4066,12 +4067,13 @@ static int hotkey_read(struct seq_file *m) return 0; } - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; res = hotkey_status_get(&status); if (!res) res = hotkey_mask_get(); - mutex_unlock(&hotkey_mutex); if (res) return res; @@ -4104,8 +4106,10 @@ static int hotkey_write(char *buf) if (!tp_features.hotkey) return -ENODEV; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; mask = hotkey_user_mask; @@ -4124,8 +4128,7 @@ static int hotkey_write(char *buf) } else if (sscanf(cmd, "%x", &mask) == 1) { /* mask set */ } else { - res = -EINVAL; - goto errexit; + return -EINVAL; } } @@ -4135,8 +4138,6 @@ static int hotkey_write(char *buf) res = hotkey_user_mask_set(mask); } -errexit: - mutex_unlock(&hotkey_mutex); return res; } @@ -6540,11 +6541,12 @@ static void tpacpi_brightness_checkpoint_nvram(void) vdbg_printk(TPACPI_DBG_BRGHT, "trying to checkpoint backlight level to NVRAM...\n"); - if (mutex_lock_killable(&brightness_mutex) < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return; if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec))) - goto unlock; + return; lec &= TP_EC_BACKLIGHT_LVLMSK; b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS); @@ -6562,9 +6564,6 @@ static void tpacpi_brightness_checkpoint_nvram(void) vdbg_printk(TPACPI_DBG_BRGHT, "NVRAM backlight level already is %u (0x%02x)\n", (unsigned int) lec, (unsigned int) b_nvram); - -unlock: - mutex_unlock(&brightness_mutex); } @@ -6642,8 +6641,9 @@ static int brightness_set(unsigned int value) vdbg_printk(TPACPI_DBG_BRGHT, "set backlight level to %d\n", value); - res = mutex_lock_killable(&brightness_mutex); - if (res < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) return res; switch (brightness_mode) { @@ -6658,7 +6658,6 @@ static int brightness_set(unsigned int value) res = -ENXIO; } - mutex_unlock(&brightness_mutex); return res; } @@ -6681,14 +6680,11 @@ static int brightness_get(struct backlight_device *bd) { int status, res; - res = mutex_lock_killable(&brightness_mutex); - if (res < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return 0; res = tpacpi_brightness_get_raw(&status); - - mutex_unlock(&brightness_mutex); - if (res < 0) return 0; @@ -7153,11 +7149,12 @@ static void tpacpi_volume_checkpoint_nvram(void) else ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK; - if (mutex_lock_killable(&volume_mutex) < 0) + ACQUIRE(mutex_kill, guard)(&volume_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return; if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec))) - goto unlock; + return; lec &= ec_mask; b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER); @@ -7174,9 +7171,6 @@ static void tpacpi_volume_checkpoint_nvram(void) "NVRAM mixer status already is 0x%02x (0x%02x)\n", (unsigned int) lec, (unsigned int) b_nvram); } - -unlock: - mutex_unlock(&volume_mutex); } static int volume_get_status_ec(u8 *status) @@ -7225,12 +7219,14 @@ static int __volume_set_mute_ec(const bool mute) int rc; u8 s, n; - if (mutex_lock_killable(&volume_mutex) < 0) - return -EINTR; + ACQUIRE(mutex_kill, guard)(&volume_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = volume_get_status_ec(&s); if (rc) - goto unlock; + return rc; n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : s & ~TP_EC_AUDIO_MUTESW_MSK; @@ -7241,8 +7237,6 @@ static int __volume_set_mute_ec(const bool mute) rc = 1; } -unlock: - mutex_unlock(&volume_mutex); return rc; } @@ -7273,12 +7267,14 @@ static int __volume_set_volume_ec(const u8 vol) if (vol > TP_EC_VOLUME_MAX) return -EINVAL; - if (mutex_lock_killable(&volume_mutex) < 0) - return -EINTR; + ACQUIRE(mutex_kill, guard)(&volume_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = volume_get_status_ec(&s); if (rc) - goto unlock; + return rc; n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; @@ -7288,8 +7284,6 @@ static int __volume_set_volume_ec(const u8 vol) rc = 1; } -unlock: - mutex_unlock(&volume_mutex); return rc; } @@ -8193,13 +8187,14 @@ static int fan_get_status_safe(u8 *status) int rc; u8 s; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = fan_get_status(&s); /* NS EC doesn't have register with level settings */ if (!rc && !fan_with_ns_addr) fan_update_desired_level(s); - mutex_unlock(&fan_mutex); if (rc) return rc; @@ -8392,8 +8387,10 @@ static int fan_set_level_safe(int level) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; if (level == TPACPI_FAN_LAST_LEVEL) level = fan_control_desired_level; @@ -8402,7 +8399,6 @@ static int fan_set_level_safe(int level) if (!rc) fan_update_desired_level(level); - mutex_unlock(&fan_mutex); return rc; } @@ -8414,8 +8410,10 @@ static int fan_set_enable(void) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_FANS: @@ -8471,8 +8469,6 @@ static int fan_set_enable(void) rc = -ENXIO; } - mutex_unlock(&fan_mutex); - if (!rc) vdbg_printk(TPACPI_DBG_FAN, "fan control: set fan control register to 0x%02x\n", @@ -8487,8 +8483,10 @@ static int fan_set_disable(void) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = 0; switch (fan_control_access_mode) { @@ -8533,7 +8531,6 @@ static int fan_set_disable(void) vdbg_printk(TPACPI_DBG_FAN, "fan control: set fan control register to 0\n"); - mutex_unlock(&fan_mutex); return rc; } @@ -8544,8 +8541,10 @@ static int fan_set_speed(int speed) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = 0; switch (fan_control_access_mode) { @@ -8579,7 +8578,6 @@ static int fan_set_speed(int speed) rc = -ENXIO; } - mutex_unlock(&fan_mutex); return rc; } @@ -8739,8 +8737,10 @@ static ssize_t fan_pwm1_store(struct device *dev, /* scale down from 0-255 to 0-7 */ newlevel = (s >> 5) & 0x07; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = fan_get_status(&status); if (!rc && (status & @@ -8754,7 +8754,6 @@ static ssize_t fan_pwm1_store(struct device *dev, } } - mutex_unlock(&fan_mutex); return (rc) ? rc : count; } @@ -10605,13 +10604,14 @@ static int dytc_profile_set(struct device *dev, int output; int err; - err = mutex_lock_interruptible(&dytc_mutex); + ACQUIRE(mutex_intr, guard)(&dytc_mutex); + err = ACQUIRE_ERR(mutex_intr, &guard); if (err) return err; err = convert_profile_to_dytc(profile, &perfmode); if (err) - goto unlock; + return err; if (dytc_capabilities & BIT(DYTC_FC_MMC)) { if (profile == PLATFORM_PROFILE_BALANCED) { @@ -10623,18 +10623,18 @@ static int dytc_profile_set(struct device *dev, */ err = dytc_cql_command(DYTC_CMD_RESET, &output); if (err) - goto unlock; + return err; } else { /* Determine if we are in CQL mode. This alters the commands we do */ err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); if (err) - goto unlock; + return err; } } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) - goto unlock; + return err; /* system supports AMT, activate it when on balanced */ if (dytc_capabilities & BIT(DYTC_FC_AMT)) @@ -10642,8 +10642,6 @@ static int dytc_profile_set(struct device *dev, } /* Success - update current profile */ dytc_current_profile = profile; -unlock: - mutex_unlock(&dytc_mutex); return err; } -- cgit v1.2.3 From 4c73df98adedc00963b6a45101a112c9ff5c2e16 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 29 Sep 2026 21:59:11 -0700 Subject: platform/x86: thinkpad_acpi: use __free(kfree) for automatic cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use __free(kfree) for local pointer allocations in dispatch_proc_write(), tpacpi_evaluate_bcl(), and auxmac_init(). This ensures automatic memory cleanup when exiting function scope and removes explicit kfree() calls on exit paths. Assisted-by: LLM Reviewed-by: Mark Pearson Tested-by: Ali Ahmet Memiş Tested-by: Mark Pearson Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260929-thinkpad-acpi-cleanup-v2-2-88e176953a4b@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 42 ++++++++++------------------- 1 file changed, 14 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 7408d9dc6cef..a1bb92512d91 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -891,7 +891,6 @@ static ssize_t dispatch_proc_write(struct file *file, size_t count, loff_t *pos) { struct ibm_struct *ibm = pde_data(file_inode(file)); - char *kernbuf; int ret; if (!ibm || !ibm->write) @@ -899,16 +898,15 @@ static ssize_t dispatch_proc_write(struct file *file, if (count > PAGE_SIZE - 1) return -EINVAL; - kernbuf = memdup_user_nul(userbuf, count); + char *kernbuf __free(kfree) = memdup_user_nul(userbuf, count); if (IS_ERR(kernbuf)) return PTR_ERR(kernbuf); - ret = ibm->write(kernbuf); - if (ret == 0) - ret = count; - kfree(kernbuf); + ret = ibm->write(kernbuf); + if (ret) + return ret; - return ret; + return count; } static const struct proc_ops dispatch_proc_ops = { @@ -6707,26 +6705,21 @@ static const struct backlight_ops ibm_backlight_data = { static int __init tpacpi_evaluate_bcl(struct acpi_device *adev, void *not_used) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; - int rc; status = acpi_evaluate_object(adev->handle, "_BCL", NULL, &buffer); if (ACPI_FAILURE(status)) return 0; - obj = buffer.pointer; + union acpi_object *obj __free(kfree) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE) { acpi_handle_info(adev->handle, "Unknown _BCL data, please report this to %s\n", TPACPI_MAIL); - rc = 0; - } else { - rc = obj->package.count; + return 0; } - kfree(obj); - return rc; + return obj->package.count; } /* @@ -11071,24 +11064,23 @@ static int auxmac_init(struct ibm_init_struct *iibm) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; status = acpi_evaluate_object(NULL, "\\MACA", NULL, &buffer); - if (ACPI_FAILURE(status)) return -ENODEV; - obj = buffer.pointer; - - if (obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) { + union acpi_object *obj __free(kfree) = buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) { pr_info("Invalid buffer for MAC address pass-through.\n"); - goto auxmacinvalid; + strscpy(auxmac, "unavailable", sizeof(auxmac)); + return 0; } if (obj->string.pointer[AUXMAC_BEGIN_MARKER] != '#' || obj->string.pointer[AUXMAC_END_MARKER] != '#') { pr_info("Invalid header for MAC address pass-through.\n"); - goto auxmacinvalid; + strscpy(auxmac, "unavailable", sizeof(auxmac)); + return 0; } if (strncmp(obj->string.pointer + AUXMAC_START, "XXXXXXXXXXXX", AUXMAC_LEN) != 0) @@ -11096,13 +11088,7 @@ static int auxmac_init(struct ibm_init_struct *iibm) else strscpy(auxmac, "disabled", sizeof(auxmac)); -free: - kfree(obj); return 0; - -auxmacinvalid: - strscpy(auxmac, "unavailable", sizeof(auxmac)); - goto free; } static struct ibm_struct auxmac_data = { -- cgit v1.2.3 From c61adb22fef8a6ba16fa2c040ca45479877ff124 Mon Sep 17 00:00:00 2001 From: Patryk Pilichowski Date: Fri, 25 Sep 2026 14:56:28 +0200 Subject: platform/x86: msi-ec: Add MSI Katana 15 B13VFK EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the EC firmware string '1585EMS1.115' to ALLOWED_FW_10. This enables battery charge thresholds on the MSI Katana 15 B13VFK (MS-1585). The out-of-tree msi-ec driver groups this firmware with 1582EMS1.107 and 1583EMS1.109, which are already in ALLOWED_FW_10. An independent report for the same model and firmware confirms the same charge control encoding. Tested on MSI Katana 15 B13VFK with EC firmware 1585EMS1.115: writing charge_control_end_threshold updates EC register 0xd7 as expected (80 -> 0xd0, 100 -> 0xe4). With AC connected and the battery at 87%, the battery was charging with the threshold at 100; writing 80 stopped charging ("Not charging"). Charging resumes 10% below the end threshold, which matches charge_control_start_threshold. Keyboard backlight (0xd3) and cooler boost (0x98) also match this configuration. Link: https://github.com/BeardOverflow/msi-ec/issues/185 Signed-off-by: Patryk Pilichowski Link: https://patch.msgid.link/20260925125628.69826-1-pat.pilichowski@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 268b24c781c9..86d4880474ad 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -826,6 +826,7 @@ static struct msi_ec_conf CONF9 __initdata = { static const char * const ALLOWED_FW_10[] __initconst = { "1582EMS1.107", // GF66 11UC "1583EMS1.109", // Pulse GL66 12UEK + "1585EMS1.115", // Katana 15 B13VFK NULL }; -- cgit v1.2.3 From e7aa0a21a38afd3063fd9b4bf31025a882537d95 Mon Sep 17 00:00:00 2001 From: Kristen Carlson Accardi Date: Thu, 24 Sep 2026 12:36:24 -0700 Subject: platform/x86/intel: power-domains: Handle failed init in tpmi_get_linux_die_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_init() returns early with -ENODEV on CPU models that are not in tpmi_cpu_ids, before domain_die_map is allocated. When the driver is built in, tpmi_get_linux_die_id() remains callable after the failed init and dereferences the NULL domain_die_map. This is reachable from user space: uncore-frequency-tpmi calls tpmi_get_linux_die_id() from the show function of the world-readable die_id sysfs attribute. The attribute is created on multi-die packages for clusters with the core agent type, so reading it on such a CPU that is missing from tpmi_cpu_ids oopses. Likewise, if cpuhp_setup_state() fails, the error path frees domain_die_map but leaves the pointer set, so a later call would read freed memory. Return -ENODEV when the map was not allocated, and clear the pointer wherever it is freed. tpmi_get_linux_cpu_number(), tpmi_get_punit_core_number() and tpmi_get_power_domain_id() read only the static per-CPU data and hash table, so they are safe after a failed init. tpmi_get_power_domain_mask() has the same problem as this helper but no callers, so it is handled separately. Fixes: e37be5d85c60 ("platform/x86/intel: power-domains: Add interface to get Linux die ID") Assisted-by: LLM Signed-off-by: Kristen Carlson Accardi Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260924193625.1830526-2-kristen.c.accardi@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_power_domains.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_power_domains.c b/drivers/platform/x86/intel/tpmi_power_domains.c index 833052dc34d2..ea51fc56297d 100644 --- a/drivers/platform/x86/intel/tpmi_power_domains.c +++ b/drivers/platform/x86/intel/tpmi_power_domains.c @@ -156,6 +156,13 @@ EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN"); int tpmi_get_linux_die_id(int pkg_id, int domain_id) { + /* + * When built in, this stays callable after tpmi_init() failed + * before allocating the map. + */ + if (!domain_die_map) + return -ENODEV; + if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS) return -EINVAL; @@ -245,6 +252,7 @@ static int __init tpmi_init(void) free_domain_map: kfree(domain_die_map); + domain_die_map = NULL; free_domain_mask: kfree(tpmi_power_domain_mask); @@ -258,6 +266,7 @@ static void __exit tpmi_exit(void) cpuhp_remove_state(tpmi_hp_state); kfree(tpmi_power_domain_mask); kfree(domain_die_map); + domain_die_map = NULL; } module_exit(tpmi_exit) -- cgit v1.2.3 From 4a8ad9828aa4a4bd4324c5b7b2d818f71bf34a9e Mon Sep 17 00:00:00 2001 From: Kristen Carlson Accardi Date: Thu, 24 Sep 2026 12:36:25 -0700 Subject: platform/x86/intel: power-domains: Handle failed init in tpmi_get_power_domain_mask() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_init() returns early on CPU models that are not in tpmi_cpu_ids, before tpmi_power_domain_mask is allocated. When the driver is built in, the exported tpmi_get_power_domain_mask() remains callable after the failed init. It returns NULL then only because it indexes the NULL array at offset 0 for the zeroed per-CPU data. If cpuhp_setup_state() fails, the error path frees the array but leaves the pointer set, so the helper would return a pointer into freed memory. Nothing in the tree calls tpmi_get_power_domain_mask() today, so this is hardening rather than a fix for a reachable bug. Return NULL when the array was not allocated, and clear the pointer wherever it is freed, as the previous patch does for domain_die_map. Assisted-by: LLM Signed-off-by: Kristen Carlson Accardi Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260924193625.1830526-3-kristen.c.accardi@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_power_domains.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_power_domains.c b/drivers/platform/x86/intel/tpmi_power_domains.c index ea51fc56297d..d89b9894c42a 100644 --- a/drivers/platform/x86/intel/tpmi_power_domains.c +++ b/drivers/platform/x86/intel/tpmi_power_domains.c @@ -139,6 +139,13 @@ cpumask_t *tpmi_get_power_domain_mask(int cpu_no) cpumask_t *mask; int index; + /* + * When built in, this stays callable after tpmi_init() failed + * before allocating the array. + */ + if (!tpmi_power_domain_mask) + return NULL; + if (cpu_no >= num_possible_cpus()) return NULL; @@ -256,6 +263,7 @@ free_domain_map: free_domain_mask: kfree(tpmi_power_domain_mask); + tpmi_power_domain_mask = NULL; return ret; } @@ -265,6 +273,7 @@ static void __exit tpmi_exit(void) { cpuhp_remove_state(tpmi_hp_state); kfree(tpmi_power_domain_mask); + tpmi_power_domain_mask = NULL; kfree(domain_die_map); domain_die_map = NULL; } -- cgit v1.2.3 From f8f31d20ab3525040a8efcebac87c34e78fd7713 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Fri, 25 Sep 2026 14:42:25 +0000 Subject: platform/x86: asus-armoury: move has_valid_limit() above the attribute declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure code move in preparation for making the power tunable attribute groups gate their visibility on the platform limits: the visibility callbacks expanded by the group macros at the attribute declaration sites will call has_valid_limit(), which therefore needs to be declared before them. Suggested-by: Ilpo Järvinen Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260925144229.3693-2-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 76 ++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 2d5ca75bc727..067d9db93ce4 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -782,6 +782,44 @@ static inline struct rog_tunables *get_current_tunables(void) return asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]; } +/** + * has_valid_limit - Checks if a power-related attribute has a valid limit value + * @name: The name of the attribute to check + * @limits: Pointer to the power_limits structure containing limit values + * + * This function checks if a power-related attribute has a valid limit value. + * It returns false if limits is NULL or if the corresponding limit value is zero. + * + * Return: true if the attribute has a valid limit value, false otherwise + */ +static bool has_valid_limit(const char *name, const struct power_limits *limits) +{ + u32 limit_value = 0; + + if (!limits) + return false; + + if (!strcmp(name, ATTR_PPT_PL1_SPL)) + limit_value = limits->ppt_pl1_spl_max; + else if (!strcmp(name, ATTR_PPT_PL2_SPPT)) + limit_value = limits->ppt_pl2_sppt_max; + else if (!strcmp(name, ATTR_PPT_PL3_FPPT)) + limit_value = limits->ppt_pl3_fppt_max; + else if (!strcmp(name, ATTR_PPT_APU_SPPT)) + limit_value = limits->ppt_apu_sppt_max; + else if (!strcmp(name, ATTR_PPT_PLATFORM_SPPT)) + limit_value = limits->ppt_platform_sppt_max; + else if (!strcmp(name, ATTR_NV_DYNAMIC_BOOST)) + limit_value = limits->nv_dynamic_boost_max; + else if (!strcmp(name, ATTR_NV_TEMP_TARGET)) + limit_value = limits->nv_temp_target_max; + else if (!strcmp(name, ATTR_NV_BASE_TGP) || + !strcmp(name, ATTR_NV_TGP)) + limit_value = limits->nv_tgp_max; + + return limit_value > 0; +} + /* Simple attribute creation */ ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n", "Show the current mode of charging"); @@ -871,44 +909,6 @@ static bool is_power_tunable_attr(const char *name) return false; } -/** - * has_valid_limit - Checks if a power-related attribute has a valid limit value - * @name: The name of the attribute to check - * @limits: Pointer to the power_limits structure containing limit values - * - * This function checks if a power-related attribute has a valid limit value. - * It returns false if limits is NULL or if the corresponding limit value is zero. - * - * Return: true if the attribute has a valid limit value, false otherwise - */ -static bool has_valid_limit(const char *name, const struct power_limits *limits) -{ - u32 limit_value = 0; - - if (!limits) - return false; - - if (!strcmp(name, ATTR_PPT_PL1_SPL)) - limit_value = limits->ppt_pl1_spl_max; - else if (!strcmp(name, ATTR_PPT_PL2_SPPT)) - limit_value = limits->ppt_pl2_sppt_max; - else if (!strcmp(name, ATTR_PPT_PL3_FPPT)) - limit_value = limits->ppt_pl3_fppt_max; - else if (!strcmp(name, ATTR_PPT_APU_SPPT)) - limit_value = limits->ppt_apu_sppt_max; - else if (!strcmp(name, ATTR_PPT_PLATFORM_SPPT)) - limit_value = limits->ppt_platform_sppt_max; - else if (!strcmp(name, ATTR_NV_DYNAMIC_BOOST)) - limit_value = limits->nv_dynamic_boost_max; - else if (!strcmp(name, ATTR_NV_TEMP_TARGET)) - limit_value = limits->nv_temp_target_max; - else if (!strcmp(name, ATTR_NV_BASE_TGP) || - !strcmp(name, ATTR_NV_TGP)) - limit_value = limits->nv_tgp_max; - - return limit_value > 0; -} - static int asus_fw_attr_add(void) { const struct rog_tunables *const ac_rog_tunables = -- cgit v1.2.3 From adf0e74e7cf19c6d1bc1ea1386617bdf554b2435 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Fri, 25 Sep 2026 14:42:26 +0000 Subject: platform/x86: asus-armoury: let attribute groups decide their own visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mini_led_mode, gpu_mux_mode and dgpu_disable are created by ad-hoc conditional blocks in asus_fw_attr_add() which must be manually tracked by the error and exit paths; those paths also re-probe WMI to know which groups were actually created. Give every attribute group its own .is_visible() callback: all groups are created unconditionally through the common loop and sysfs hides the unsupported ones entirely. The plain group macros now gate their visibility on the WMI presence of their device, the ASUS_ATTR_GROUP_BOOL() and ASUS_ATTR_GROUP_ENUM() macros declare groups backed by a device ID resolved at probe time, and the power tunable macros are additionally gated on the platform limits defining a max value, replacing the special-casing in asus_fw_attr_add() and with it is_power_tunable_attr(). The dgpu_disable attribute now goes through the same resolved device ID scheme as mini_led_mode and gpu_mux_mode, storing the device ID to use in asus_armoury.dgpu_disable_dev_id instead of always operating on ASUS_WMI_DEVID_DGPU. No functional change is intended. Suggested-by: Ilpo Järvinen Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260925144229.3693-3-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 204 ++++++++++++++---------------------- drivers/platform/x86/asus-armoury.h | 124 +++++++++++++++++----- 2 files changed, 175 insertions(+), 153 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 067d9db93ce4..7812e93734b8 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -94,6 +94,7 @@ struct asus_armoury_priv { u32 mini_led_dev_id; u32 gpu_mux_dev_id; + u32 dgpu_disable_dev_id; bool requires_fan_curve; }; @@ -110,11 +111,6 @@ static struct fw_attrs_group fw_attrs = { .pending_reboot = false, }; -struct asus_attr_group { - const struct attribute_group *attr_group; - u32 wmi_devid; -}; - static void asus_set_reboot_and_signal_event(void) { fw_attrs.pending_reboot = true; @@ -458,6 +454,12 @@ static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, return -ENODEV; } } + +static bool mini_led_mode_group_visible(struct kobject *kobj) +{ + return asus_armoury.mini_led_dev_id; +} + ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode"); static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, @@ -471,8 +473,8 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, if (err) return err; - if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) { - err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU); + if (asus_armoury.dgpu_disable_dev_id) { + err = armoury_get_devstate(NULL, &result, asus_armoury.dgpu_disable_dev_id); if (err) return err; if (result && !optimus) { @@ -502,6 +504,12 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, return count; } ASUS_WMI_SHOW_INT(gpu_mux_mode_current_value, asus_armoury.gpu_mux_dev_id); + +static bool gpu_mux_mode_group_visible(struct kobject *kobj) +{ + return asus_armoury.gpu_mux_dev_id; +} + ASUS_ATTR_GROUP_BOOL(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode"); static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, @@ -538,7 +546,8 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, } scoped_guard(mutex, &asus_armoury.egpu_mutex) { - err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, ASUS_WMI_DEVID_DGPU); + err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, + asus_armoury.dgpu_disable_dev_id); if (err) return err; } @@ -547,7 +556,13 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, return count; } -ASUS_WMI_SHOW_INT(dgpu_disable_current_value, ASUS_WMI_DEVID_DGPU); + +static bool dgpu_disable_group_visible(struct kobject *kobj) +{ + return asus_armoury.dgpu_disable_dev_id; +} + +ASUS_WMI_SHOW_INT(dgpu_disable_current_value, asus_armoury.dgpu_disable_dev_id); ASUS_ATTR_GROUP_BOOL(dgpu_disable, "dgpu_disable", "Disable the dGPU"); /* Values map for eGPU activation requests. */ @@ -695,6 +710,12 @@ static ssize_t egpu_enable_possible_values_show(struct kobject *kobj, struct kob { return armoury_attr_enum_list(buf, ARRAY_SIZE(egpu_status_map)); } + +static bool egpu_enable_group_visible(struct kobject *kobj) +{ + return armoury_has_devstate(ASUS_WMI_DEVID_EGPU); +} + ASUS_ATTR_GROUP_ENUM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)"); /* Device memory available to APU */ @@ -771,6 +792,12 @@ static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_at { return armoury_attr_enum_list(buf, ARRAY_SIZE(apu_mem_map)); } + +static bool apu_mem_group_visible(struct kobject *kobj) +{ + return armoury_has_devstate(ASUS_WMI_DEVID_APU_MEM); +} + ASUS_ATTR_GROUP_ENUM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use"); /* Define helper to access the current power mode tunable values */ @@ -857,65 +884,35 @@ ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP, ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP, "Read the base TGP value"); -/* If an attribute does not require any special case handling add it here */ -static const struct asus_attr_group armoury_attr_groups[] = { - { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED }, - { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU }, - { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU }, - { &dgpu_power_state_attr_group, ASUS_WMI_DEVID_DGPU_POWER_STATE }, - { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM }, - - { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL }, - { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT }, - { &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_PL3_FPPT }, - { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT }, - { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT }, - { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST }, - { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET }, - { &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP }, - { &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP }, - - { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE }, - { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND }, - { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE }, - { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD }, - { &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD }, - { &screen_auto_brightness_attr_group, ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS }, +static const struct attribute_group *armoury_attr_groups[] = { + &mini_led_mode_attr_group, + &gpu_mux_mode_attr_group, + &egpu_connected_attr_group, + &egpu_enable_attr_group, + &dgpu_disable_attr_group, + &dgpu_power_state_attr_group, + &apu_mem_attr_group, + + &ppt_pl1_spl_attr_group, + &ppt_pl2_sppt_attr_group, + &ppt_pl3_fppt_attr_group, + &ppt_apu_sppt_attr_group, + &ppt_platform_sppt_attr_group, + &nv_dynamic_boost_attr_group, + &nv_temp_target_attr_group, + &nv_base_tgp_attr_group, + &nv_tgp_attr_group, + + &charge_mode_attr_group, + &boot_sound_attr_group, + &mcu_powersave_attr_group, + &panel_od_attr_group, + &panel_hd_mode_attr_group, + &screen_auto_brightness_attr_group, }; -/** - * is_power_tunable_attr - Determines if an attribute is a power-related tunable - * @name: The name of the attribute to check - * - * This function checks if the given attribute name is related to power tuning. - * - * Return: true if the attribute is a power-related tunable, false otherwise - */ -static bool is_power_tunable_attr(const char *name) -{ - static const char * const power_tunable_attrs[] = { - ATTR_PPT_PL1_SPL, ATTR_PPT_PL2_SPPT, - ATTR_PPT_PL3_FPPT, ATTR_PPT_APU_SPPT, - ATTR_PPT_PLATFORM_SPPT, ATTR_NV_DYNAMIC_BOOST, - ATTR_NV_TEMP_TARGET, ATTR_NV_BASE_TGP, - ATTR_NV_TGP - }; - - for (unsigned int i = 0; i < ARRAY_SIZE(power_tunable_attrs); i++) { - if (!strcmp(name, power_tunable_attrs[i])) - return true; - } - - return false; -} - static int asus_fw_attr_add(void) { - const struct rog_tunables *const ac_rog_tunables = - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]; - const struct power_limits *limits; - bool should_create; - const char *name; int err, i; asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), @@ -944,73 +941,32 @@ static int asus_fw_attr_add(void) else if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE2)) asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; - if (asus_armoury.mini_led_dev_id) { - err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, - &mini_led_mode_attr_group); - if (err) { - pr_err("Failed to create sysfs-group for mini_led\n"); - goto err_remove_file; - } - } - asus_armoury.gpu_mux_dev_id = 0; if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX)) asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX; else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX_VIVO)) asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO; - if (asus_armoury.gpu_mux_dev_id) { - err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, - &gpu_mux_mode_attr_group); - if (err) { - pr_err("Failed to create sysfs-group for gpu_mux\n"); - goto err_remove_mini_led_group; - } - } + asus_armoury.dgpu_disable_dev_id = 0; + if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) + asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_DGPU; for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { - if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) - continue; - - /* Always create by default, unless PPT is not present */ - should_create = true; - name = armoury_attr_groups[i].attr_group->name; - - /* Check if this is a power-related tunable requiring limits */ - if (ac_rog_tunables && ac_rog_tunables->power_limits && - is_power_tunable_attr(name)) { - limits = ac_rog_tunables->power_limits; - /* Check only AC: if not present then DC won't be either */ - should_create = has_valid_limit(name, limits); - if (!should_create) - pr_debug("Missing max value for tunable %s\n", name); - } - - if (should_create) { - err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i].attr_group); - if (err) { - pr_err("Failed to create sysfs-group for %s\n", - armoury_attr_groups[i].attr_group->name); - goto err_remove_groups; - } + err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i]); + if (err) { + pr_err("Failed to create sysfs-group for %s\n", + armoury_attr_groups[i]->name); + goto err_remove_groups; } } return 0; err_remove_groups: - while (i--) { - if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i].attr_group); - } - if (asus_armoury.gpu_mux_dev_id) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); -err_remove_mini_led_group: - if (asus_armoury.mini_led_dev_id) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); -err_remove_file: + while (i--) + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i]); sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); err_destroy_kset: kset_unregister(asus_armoury.fw_attr_kset); @@ -1182,17 +1138,9 @@ static void __exit asus_fw_exit(void) { int i; - for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) { - if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i].attr_group); - } - - if (asus_armoury.gpu_mux_dev_id) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); - - if (asus_armoury.mini_led_dev_id) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); + for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) + sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups[i]); sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); kset_unregister(asus_armoury.fw_attr_kset); diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 7134f80c0c42..5792210b75bd 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -100,6 +100,43 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr static struct kobj_attribute attr_##_attrname##_##_prop = \ __ASUS_ATTR_RO(_attrname, _prop) +/* + * Every attribute group decides its own visibility through .is_visible(): + * sysfs hides a named group entirely when its first attribute reports + * SYSFS_GROUP_INVISIBLE, so groups are always created and never leave + * empty directories behind. + */ +#define __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi) \ + static bool _attrname##_group_visible(struct kobject *kobj) \ + { \ + return armoury_has_devstate(_wmi); \ + } \ + DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) + +/* + * Power tunables are additionally gated on the platform limits actually + * defining a max value for them. Only the AC limits are checked: if not + * present then DC won't be either. + */ +#define __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi) \ + static bool _attrname##_group_visible(struct kobject *kobj) \ + { \ + const struct rog_tunables *tunables = \ + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]; \ + \ + if (!tunables || !tunables->power_limits) \ + return armoury_has_devstate(_wmi); \ + \ + if (!has_valid_limit(_fsname, tunables->power_limits)) {\ + pr_debug("Missing max value for tunable %s\n", \ + _fsname); \ + return false; \ + } \ + \ + return armoury_has_devstate(_wmi); \ + } \ + DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) + #define __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)\ ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \ static struct kobj_attribute attr_##_attrname##_current_value = \ @@ -108,6 +145,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ static struct kobj_attribute attr_##_attrname##_type = \ __ASUS_ATTR_RO_AS(type, enum_type_show); \ + __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \ static struct attribute *_attrname##_attrs[] = { \ &attr_##_attrname##_current_value.attr, \ &attr_##_attrname##_display_name.attr, \ @@ -116,7 +154,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr NULL \ }; \ static const struct attribute_group _attrname##_attr_group = { \ - .name = _fsname, .attrs = _attrname##_attrs \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ } #define __ATTR_RW_INT_GROUP_ENUM(_attrname, _minv, _maxv, _wmi, _fsname,\ @@ -129,6 +169,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \ static struct kobj_attribute attr_##_attrname##_type = \ __ASUS_ATTR_RO_AS(type, enum_type_show); \ + __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \ static struct attribute *_attrname##_attrs[] = { \ &attr_##_attrname##_current_value.attr, \ &attr_##_attrname##_display_name.attr, \ @@ -137,7 +178,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr NULL \ }; \ static const struct attribute_group _attrname##_attr_group = { \ - .name = _fsname, .attrs = _attrname##_attrs \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ } /* Boolean style enumeration, base macro. Requires adding show/store */ @@ -168,37 +211,63 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname) /* - * Requires _current_value_show(), _current_value_show() + * Boolean style group whose whole visibility is decided by + * _group_visible(), for attributes backed by a device ID resolved + * at probe time. + * Requires _current_value_show(), _current_value_store() + * and _group_visible() */ #define ASUS_ATTR_GROUP_BOOL(_attrname, _fsname, _dispname) \ + DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \ static struct kobj_attribute attr_##_attrname##_current_value = \ __ASUS_ATTR_RW(_attrname, current_value); \ - __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname) + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", "0;1"); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ + } /* - * Requires _current_value_show(), _current_value_show() - * and _possible_values_show() + * Group whose whole visibility is decided by _group_visible(), + * for attributes backed by a device ID resolved at probe time. + * Requires _current_value_show(), _current_value_store(), + * _possible_values_show() and _group_visible() */ -#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \ - __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ - static struct kobj_attribute attr_##_attrname##_current_value = \ - __ASUS_ATTR_RW(_attrname, current_value); \ - static struct kobj_attribute attr_##_attrname##_possible_values = \ - __ASUS_ATTR_RO(_attrname, possible_values); \ - static struct kobj_attribute attr_##_attrname##_type = \ - __ASUS_ATTR_RO_AS(type, enum_type_show); \ - static struct attribute *_attrname##_attrs[] = { \ - &attr_##_attrname##_current_value.attr, \ - &attr_##_attrname##_display_name.attr, \ - &attr_##_attrname##_possible_values.attr, \ - &attr_##_attrname##_type.attr, \ - NULL \ - }; \ - static const struct attribute_group _attrname##_attr_group = { \ - .name = _fsname, .attrs = _attrname##_attrs \ +#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \ + DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __ASUS_ATTR_RW(_attrname, current_value); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + static struct kobj_attribute attr_##_attrname##_possible_values =\ + __ASUS_ATTR_RO(_attrname, possible_values); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __ASUS_ATTR_RO_AS(type, enum_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_possible_values.attr, \ + &attr_##_attrname##_type.attr, \ + NULL \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ } #define ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \ + __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \ ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \ static struct kobj_attribute attr_##_attrname##_current_value = \ __ASUS_ATTR_RO(_attrname, current_value); \ @@ -211,7 +280,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr &attr_##_attrname##_type.attr, NULL \ }; \ static const struct attribute_group _attrname##_attr_group = { \ - .name = _fsname, .attrs = _attrname##_attrs \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ } /* @@ -284,6 +355,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr __ASUS_ATTR_RW(_attr, current_value) #define ASUS_ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \ + __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \ __ROG_TUNABLE_RW(_attrname, _wmi); \ __ROG_TUNABLE_SHOW_DEFAULT(_attrname); \ __ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \ @@ -303,7 +375,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr NULL \ }; \ static const struct attribute_group _attrname##_attr_group = { \ - .name = _fsname, .attrs = _attrname##_attrs \ + .name = _fsname, \ + .is_visible = SYSFS_GROUP_VISIBLE(_attrname), \ + .attrs = _attrname##_attrs \ } /* Default is always the maximum value unless *_def is specified */ -- cgit v1.2.3 From eda58298d53ae0bd28ead8dede4d2996d9d7efa7 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Fri, 25 Sep 2026 14:42:27 +0000 Subject: platform/x86: asus-armoury: add dGPU disable fallback DEVID for ProArt H7606 series MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer ASUS ProArt laptops (e.g. H7606 series) implement dGPU power control on WMI DEVID 0x00090120 instead of the usual 0x00090020. The default DEVID returns 0xFFFFFFE2 on these models, so the dgpu_disable firmware attribute is never created and the dGPU cannot be re-enabled from Linux at all. Add a fallback probe for device ID 0x00090120 following the same approach as the gpu_mux_dev_id probe. Details for 0x00090120 (confirmed on H7606W using direct WMNB calls and inspecting the DSDT DEVS handler): - Reading DSTS returns 0x00010001 when the dGPU is off (CUMA=1), or 0x00010000 when it's on. - Writing DEVS: 0 enables the dGPU (calls PG00._ON() + Notifies PEGP, Device Check), and 1 disables or ejects it. This behavior matches the dgpu_disable attribute (1 = disabled). Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260925144229.3693-4-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 7812e93734b8..7c54f616d701 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -950,6 +950,8 @@ static int asus_fw_attr_add(void) asus_armoury.dgpu_disable_dev_id = 0; if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_DGPU; + else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MODE)) + asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_GPU_MODE; for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, -- cgit v1.2.3 From 3036516c672447f5b6670e4c875b113b0b199868 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Fri, 25 Sep 2026 14:42:28 +0000 Subject: platform/x86: asus-armoury: use sysfs_create_groups() and sysfs_remove_groups() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NULL-terminate the attribute group list and let sysfs_create_groups() create it so that sysfs_remove_groups() can be used to rollback in case of errors and on module removal. Suggested-by: Ilpo Järvinen Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260925144229.3693-5-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 7c54f616d701..5d74a2322f4b 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -909,11 +909,12 @@ static const struct attribute_group *armoury_attr_groups[] = { &panel_od_attr_group, &panel_hd_mode_attr_group, &screen_auto_brightness_attr_group, + NULL }; static int asus_fw_attr_add(void) { - int err, i; + int err; asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), NULL, "%s", DRIVER_NAME); @@ -953,22 +954,16 @@ static int asus_fw_attr_add(void) else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MODE)) asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_GPU_MODE; - for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { - err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i]); - if (err) { - pr_err("Failed to create sysfs-group for %s\n", - armoury_attr_groups[i]->name); - goto err_remove_groups; - } + err = sysfs_create_groups(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups); + if (err) { + pr_err("Failed to create firmware attributes groups\n"); + goto err_remove_file; } return 0; -err_remove_groups: - while (i--) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i]); +err_remove_file: sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); err_destroy_kset: kset_unregister(asus_armoury.fw_attr_kset); @@ -1138,11 +1133,8 @@ err_free_tunables: static void __exit asus_fw_exit(void) { - int i; - - for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) - sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, - armoury_attr_groups[i]); + sysfs_remove_groups(&asus_armoury.fw_attr_kset->kobj, + armoury_attr_groups); sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); kset_unregister(asus_armoury.fw_attr_kset); -- cgit v1.2.3 From 955d32f51373828a6034a6d76eaef1857ff331d1 Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Sun, 27 Sep 2026 15:27:00 +0530 Subject: platform/x86: hp-wmi: Add Victus 15-fb3xxx support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP Victus 15-fb3xxx (board ID: 8DD0) uses the existing OMEN v1 WMI interface (despite being a Victus model) but does not use the standard EC thermal profile parameters. Add the DMI board name to hp_wmi_feature_boards[] and map it to omen_v1_no_ec_board_params. This enables the existing board-specific handling for 8DD0, including platform profile and fan control support. Link: https://github.com/yunusemreyl/omen-space/issues/206 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260927095701.185772-1-technosfan14@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 165839330500..64d0ea8de32e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -347,6 +347,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, .driver_data = (void *)&omen_v1_no_ec_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD0") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, .driver_data = (void *)&omen_v1_no_ec_thermal_params, -- cgit v1.2.3 From d35a1ea827a2efaea7fb059ffafefae7c13b9556 Mon Sep 17 00:00:00 2001 From: Ciro Pedrini Date: Fri, 25 Sep 2026 17:52:26 -0300 Subject: platform/x86: hp-wmi: Add OMEN board 8A17 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 17-ck1xxx (board 8A17) isn't in omen_thermal_profile_boards, so hp-wmi uses the generic thermal profile query. The firmware only stores that value and never acts on it, so the EC stays in its default mode and the CPU is held at 45 W whatever profile is selected. The board uses the OMEN interface (mode byte at EC 0x95, thermal policy v1) and also needs the timed profile quirk: without it, the EC drops back to the default mode about two minutes after each profile change. Add 8A17 to omen_thermal_profile_boards and omen_timed_thermal_profile_boards. With this, all three profiles work and sustained package power under load goes from 45 W to 56-79 W. Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Ciro Pedrini Link: https://patch.msgid.link/20260925205226.883724-1-ciro.pedrini@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 64d0ea8de32e..a2afa86f2fe4 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -207,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", - "8A15", "8A18", "8A42", "8A43", + "8A15", "8A17", "8A18", "8A42", "8A43", "8BAD", "8C58", "8E41", @@ -227,7 +227,7 @@ static const char * const omen_thermal_profile_force_v0_boards[] = { * "balanced" when reaching zero. */ static const char * const omen_timed_thermal_profile_boards[] = { - "8A15", "8A42", + "8A15", "8A17", "8A42", "8BAD", }; -- cgit v1.2.3 From 82a93740890b6a2c6bad5e69907c3882e8601493 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Sat, 26 Sep 2026 12:25:28 -0400 Subject: platform/x86: thinkpad_acpi: fix typos in comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix various spelling mistakes in comments found by codespell. Assisted-by: Claude:claude-opus-5-5 codespell Signed-off-by: Zain Aboobacker Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260926162528.25365-1-zainaboobacker33@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index a1bb92512d91..848551099436 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -2106,7 +2106,7 @@ static int hotkey_mask_set(u32 mask) } /* - * We *must* make an inconditional call to hotkey_mask_get to + * We *must* make an unconditional call to hotkey_mask_get to * refresh hotkey_acpi_mask and update hotkey_user_mask * * Take the opportunity to also log when we cannot _enable_ @@ -3769,7 +3769,7 @@ static bool hotkey_notify_dockevent(const u32 hkey, bool *send_acpi_ev) return true; /* - * Deliberately ignore attaching and detaching the keybord cover to avoid + * Deliberately ignore attaching and detaching the keyboard cover to avoid * duplicates from intel-vbtn, which already emits SW_TABLET_MODE events * to userspace. * @@ -4046,7 +4046,7 @@ static void hotkey_resume(void) hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); - /* restore previous mode of adapive keyboard of X1 Carbon */ + /* restore previous mode of adaptive keyboard of X1 Carbon */ if (tp_features.has_adaptive_kbd) { if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", adaptive_keyboard_prev_mode)) { @@ -7116,7 +7116,7 @@ static bool software_mute_active; static int software_mute_orig_mode; /* - * Used to syncronize writers to TP_EC_AUDIO and + * Used to synchronize writers to TP_EC_AUDIO and * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write */ static struct mutex volume_mutex; @@ -7307,7 +7307,7 @@ static int volume_set_software_mute(bool startup) /* * In software mute mode, the standard codec controls take - * precendence, so we unmute the ThinkPad HW switch at + * precedence, so we unmute the ThinkPad HW switch at * startup. Just on case there are SAUM-capable ThinkPads * with level controls, set max HW volume as well. */ @@ -8026,7 +8026,7 @@ TPACPI_HANDLE(fanw, ec, "FANW", /* E531 */ ); /* all others */ /* - * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the + * Uninitialized HFSP quirk: ACPI DSDT and EC fail to initialize the * HFSP register at boot, so it contains 0x07 but the Thinkpad could * be in auto mode (0x80). * @@ -10612,7 +10612,7 @@ static int dytc_profile_set(struct device *dev, * To get back to balanced mode we need to issue a reset command. * Note we still need to disable CQL mode before hand and re-enable * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays - * stuck at 0 for aprox. 30 minutes. + * stuck at 0 for approx. 30 minutes. */ err = dytc_cql_command(DYTC_CMD_RESET, &output); if (err) @@ -11665,7 +11665,7 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) static struct proc_dir_entry *proc_dir; /* - * Module and infrastructure proble, init and exit handling + * Module and infrastructure probe, init and exit handling */ static bool force_load; -- cgit v1.2.3 From df1e0e62030849c5929d0e0cc13f21864fbeefd4 Mon Sep 17 00:00:00 2001 From: Nikolay Plastinin Date: Thu, 24 Sep 2026 01:48:02 +0800 Subject: platform/x86: asus-nb-wmi: add tablet mode quirk for ROG Flow X13 GV302X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2023 ROG Flow X13 (GV302X*) is a 360-degree convertible like the GV301, but the existing "GV301" DMI entry does not match it, so the driver does not register SW_TABLET_MODE and the desktop does not switch to tablet mode when the screen is folded back. With asus_nb_wmi.tablet_mode_sw=3 (lid-flip-rog) the "Asus WMI hotkeys" input device reports SW_TABLET_MODE on every fold and unfold, so apply quirk_asus_tablet_mode to GV302X* too. Apart from the tablet switch this quirk is the same as quirk_asus_unknown, which these models get today. DMI product name of the tested machine: "ROG Flow X13 GV302XV_GV302XV". Tested on GV302XV. Signed-off-by: Nikolay Plastinin Link: https://patch.msgid.link/20260924-gv302-tablet-v1-1-a45dd3a383f1@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-nb-wmi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index da1ddd6016c6..3b0f3346dc60 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -503,6 +503,16 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_tablet_mode, }, + { + .callback = dmi_matched, + .ident = "ASUS ROG FLOW X13", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + /* Match GV302X* */ + DMI_MATCH(DMI_PRODUCT_NAME, "GV302X"), + }, + .driver_data = &quirk_asus_tablet_mode, + }, { .callback = dmi_matched, .ident = "ASUS ROG FLOW X16", -- cgit v1.2.3 From ec474dc09ca129c8f312921ae28810c067d69f55 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 1 Sep 2026 23:02:11 +0800 Subject: platform/x86/amd/pmf: Inline !AMD_PMF_DEBUG stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A !CONFIG_AMD_PMF_DEBUG build compiles amd_pmf_dump_ta_inputs() as a stub. However, the stub is implemented as a global function and becomes a symbol against linkage. This bloats the size of the module. As a best practice, convert the stub into a static inline function to get rid of the overhead of function outlining and linkage, and shrink the module (!CONFIG_AMD_PMF_DEBUG && CONFIG_AMD_PMF_UTIL_SUPPORT) size by 65 Bytes (GCC 16 -O2): text data bss total filename (before) 26810 31672 2768 61250 amd-pmf.ko text data bss total filename (after) 26781 31636 2768 61185 amd-pmf.ko Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260901-amd-pmf-fix-outlining-v1-1-b54ff94d3d45@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 7 ++++++- drivers/platform/x86/amd/pmf/spc.c | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 07ec00684233..4da2ef1abb50 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -1137,10 +1137,15 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq /* Smart PC - TA interfaces */ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); -void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); +#ifdef CONFIG_AMD_PMF_DEBUG +void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); +#else +static inline void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {} +#endif + int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index efb09a7915f7..f4ddb7333ff4 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -60,8 +60,6 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * amd_pmf_get_ta_custom_bios_inputs(in, i)); dev_dbg(dev->dev, "==== TA inputs END ====\n"); } -#else -void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {} #endif /* -- cgit v1.2.3 From 23ab340126aa1f70190272a65354e160ca82a2cf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 1 Sep 2026 23:02:12 +0800 Subject: platform/x86/amd/pmf: Inline simple helper functions of Core Layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Core Layer has simple and dumb helper functions defined as global symbols, which are heavily used by other layers and become symbols against linkage. This bloats the size of the module. Convert them into static inline functions to get rid of the overhead of function outlining and linkage, and shrink the module (!CONFIG_AMD_PMF_DEBUG && CONFIG_AMD_PMF_UTIL_SUPPORT) size by 432 Bytes (GCC 16 -O2): text data bss total filename (before) 26781 31636 2768 61185 amd-pmf.ko text data bss total filename (after) 26533 31452 2768 60753 amd-pmf.ko Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260901-amd-pmf-fix-outlining-v1-2-b54ff94d3d45@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/acpi.c | 11 ----------- drivers/platform/x86/amd/pmf/core.c | 22 --------------------- drivers/platform/x86/amd/pmf/pmf.h | 39 +++++++++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c index 3d94b03cf794..6a2c11e75eea 100644 --- a/drivers/platform/x86/amd/pmf/acpi.c +++ b/drivers/platform/x86/amd/pmf/acpi.c @@ -158,17 +158,6 @@ out: return err; } -int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index) -{ - /* If bit-n is set, that indicates function n+1 is supported */ - return !!(pdev->supported_func & BIT(index - 1)); -} - -int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev) -{ - return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS); -} - int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, struct amd_pmf_apts_granular_output *data, u32 apts_idx) { diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index 5966190d1496..ba9324448586 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -138,14 +138,6 @@ static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev) ¤t_power_limits_fops); } -int amd_pmf_get_power_source(void) -{ - if (power_supply_is_system_supplied() > 0) - return POWER_SOURCE_AC; - else - return POWER_SOURCE_DC; -} - static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) { return ioread32(dev->regbase + reg_offset); @@ -176,20 +168,6 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); } -/** - * fixp_q88_fromint: Convert integer to Q8.8 - * @val: input value - * - * Converts an integer into binary fixed point format where 8 bits - * are used for integer and 8 bits are used for the decimal. - * - * Return: unsigned integer converted to Q8.8 format - */ -u32 fixp_q88_fromint(u32 val) -{ - return val << 8; -} - int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data) { int rc; diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 4da2ef1abb50..024f20306e11 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -13,11 +13,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -1073,18 +1075,47 @@ struct ta_pmf_shared_memory { /* Core Layer */ int apmf_acpi_init(struct amd_pmf_dev *pmf_dev); void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev); -int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index); int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data); int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev); -int amd_pmf_get_power_source(void); int apmf_install_handler(struct amd_pmf_dev *pmf_dev); int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag); int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer); int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag); -u32 fixp_q88_fromint(u32 val); -int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev); void amd_pmf_set_device(struct device *p_device); +static inline int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index) +{ + /* If bit-n is set, that indicates function n+1 is supported */ + return !!(pdev->supported_func & BIT(index - 1)); +} + +static inline int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev) +{ + return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS); +} + +static inline int amd_pmf_get_power_source(void) +{ + if (power_supply_is_system_supplied() > 0) + return POWER_SOURCE_AC; + else + return POWER_SOURCE_DC; +} + +/** + * fixp_q88_fromint: Convert integer to Q8.8 + * @val: input value + * + * Converts an integer into binary fixed point format where 8 bits + * are used for integer and 8 bits are used for the decimal. + * + * Return: unsigned integer converted to Q8.8 format + */ +static inline u32 fixp_q88_fromint(u32 val) +{ + return val << 8; +} + /* Metrics layer */ int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); -- cgit v1.2.3 From 1987748fe49b55409a28722dcebbaaab50afbe69 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 1 Sep 2026 23:02:13 +0800 Subject: platform/x86/amd/pmf: Inline simple helper function of SPS Layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SPS Layer has a simple and dumb helper function defined as a global symbol, which is heavily used by other layers and becomes a symbol against linkage. This bloats the size of the module. Convert it into a static inline function to get rid of the overhead of function outlining and linkage, and shrink the module (!CONFIG_AMD_PMF_DEBUG && CONFIG_AMD_PMF_UTIL_SUPPORT) size by 252 Bytes (GCC 16 -O2): text data bss total filename (before) 26533 31452 2768 60753 amd-pmf.ko text data bss total filename (after) 26341 31392 2768 60501 amd-pmf.ko There are also two duplicated prototypes, so remove one of them as well. Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260901-amd-pmf-fix-outlining-v1-3-b54ff94d3d45@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 8 +++++--- drivers/platform/x86/amd/pmf/sps.c | 5 ----- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 024f20306e11..4a4c3aeadecc 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -1126,12 +1126,9 @@ void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx, int amd_pmf_init_sps(struct amd_pmf_dev *dev); int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev, struct apmf_static_slider_granular_output *output); -bool is_pprof_balanced(struct amd_pmf_dev *pmf); int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev); const char *amd_pmf_source_as_str(unsigned int state); -const char *amd_pmf_source_as_str(unsigned int state); - int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx); int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf); int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev, @@ -1139,6 +1136,11 @@ int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev, int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, struct amd_pmf_apts_granular_output *data, u32 apts_idx); +static inline bool is_pprof_balanced(struct amd_pmf_dev *pmf) +{ + return pmf->current_profile == PLATFORM_PROFILE_BALANCED; +} + /* Auto Mode Layer */ int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data); void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev); diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c index 0918fb388fde..b0031afae33b 100644 --- a/drivers/platform/x86/amd/pmf/sps.c +++ b/drivers/platform/x86/amd/pmf/sps.c @@ -285,11 +285,6 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf) return 0; } -bool is_pprof_balanced(struct amd_pmf_dev *pmf) -{ - return pmf->current_profile == PLATFORM_PROFILE_BALANCED; -} - static int amd_pmf_profile_get(struct device *dev, enum platform_profile_option *profile) { -- cgit v1.2.3 From 4cef7b872a6cd5d5987171b661d2dd7d3d936a2a Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 1 Sep 2026 23:02:14 +0800 Subject: platform/x86/amd/pmf: Inline simple helper function of Smart PC TA interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Smart PC TA interfaces have a simple and dumb helper function defined as a global symbol, which is used by other layers and becomes a symbol against linkage. This bloats the size of the module. Convert it into a static inline function to get rid of the overhead of function outlining and linkage, and shrink the module (!CONFIG_AMD_PMF_DEBUG && CONFIG_AMD_PMF_UTIL_SUPPORT) size by 208 Bytes (GCC 16 -O2): text data bss total filename (before) 26341 31392 2768 60501 amd-pmf.ko text data bss total filename (after) 26261 31264 2768 60293 amd-pmf.ko Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260901-amd-pmf-fix-outlining-v1-4-b54ff94d3d45@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 13 ++++++++++++- drivers/platform/x86/amd/pmf/spc.c | 13 ------------- 2 files changed, 12 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 4a4c3aeadecc..6d9963677a46 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -1171,7 +1171,18 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq /* Smart PC - TA interfaces */ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); -u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); + +static inline u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) +{ + switch (index) { + case 0 ... 1: + return in->ev_info.bios_input_1[index]; + case 2 ... 9: + return in->ev_info.bios_input_2[index - 2]; + default: + return 0; + } +} #ifdef CONFIG_AMD_PMF_DEBUG void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index f4ddb7333ff4..a12b4ee965a2 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -17,19 +17,6 @@ #include #include "pmf.h" -u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) -{ - switch (index) { - case 0 ... 1: - return in->ev_info.bios_input_1[index]; - case 2 ... 9: - return in->ev_info.bios_input_2[index - 2]; - default: - return 0; - } -} -EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); - #ifdef CONFIG_AMD_PMF_DEBUG void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) { -- cgit v1.2.3 From 29c2ed3f41370a6e4884fd61fae0d8e382578afd Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 1 Sep 2026 23:02:15 +0800 Subject: platform/x86/amd/pmf: Inline wrappers of ap{mf,ts}_if_call_store_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are several simple and dumb wrappers of ap{mf,ts}_if_call_store_buffer() defined as global symbols, which are heavily used by multiple layers and become symbols against linkage. This bloats the size of the module. Since is_apmf_func_supported() has been converted, convert the mentioned wrappers into static inline functions to get rid of the overhead of function outlining and linkage, and shrink the module (!CONFIG_AMD_PMF_DEBUG && CONFIG_AMD_PMF_UTIL_SUPPORT) size by 515 Bytes (GCC 16 -O2): text data bss total filename (before) 26261 31264 2768 60293 amd-pmf.ko text data bss total filename (after) 26046 30964 2768 59778 amd-pmf.ko Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260901-amd-pmf-fix-outlining-v1-5-b54ff94d3d45@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/acpi.c | 65 +---------------------------- drivers/platform/x86/amd/pmf/pmf.h | 83 +++++++++++++++++++++++++++++++------ 2 files changed, 73 insertions(+), 75 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c index 6a2c11e75eea..4392bf0bc0bf 100644 --- a/drivers/platform/x86/amd/pmf/acpi.c +++ b/drivers/platform/x86/amd/pmf/acpi.c @@ -50,7 +50,7 @@ static union acpi_object *apmf_if_call(struct amd_pmf_dev *pdev, int fn, struct return buffer.pointer; } -static int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz) +int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz) { union acpi_object *info; size_t size; @@ -121,8 +121,7 @@ static union acpi_object *apts_if_call(struct amd_pmf_dev *pdev, u32 state_index return buffer.pointer; } -static int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, - u32 index, void *data, size_t out_sz) +int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz) { union acpi_object *info; size_t size; @@ -158,35 +157,6 @@ out: return err; } -int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, - struct amd_pmf_apts_granular_output *data, u32 apts_idx) -{ - if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) - return -EINVAL; - - return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data)); -} - -int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, - struct apmf_static_slider_granular_output_v2 *data) -{ - if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) - return -EINVAL; - - return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR, - data, sizeof(*data)); -} - -int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev, - struct apmf_static_slider_granular_output *data) -{ - if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) - return -EINVAL; - - return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR, - data, sizeof(*data)); -} - int apmf_os_power_slider_update(struct amd_pmf_dev *pdev, u8 event) { struct os_power_slider args; @@ -302,27 +272,6 @@ static int apmf_notify_smart_pc_update(struct amd_pmf_dev *pdev, u32 val, u32 pr return 0; } -int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data)); -} - -int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); -} - -int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); -} - -int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, - req, sizeof(*req)); -} - /* Store custom BIOS inputs data in ring buffer */ static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev) { @@ -484,16 +433,6 @@ static int apmf_get_system_params(struct amd_pmf_dev *dev) return 0; } -int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data)); -} - -int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data) -{ - return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data)); -} - static apmf_event_handler_t apmf_event_handlers[] = { [PMF_IF_V1] = apmf_event_handler_v1, [PMF_IF_V2] = apmf_event_handler_v2, diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 6d9963677a46..9f64ab833c08 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1075,6 +1076,8 @@ struct ta_pmf_shared_memory { /* Core Layer */ int apmf_acpi_init(struct amd_pmf_dev *pmf_dev); void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev); +int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz); +int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz); int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data); int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev); int apmf_install_handler(struct amd_pmf_dev *pmf_dev); @@ -1124,17 +1127,43 @@ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx, struct amd_pmf_static_slider_granular *table); int amd_pmf_init_sps(struct amd_pmf_dev *dev); -int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev, - struct apmf_static_slider_granular_output *output); int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev); const char *amd_pmf_source_as_str(unsigned int state); int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx); int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf); -int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev, - struct apmf_static_slider_granular_output_v2 *data); -int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, - struct amd_pmf_apts_granular_output *data, u32 apts_idx); + +static inline int +apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, + struct amd_pmf_apts_granular_output *data, u32 apts_idx) +{ + if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) + return -EINVAL; + + return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data)); +} + +static inline int +apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, + struct apmf_static_slider_granular_output_v2 *data) +{ + if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) + return -EINVAL; + + return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR, + data, sizeof(*data)); +} + +static inline int +apmf_get_static_slider_granular(struct amd_pmf_dev *pdev, + struct apmf_static_slider_granular_output *data) +{ + if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) + return -EINVAL; + + return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR, + data, sizeof(*data)); +} static inline bool is_pprof_balanced(struct amd_pmf_dev *pmf) { @@ -1142,26 +1171,56 @@ static inline bool is_pprof_balanced(struct amd_pmf_dev *pmf) } /* Auto Mode Layer */ -int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data); void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev); void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev); void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms); -int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req); -int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req); -int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req); void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event); int amd_pmf_reset_amt(struct amd_pmf_dev *dev); void amd_pmf_handle_amt(struct amd_pmf_dev *dev); +static inline int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data)); +} + +static inline int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, + struct apmf_sbios_req_v2 *req) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); +} + +static inline int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, + struct apmf_sbios_req_v1 *req) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); +} + +static inline int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, + struct apmf_sbios_req *req) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, + req, sizeof(*req)); +} + /* CnQF Layer */ -int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data); -int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data); int amd_pmf_init_cnqf(struct amd_pmf_dev *dev); void amd_pmf_deinit_cnqf(struct amd_pmf_dev *dev); int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_lapsed_ms); extern const struct attribute_group cnqf_feature_attribute_group; +static inline int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, + struct apmf_dyn_slider_output *data) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data)); +} + +static inline int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, + struct apmf_dyn_slider_output *data) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data)); +} + /* Smart PC builder Layer */ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev); void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev); -- cgit v1.2.3 From 1b55ec81926eb7f6e4fad692fa4040a569e9daf7 Mon Sep 17 00:00:00 2001 From: Aleksandrova Alyona Date: Thu, 17 Sep 2026 14:08:12 +0300 Subject: platform/x86: intel_telemetry: initialize default trace verbosity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit telemetry_def_get_trace_verbosity() returns success without initializing the output verbosity value. The default telemetry operations are installed when the core driver is initialized and again by telemetry_clear_pltdata() when the platform driver is removed. The telemetry debugfs files can remain available after the platform driver removal. A subsequent read of pss_trace_verbosity or ioss_trace_verbosity calls telemetry_get_trace_verbosity(), which may use the default callback and return success without setting verbosity. The uninitialized value is then passed to seq_printf(). Validate the output pointer and initialize the default verbosity to zero so callers never use an uninitialized value after a successful call. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 378f956e3f93 ("platform/x86: Add Intel Telemetry Core Driver") Signed-off-by: Aleksandrova Alyona Link: https://patch.msgid.link/20260917110812.2831-1-aga@itb.spb.ru Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/telemetry/core.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c index f312864b8d07..62299524a07b 100644 --- a/drivers/platform/x86/intel/telemetry/core.c +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -24,6 +24,10 @@ static struct telemetry_core_config telm_core_conf; static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit, u32 *verbosity) { + if (!verbosity) + return -EINVAL; + + *verbosity = 0; return 0; } -- cgit v1.2.3 From 97fa641b21c0bf05e2c714c971a19f9d24b42d6f Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 24 Sep 2026 00:31:27 +0800 Subject: platform/x86: ideapad-laptop: Report camera switch as SW_CAMERA_LENS_COVER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On certain 2025 Lenovo laptops, such as the Yoga Pro 7 14ASP10 and Legion Pro 7 16AFR10H, a switch on the side disables the camera. This is handled by the firmware, which also sends event 0x0d (camera disabled) or 0x0c (camera enabled) through the WMI event GUID handled with ideapad_wmi_context_fn_keys. These events are currently reported as KEY_UNKNOWN. Other Lenovo laptops expose a similar camera switch through a separate WMI GUID, which lenovo-wmi-camera reports as SW_CAMERA_LENS_COVER since commit d98bf6a6ed61 ("platform/x86: lenovo-wmi-camera: Use SW_CAMERA_LENS_COVER instead of KEY_CAMERA_ACESS"). That driver does not bind on these laptops, so map the WMI events to SW_CAMERA_LENS_COVER as well via sparse keymap KE_SW. Unfortunately, there is no programmatic way to determine the current state on the mentioned devices until the next relevant WMI key event. Therefore, no switch event is emitted on probe or resume, intentionally leaving the default (0, i.e., no covered) or the last state unchanged, and let the first relevant WMI key event to correct it. This should fulfill the requirement of using the switch events to show an OSD. AFAIK, no userspace software queries its state via EVIOCGBIT(EV_SW). The above behavior is basically the same as lenovo-wmi-camera, except that SW_CAMERA_LENS_COVER is always exposed even on devices with no camera switch. It's because, again, there is no programmatic way to determine the switch's existence on the mentioned devices. Doing so should be harmless as long as no switch event is emitted and eliminates the need to dynamically register an additional input device at run time like lenovo-wmi-camera does. Even if a userspace program does query the switch's state via EVIOCGBIT(EV_SW), the returned state will always be 0 (not covered), which is the correct state on unsupported devices. Co-developed-by: Marco Giunta Signed-off-by: Marco Giunta Tested-by: Marco Giunta Signed-off-by: Rong Zhang Reviewed-by: Huang Wei Link: https://patch.msgid.link/20260924-ideapad-laptop-camera-sw-v3-1-922ea6a9fff4@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ideapad-laptop.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 207368f5d489..fcb5ed7035f5 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -1369,6 +1369,18 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 0x29 | IDEAPAD_WMI_KEY, { KEY_TOUCHPAD_TOGGLE } }, { KE_KEY, 0x2a | IDEAPAD_WMI_KEY, { KEY_ROOT_MENU } }, + /* + * WMI switches + * + * Some devices come with physical switches. Whenever a switch is + * toggled, an WMI event is emitted to inform the software about the + * switch's latest state. + */ + + /* Camera switch */ + { KE_SW, 0x0c | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 0 } }, + { KE_SW, 0x0d | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 1 } }, + { KE_END }, }; -- cgit v1.2.3 From 9523698252fd583d6987582b9c34ec6500d10986 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 24 Sep 2026 19:56:17 +0800 Subject: platform/x86: simatic-ipc: fix platform device leak on registration failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit register_platform_devices() registers the battery, LED, and watchdog platform devices in sequence. If LED registration fails, the battery device remains registered. If watchdog registration fails, the LED and battery devices remain registered. These failure paths leave successful platform_device_register_data() calls without the matching platform_device_unregister() calls. Add an unwind ladder to unregister previously registered platform devices in reverse order before returning the registration error. The issue was identified by a static analysis tool I developed and confirmed by manual review. Fixes: dd123e62bded ("platform/x86: simatic-ipc: add main driver for Siemens devices") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260924115617.1578309-1-lgs201920130244@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/siemens/simatic-ipc.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/siemens/simatic-ipc.c b/drivers/platform/x86/siemens/simatic-ipc.c index 7039874d8f11..4a4509fc9263 100644 --- a/drivers/platform/x86/siemens/simatic-ipc.c +++ b/drivers/platform/x86/siemens/simatic-ipc.c @@ -92,6 +92,7 @@ static int register_platform_devices(u32 station_id) u8 wdtmode = SIMATIC_IPC_DEVICE_NONE; u8 battmode = SIMATIC_IPC_DEVICE_NONE; char *pdevname; + int ret; int i; for (i = 0; i < ARRAY_SIZE(device_modes); i++) { @@ -139,8 +140,10 @@ static int register_platform_devices(u32 station_id) pdevname, PLATFORM_DEVID_NONE, &platform_data, sizeof(struct simatic_ipc_platform)); - if (IS_ERR(ipc_led_platform_device)) - return PTR_ERR(ipc_led_platform_device); + if (IS_ERR(ipc_led_platform_device)) { + ret = PTR_ERR(ipc_led_platform_device); + goto err_unregister_batt; + } pr_debug("device=%s created\n", ipc_led_platform_device->name); @@ -153,8 +156,10 @@ static int register_platform_devices(u32 station_id) KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE, &platform_data, sizeof(struct simatic_ipc_platform)); - if (IS_ERR(ipc_wdt_platform_device)) - return PTR_ERR(ipc_wdt_platform_device); + if (IS_ERR(ipc_wdt_platform_device)) { + ret = PTR_ERR(ipc_wdt_platform_device); + goto err_unregister_led; + } pr_debug("device=%s created\n", ipc_wdt_platform_device->name); @@ -169,6 +174,14 @@ static int register_platform_devices(u32 station_id) } return 0; + +err_unregister_led: + if (ledmode != SIMATIC_IPC_DEVICE_NONE) + platform_device_unregister(ipc_led_platform_device); +err_unregister_batt: + if (battmode != SIMATIC_IPC_DEVICE_NONE) + platform_device_unregister(ipc_batt_platform_device); + return ret; } static void request_additional_modules(u32 station_id) -- cgit v1.2.3 From ae53bb89b9703e2a691651368411988782e9b1a7 Mon Sep 17 00:00:00 2001 From: Artem Maksimov Date: Fri, 25 Sep 2026 22:44:54 +0300 Subject: platform/x86: hp-bioscfg: Support allocating large number of BIOS vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On HP systems with a large number of BIOS attribute instances, the kzalloc_objs() allocation in hp_alloc_ordered_list_data() and hp_alloc_string_data() can fail when physical memory is fragmented, leaving the driver unable to register those attributes. The same failure mode was already fixed for enumeration_data in hp_alloc_enumeration_data() by commit 916727cfdb72 ("platform/x86: hp-bioscfg: Support allocations of larger data"), reported on HP Z2 Tower G9 and Z4 G5 Workstation systems: https://bugs.debian.org/1127612 order-list-attributes.c and string-attributes.c use the identical allocation pattern and are still affected. Apply the same fix: fall back to kvzalloc_objs()/kvfree() instead of kzalloc_objs()/kfree(), and return -EINVAL early when the instance count is zero. Link: https://bugs.debian.org/1127612 Signed-off-by: Artem Maksimov Link: https://patch.msgid.link/20260925194454.6850-1-okuroshioffc@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 11 ++++++++--- drivers/platform/x86/hp/hp-bioscfg/string-attributes.c | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c index 95fca0919f1f..ccb1ea03ef64 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -98,8 +98,13 @@ int hp_alloc_ordered_list_data(void) { bioscfg_drv.ordered_list_instances_count = hp_get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID); - bioscfg_drv.ordered_list_data = kzalloc_objs(*bioscfg_drv.ordered_list_data, - bioscfg_drv.ordered_list_instances_count); + + if (!bioscfg_drv.ordered_list_instances_count) + return -EINVAL; + bioscfg_drv.ordered_list_data = + kvzalloc_objs(*bioscfg_drv.ordered_list_data, + bioscfg_drv.ordered_list_instances_count); + if (!bioscfg_drv.ordered_list_data) { bioscfg_drv.ordered_list_instances_count = 0; return -ENOMEM; @@ -390,6 +395,6 @@ void hp_exit_ordered_list_attributes(void) } bioscfg_drv.ordered_list_instances_count = 0; - kfree(bioscfg_drv.ordered_list_data); + kvfree(bioscfg_drv.ordered_list_data); bioscfg_drv.ordered_list_data = NULL; } diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index e9bfbef8a3a4..c433b67870bd 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -101,8 +101,13 @@ static const struct attribute_group string_attr_group = { int hp_alloc_string_data(void) { bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID); - bioscfg_drv.string_data = kzalloc_objs(*bioscfg_drv.string_data, - bioscfg_drv.string_instances_count); + + if (!bioscfg_drv.string_instances_count) + return -EINVAL; + bioscfg_drv.string_data = + kvzalloc_objs(*bioscfg_drv.string_data, + bioscfg_drv.string_instances_count); + if (!bioscfg_drv.string_data) { bioscfg_drv.string_instances_count = 0; return -ENOMEM; @@ -342,6 +347,6 @@ void hp_exit_string_attributes(void) } bioscfg_drv.string_instances_count = 0; - kfree(bioscfg_drv.string_data); + kvfree(bioscfg_drv.string_data); bioscfg_drv.string_data = NULL; } -- cgit v1.2.3 From 1fe88b1741ff655bb59e4fd78178fe2c557b9185 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Sat, 26 Sep 2026 20:12:48 +0800 Subject: platform/olpc: Reserve space for a string terminator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ec_dbgfs_cmd_write() copies user input into a zero-initialized stack buffer and passes it to sscanf(). A write that overwrites the entire buffer leaves no terminator, so the subsequent parsing can read beyond the buffer. Limit the copy to leave room for the trailing NUL. Fixes: 6cca83d498bd ("Platform: OLPC: move debugfs support from x86 EC driver") Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260926121250.3258285-2-yaojiale02@163.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/olpc/olpc-ec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 4a2d36f7331e..063f7e55c421 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -269,7 +269,7 @@ static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf, mutex_lock(&ec_dbgfs_lock); - size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size); + size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf) - 1, ppos, buf, size); m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0], &ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2], -- cgit v1.2.3 From 450d2495936614ccf22c7bdf103a956833f084d5 Mon Sep 17 00:00:00 2001 From: Mohamed Jouini Date: Thu, 8 Oct 2026 14:56:23 +0100 Subject: platform/x86: uniwill-laptop: Add PCSpecialist Recoil 16 AMD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCSpecialist Recoil 16 AMD is a rebadged TUXEDO Stellaris 16 Gen7 AMD but reports sys_vendor "PCSpecialist" and board name "X6FR57TY", so the existing entry does not match it. Use the Stellaris 16 Gen7 AMD feature set. Signed-off-by: Mohamed Jouini Link: https://patch.msgid.link/20261008135624.3520849-2-amad3v@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 07b69629bb5e..dc9b73afd636 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -3451,6 +3451,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { }, .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, + { + .ident = "PCSpecialist Recoil 16 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR57TY"), + }, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, + }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", .matches = { -- cgit v1.2.3 From ce1ad174ca7613c8a3d358f08ba145d1e63ed7b1 Mon Sep 17 00:00:00 2001 From: Mohamed Jouini Date: Thu, 8 Oct 2026 14:56:24 +0100 Subject: platform/x86: uniwill-laptop: Map the screen rotation key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PCSpecialist Recoil 16 AMD / TUXEDO Stellaris 16 Gen7 chassis has an "Sc" key next to F12, meant to rotate the lay-flat screen by 180 degrees. It reports WMI event 0xD0, which is currently reported as KEY_UNKNOWN. Map it to KEY_ROTATE_DISPLAY. Signed-off-by: Mohamed Jouini Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20261008135624.3520849-3-amad3v@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 3 +++ drivers/platform/x86/uniwill/uniwill-wmi.h | 3 +++ 2 files changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index dc9b73afd636..6b0dd0e4f01a 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -506,6 +506,9 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the user wants to rotate the (lay-flat) screen by 180 degrees */ + { KE_KEY, UNIWILL_OSD_ROTATE_SCREEN, { KEY_ROTATE_DISPLAY }}, + { KE_END } }; diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index b25b2f31211c..1b6f650c40e5 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -117,6 +117,9 @@ #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF +/* "Sc" key next to F12 on the PCSpecialist Recoil 16 / Stellaris 16 Gen7 */ +#define UNIWILL_OSD_ROTATE_SCREEN 0xD0 + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 struct device; -- cgit v1.2.3 From eb3de1b35c871d4351900421d9ec1a2114d1dd98 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 7 Oct 2026 16:38:54 +0200 Subject: platform/x86: uniwill-laptop: Report correct lightbar brightness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LED sysfs ABI now allows us to specify the maximum values for the global brightness and the color intensity values independently. At the same time the maximum brightness value reported for the lightbar (like 200) does not match the real value supported by the hardware (1), potentially confusing users. Report the correct maximum brightness value and only use the intensity values for controlling the lightbar color. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20261007144101.323289-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 43 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 6b0dd0e4f01a..6de36aa7b62b 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -398,7 +398,7 @@ struct uniwill_data { struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ - u8 lightbar_max_brightness; + u8 lightbar_max_intensity; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; bool kbd_led_single_color; @@ -429,7 +429,7 @@ struct uniwill_device_descriptor { unsigned int features; bool kbd_led_single_color; u8 kbd_led_max_brightness; - u8 lightbar_max_brightness; + u8 lightbar_max_intensity; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -1514,20 +1514,16 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br unsigned int value; int ret; - ret = led_mc_calc_color_components(led_mc_cdev, brightness); - if (ret < 0) - return ret; - guard(mutex)(&data->led_lock); for (int i = 0; i < LED_CHANNELS; i++) { - /* Prevent the brightness values from overflowing */ - value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); - ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], + data->led_mc_subled_info[i].intensity); if (ret < 0) return ret; - ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], + data->led_mc_subled_info[i].intensity); if (ret < 0) return ret; } @@ -1591,14 +1587,14 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; - data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.max_brightness = 1; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else - data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.brightness = 1; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1609,9 +1605,9 @@ static int uniwill_led_init(struct uniwill_data *data) /* * Make sure that the initial intensity value is not greater than - * the maximum brightness. + * the maximum intensity. */ - value = min(data->lightbar_max_brightness, value); + value = min(data->lightbar_max_intensity, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1621,6 +1617,7 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_subled_info[i].intensity = value; + data->led_mc_subled_info[i].max_intensity = data->lightbar_max_intensity; data->led_mc_subled_info[i].channel = i; } @@ -2369,7 +2366,7 @@ static int uniwill_probe(struct platform_device *pdev) data->features = device_descriptor.features; data->kbd_led_single_color = device_descriptor.kbd_led_single_color; data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; - data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + data->lightbar_max_intensity = device_descriptor.lightbar_max_intensity; /* * Some devices might need to perform some device-specific initialization steps @@ -2716,7 +2713,7 @@ static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, - .lightbar_max_brightness = 36, + .lightbar_max_intensity = 36, }; static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { @@ -2740,7 +2737,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, - .lightbar_max_brightness = 200, + .lightbar_max_intensity = 200, }; static struct uniwill_device_descriptor pfxnuxx_pfxluxx_phxprxx_descriptor __initdata = { @@ -2780,7 +2777,7 @@ static struct uniwill_device_descriptor gmxmpxx_descriptor __initdata = { UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .lightbar_max_brightness = 36, + .lightbar_max_intensity = 36, }; static struct uniwill_device_descriptor phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor __initdata = { @@ -2839,7 +2836,7 @@ static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_ste_descriptor __initdat UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .lightbar_max_brightness = 36, + .lightbar_max_intensity = 36, }; static int gmxrgxx_probe(struct uniwill_data *data) @@ -2853,7 +2850,7 @@ static int gmxrgxx_probe(struct uniwill_data *data) if (value & LIGHTBAR) { data->features |= UNIWILL_FEATURE_LIGHTBAR; - data->lightbar_max_brightness = 36; + data->lightbar_max_intensity = 36; } ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &value); @@ -2891,7 +2888,7 @@ static struct uniwill_device_descriptor gmxagxx_descriptor __initdata = { UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, - .lightbar_max_brightness = 36, + .lightbar_max_intensity = 36, }; static struct uniwill_device_descriptor gmxxgxx_gmxpxxx_descriptor __initdata = { @@ -3549,8 +3546,8 @@ static int __init uniwill_init(void) device_descriptor.kbd_led_single_color = false; /* Some models only support 3 brightness levels */ device_descriptor.kbd_led_max_brightness = 4; - /* Some models only support 36 brightness levels per color component */ - device_descriptor.lightbar_max_brightness = 200; + /* Some models only support 36 intensity levels per color component */ + device_descriptor.lightbar_max_intensity = 200; pr_warn("Enabling potentially unsupported features\n"); } -- cgit v1.2.3 From d1c0c89969779788ece8f3a12add87f3ba91352c Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 7 Oct 2026 16:38:55 +0200 Subject: platform/x86: uniwill-laptop: Implement rainbow animation as trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It turns out that some models require the lightbar to be activated in order to display the rainbow animation, so users have to access both the "rainbow_animation" sysfs attribute and the LED sysfs interface when enabling the rainbow animation. This can cause short glitches when the lightbar was previously disabled. Fix this by replacing the sysfs attribute with a custom LED trigger called "uniwill-rainbow". Selecting this trigger will atomically activate the lightbar and enable the rainbow animation mode. This can potentially break userspace applications using the old sysfs attribute, but so far no such application is known to exist (yet). Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20261007144101.323289-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/Kconfig | 1 + drivers/platform/x86/uniwill/uniwill-acpi.c | 133 +++++++++++++++++----------- 2 files changed, 81 insertions(+), 53 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/uniwill/Kconfig index d07cc8440188..f80794bdfaef 100644 --- a/drivers/platform/x86/uniwill/Kconfig +++ b/drivers/platform/x86/uniwill/Kconfig @@ -28,6 +28,7 @@ config UNIWILL_LAPTOP depends on DMI select REGMAP select INPUT_SPARSEKMAP + select LEDS_TRIGGERS help This driver adds support for various extra features found on Uniwill laptops, like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 6de36aa7b62b..c8d9289770e5 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -903,51 +903,6 @@ static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_att static DEVICE_ATTR_RW(touchpad_toggle_enable); -static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; - bool enable; - int ret; - - ret = kstrtobool(buf, &enable); - if (ret < 0) - return ret; - - if (enable) - value = LIGHTBAR_WELCOME; - else - value = 0; - - guard(mutex)(&data->led_lock); - - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value); - if (ret < 0) - return ret; - - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; - int ret; - - ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); - if (ret < 0) - return ret; - - return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME)); -} - -static DEVICE_ATTR_RW(rainbow_animation); - static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -1261,7 +1216,6 @@ static struct attribute *uniwill_attrs[] = { &dev_attr_super_key_enable.attr, &dev_attr_touchpad_toggle_enable.attr, /* Lightbar-related */ - &dev_attr_rainbow_animation.attr, &dev_attr_breathing_in_suspend.attr, /* Power-management-related */ &dev_attr_ctgp_offset.attr, @@ -1291,8 +1245,7 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } - if (attr == &dev_attr_rainbow_animation.attr || - attr == &dev_attr_breathing_in_suspend.attr) { + if (attr == &dev_attr_breathing_in_suspend.attr) { if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR)) return attr->mode; } @@ -1540,6 +1493,59 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value); } +static int uniwill_led_hw_control_set(struct led_classdev *led_cdev, unsigned long flags) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev); + unsigned int value; + int ret; + + guard(mutex)(&data->led_lock); + + if (flags) + value = LIGHTBAR_WELCOME; + else + value = LIGHTBAR_S0_OFF; + + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, + LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, + LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value); + if (ret < 0) + return ret; + + /* The LED is always on during hw control */ + led_cdev->brightness = flags; + + return 0; +} + +static int uniwill_lightbar_trigger_activate(struct led_classdev *led_cdev) +{ + return led_cdev->hw_control_set(led_cdev, 1); +} + +static void uniwill_lightbar_trigger_deactivate(struct led_classdev *led_cdev) +{ + int ret; + + ret = led_cdev->hw_control_set(led_cdev, 0); + if (ret < 0) + dev_err(led_cdev->dev, "Failed to stop rainbow animation: %d\n", ret); +} + +static struct led_hw_trigger_type uniwill_lightbar_trigger_type; + +static struct led_trigger uniwill_lightbar_trigger = { + .name = "uniwill-rainbow", + .activate = uniwill_lightbar_trigger_activate, + .deactivate = uniwill_lightbar_trigger_deactivate, + .trigger_type = &uniwill_lightbar_trigger_type, +}; + #define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME) static int uniwill_led_init(struct uniwill_data *data) @@ -1590,12 +1596,18 @@ static int uniwill_led_init(struct uniwill_data *data) data->led_mc_cdev.led_cdev.max_brightness = 1; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + data->led_mc_cdev.led_cdev.trigger_type = &uniwill_lightbar_trigger_type; + data->led_mc_cdev.led_cdev.hw_control_trigger = uniwill_lightbar_trigger.name; + data->led_mc_cdev.led_cdev.hw_control_set = uniwill_led_hw_control_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else data->led_mc_cdev.led_cdev.brightness = 1; + if (value & LIGHTBAR_WELCOME) + data->led_mc_cdev.led_cdev.default_trigger = uniwill_lightbar_trigger.name; + for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -3551,17 +3563,31 @@ static int __init uniwill_init(void) pr_warn("Enabling potentially unsupported features\n"); } - ret = platform_driver_register(&uniwill_driver); + /* + * We cannot register the trigger inside the .probe callback of the + * platform driver, because each trigger needs a unique name. + */ + ret = led_trigger_register(&uniwill_lightbar_trigger); if (ret < 0) return ret; + ret = platform_driver_register(&uniwill_driver); + if (ret < 0) + goto err_platform; + ret = uniwill_wmi_register_driver(); - if (ret < 0) { - platform_driver_unregister(&uniwill_driver); - return ret; - } + if (ret < 0) + goto err_wmi; return 0; + +err_wmi: + platform_driver_unregister(&uniwill_driver); + +err_platform: + led_trigger_unregister(&uniwill_lightbar_trigger); + + return ret; } module_init(uniwill_init); @@ -3569,6 +3595,7 @@ static void __exit uniwill_exit(void) { uniwill_wmi_unregister_driver(); platform_driver_unregister(&uniwill_driver); + led_trigger_unregister(&uniwill_lightbar_trigger); } module_exit(uniwill_exit); -- cgit v1.2.3 From 1a1ecc9aa0b60a24e02fb7cb826f4a323a40ab86 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 7 Oct 2026 16:38:56 +0200 Subject: platform/x86: uniwill-laptop: Fix breathing animation on Intel QC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intel NUC 9 Extreme (Queens County) devices have a separate bit inside the EC_ADDR_LIGHTBAR_AC_CTRL register for enabling the breathing animation in s2idle mode. Add support for this additional setting. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20261007144101.323289-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index c8d9289770e5..b58502dc04de 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -165,9 +165,10 @@ #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 #define LIGHTBAR_APP_EXISTS BIT(0) #define LIGHTBAR_POWER_SAVE BIT(1) -#define LIGHTBAR_S0_OFF BIT(2) -#define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended -#define LIGHTBAR_WELCOME BIT(7) // Rainbow animation +#define LIGHTBAR_S0_OFF BIT(2) /* Also disables animations */ +#define LIGHTBAR_S3_OFF BIT(3) /* No breathing animation when in s2ram */ +#define LIGHTBAR_MODERN_STANDBY_ON BIT(6) /* Breathing animation when in s2idle */ +#define LIGHTBAR_WELCOME BIT(7) /* Rainbow animation */ #define EC_ADDR_LIGHTBAR_AC_RED 0x0749 @@ -306,7 +307,7 @@ #define EC_ADDR_USB_C_POWER_PRIORITY 0x07CC #define USB_C_POWER_PRIORITY BIT(7) -/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ +/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF and LIGHTBAR_MODERN_STANDBY_ON */ #define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 #define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3 @@ -916,12 +917,13 @@ static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attr return ret; if (enable) - value = 0; + value = LIGHTBAR_MODERN_STANDBY_ON; else value = LIGHTBAR_S3_OFF; /* We only access a single register here, so we do not need to use data->led_lock */ - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value); + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, + LIGHTBAR_S3_OFF | LIGHTBAR_MODERN_STANDBY_ON, value); if (ret < 0) return ret; @@ -939,6 +941,10 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri if (ret < 0) return ret; + /* + * We only test LIGHTBAR_S3_OFF here, because LIGHTBAR_MODERN_STANDBY_ON + * should have the exact opposite value. + */ return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF)); } @@ -1546,7 +1552,8 @@ static struct led_trigger uniwill_lightbar_trigger = { .trigger_type = &uniwill_lightbar_trigger_type, }; -#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME) +#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | \ + LIGHTBAR_MODERN_STANDBY_ON | LIGHTBAR_WELCOME) static int uniwill_led_init(struct uniwill_data *data) { @@ -1579,6 +1586,11 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; value |= LIGHTBAR_APP_EXISTS; + if (value & LIGHTBAR_S3_OFF) + value &= ~LIGHTBAR_MODERN_STANDBY_ON; + else + value |= LIGHTBAR_MODERN_STANDBY_ON; + ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value); if (ret < 0) return ret; @@ -1588,6 +1600,7 @@ static int uniwill_led_init(struct uniwill_data *data) * running on battery power. */ value |= LIGHTBAR_S3_OFF; + value &= ~LIGHTBAR_MODERN_STANDBY_ON; ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value); if (ret < 0) return ret; -- cgit v1.2.3 From 207c3d61564726720be9250312316e7dc491e24d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 7 Oct 2026 16:38:57 +0200 Subject: platform/x86: uniwill-laptop: Label multicolor LEDs correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LED subsystem specifies that LEDs capable of producing arbitrary colors (like RGB) should label themselfs as "rgb" instead of "multicolor". Use the "rgb" label instead of the "multicolor" label to fix detection by userspace applications. Reported-by: Werner Sembach Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/18 Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20261007144101.323289-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index b58502dc04de..c37dfba801f2 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1559,7 +1559,7 @@ static int uniwill_led_init(struct uniwill_data *data) { struct led_init_data init_data = { .devicename = DRIVER_NAME, - .default_label = "multicolor:" LED_FUNCTION_STATUS, + .default_label = "rgb:" LED_FUNCTION_STATUS, .devname_mandatory = true, }; unsigned int color_indices[3] = { @@ -1605,7 +1605,7 @@ static int uniwill_led_init(struct uniwill_data *data) if (ret < 0) return ret; - data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB; data->led_mc_cdev.led_cdev.max_brightness = 1; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; @@ -1813,7 +1813,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) LED_COLOR_ID_BLUE, }; struct led_init_data init_data = { - .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, + .default_label = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, .devicename = DRIVER_NAME, .devname_mandatory = true, }; @@ -1874,7 +1874,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) return ret; data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; - data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB; data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; -- cgit v1.2.3 From 779f568965475573c2d6f7610f386b625795822f Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 7 Oct 2026 16:38:58 +0200 Subject: platform/x86: uniwill-laptop: Extend support for the Intel NUC x15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user has reported that the LAPKC71E variant of the Intel NUC x15 also works with the uniwill-laptop driver. The difference between the LAPKC71E and the LAPKC71F seems to be the dedicated NVidia GPU module. Add support for this device as well. It is suspected that the LAPKC71F also supports the new UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL feature, but this would need to be verified on real hardware. Link: https://github.com/Wer-Wolf/uniwill-laptop/issues/17 Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20261007144101.323289-6-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index c37dfba801f2..f62d59f29676 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2752,6 +2752,20 @@ static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { UNIWILL_FEATURE_SECONDARY_FAN, }; +static struct uniwill_device_descriptor lapkc71e_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .lightbar_max_intensity = 200, +}; + static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | @@ -3116,6 +3130,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { }, .driver_data = &lapac71h_descriptor, }, + { + .ident = "Intel NUC x15", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71E"), + }, + .driver_data = &lapkc71e_descriptor, + }, { .ident = "Intel NUC x15", .matches = { -- cgit v1.2.3 From 55d8406dfa2fa8ad8ac2f053c2a53a97d8b0ce48 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 12:04:09 -0500 Subject: platform/x86/amd: hfi: Fix a use-after-free when unloading the driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_hfi_probe() registers CPU hotplug callbacks with cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, ...) and schedules the static sched_amd_hfi_itmt_work work item, but amd_hfi_remove() tears down neither. The dynamically assigned hotplug state returned by cpuhp_setup_state() is discarded, so cpuhp_remove_state() can never be called, and the pending work is never cancelled. After the module is unloaded and its memory is freed, any subsequent CPU hotplug event or execution of the scheduled work dereferences the stale callback and work function pointers, resulting in a use-after-free and kernel panic. Store the hotplug state returned by cpuhp_setup_state() and, on removal, call cpuhp_remove_state() to unregister the callbacks and cancel_work_sync() to flush the scheduled work before the module memory goes away. Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: bb20421c05fc ("platform/x86: hfi: Add online and offline callback support") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260921170411.1149960-2-mario.limonciello@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hfi/hfi.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c index e0ebcb0c4acd..e1c776b71f3e 100644 --- a/drivers/platform/x86/amd/hfi/hfi.c +++ b/drivers/platform/x86/amd/hfi/hfi.c @@ -76,6 +76,8 @@ struct amd_hfi_data { struct amd_shmem_info *shmem; struct dentry *dbgfs_dir; + + int cpuhp_state; }; /** @@ -244,6 +246,8 @@ static void amd_hfi_remove(struct platform_device *pdev) { struct amd_hfi_data *dev = platform_get_drvdata(pdev); + cpuhp_remove_state(dev->cpuhp_state); + cancel_work_sync(&sched_amd_hfi_itmt_work); debugfs_remove_recursive(dev->dbgfs_dir); } @@ -494,6 +498,7 @@ static int amd_hfi_probe(struct platform_device *pdev) amd_hfi_online, amd_hfi_offline); if (ret < 0) return ret; + amd_hfi_data->cpuhp_state = ret; schedule_work(&sched_amd_hfi_itmt_work); -- cgit v1.2.3 From b7b273844b94af9cc64907dccd9e7997c2b60f1a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 12:04:10 -0500 Subject: platform/x86/amd: hfi: Fix out-of-bounds reads when parsing ranking data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_hfi_fill_metadata() parses CPU core ranking data out of the firmware-provided PCC shared memory but trusts two values it should not: - n_bitmaps is used to bound the outer loop that reads the APIC ID bitmaps from table_data[], but it is never validated against the size of the shared memory region (pcct_ext->length). A firmware-supplied count larger than the region reads past the end of table_data. - When resolving the base of the ranking data for a processor, the pointer is shifted by an extra "i * nr_class" term, where i is the current bitmap index. The per-processor offset is already applied through apic_index, which uses the running count of active processors (apic_start). The extra shift compounds for every logical processor beyond the first bitmap (APIC ID >= 32), reading out of bounds. Both result in out-of-bounds reads that can corrupt the ranking metrics, oops, or otherwise destabilise the system. Reject an n_bitmaps value that would not fit within the shared memory region, and drop the bogus "i * nr_class" term so the ranking data is addressed only through its correct per-processor offset. Cc: stable@vger.kernel.org Reported-by: Sashiko Fixes: d4e95ea7a78e ("platform/x86: hfi: Parse CPU core ranking data from shared memory") Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260921170411.1149960-3-mario.limonciello@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hfi/hfi.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c index e1c776b71f3e..6d1d3b6c28a9 100644 --- a/drivers/platform/x86/amd/hfi/hfi.c +++ b/drivers/platform/x86/amd/hfi/hfi.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -168,6 +169,21 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data) return -EINVAL; } + /* + * The bitmaps enumerating the APIC IDs occupy the first n_bitmaps + * words of table_data. Reject a firmware-provided count that would + * push those reads past the end of the shared memory region. + */ + if (struct_size(amd_hfi_data->shmem, table_data, amd_hfi_data->shmem->n_bitmaps) > + pcct_ext->length) { + dev_err(amd_hfi_data->dev, "invalid number of bitmaps: %u\n", + amd_hfi_data->shmem->n_bitmaps); + return -EINVAL; + } + + /* The ranking data for each processor follows the bitmaps */ + u32 *table = amd_hfi_data->shmem->table_data + amd_hfi_data->shmem->n_bitmaps; + for (unsigned int i = 0; i < amd_hfi_data->shmem->n_bitmaps; i++) { u32 bitmap = amd_hfi_data->shmem->table_data[i]; @@ -192,10 +208,6 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data) info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu_index); apic_index = apic_start * info->nr_class * 2; for (unsigned int k = 0; k < info->nr_class; k++) { - u32 *table = amd_hfi_data->shmem->table_data + - amd_hfi_data->shmem->n_bitmaps + - i * info->nr_class; - info->amd_hfi_classes[k].eff = table[apic_index + 2 * k]; info->amd_hfi_classes[k].perf = table[apic_index + 2 * k + 1]; } -- cgit v1.2.3 From 33c14a2a9dca43875621a02eaac99c6e321f4a3c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 12:04:11 -0500 Subject: platform/x86/amd: hfi: Rely on ACPI enumeration instead of a dummy device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_hfi_init() manually registers a platform device with platform_device_register_simple() before registering the driver. This device has no ACPI companion, so when amd_hfi_probe() runs for it the acpi_match_device() check fails and probe returns -ENODEV. The driver already advertises an ACPI match table, so the ACPI subsystem enumerates the real AMDI0104 device and binds it to the driver. The manually created device therefore serves no purpose: it never binds and lingers as an unbound platform device, wasting memory and cluttering the device tree. Drop the manual platform_device_register_simple() and the device that tracked it, and rely solely on ACPI enumeration. The CPU feature gating in amd_hfi_init() is retained so the driver is only registered on supported hardware. Signed-off-by: Mario Limonciello Reported-by: Sashiko Link: https://patch.msgid.link/20260921170411.1149960-4-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hfi/hfi.c | 9 --------- 1 file changed, 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c index 6d1d3b6c28a9..a923345dd1cf 100644 --- a/drivers/platform/x86/amd/hfi/hfi.c +++ b/drivers/platform/x86/amd/hfi/hfi.c @@ -39,8 +39,6 @@ #define AMD_HETERO_CPUID_27 0x80000027 -static struct platform_device *device; - /** * struct amd_shmem_info - Shared memory table for AMD HFI * @@ -540,12 +538,6 @@ static int __init amd_hfi_init(void) !cpu_feature_enabled(X86_FEATURE_AMD_WORKLOAD_CLASS)) return -ENODEV; - device = platform_device_register_simple(AMD_HFI_DRIVER, -1, NULL, 0); - if (IS_ERR(device)) { - pr_err("unable to register HFI platform device\n"); - return PTR_ERR(device); - } - ret = platform_driver_register(&amd_hfi_driver); if (ret) pr_err("failed to register HFI driver\n"); @@ -556,7 +548,6 @@ static int __init amd_hfi_init(void) static __exit void amd_hfi_exit(void) { platform_driver_unregister(&amd_hfi_driver); - platform_device_unregister(device); } module_init(amd_hfi_init); module_exit(amd_hfi_exit); -- cgit v1.2.3 From 3e0193dc381468589e597c9e5144eb050d36f016 Mon Sep 17 00:00:00 2001 From: Patryk Pilichowski Date: Fri, 25 Sep 2026 23:33:33 +0200 Subject: platform/x86: msi-wmi: Fix hotkeys dropped with vendor backlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit dee82409a883 ("platform/x86: msi-wmi: Reformat msi_wmi_notify()") turned the condition for reporting a hotkey into an early return, but only negated the brightness key comparisons, not the backlight check. As a result, when the driver registers its own backlight device, all WMI hotkeys are now dropped, instead of all of them being reported as before. Restore the original behavior: only drop the brightness keys, and only when brightness is handled by the ACPI video driver. This was found by code inspection. It is not tested on hardware that uses the vendor backlight; on an MSI Katana 15 B13VFK (no vendor backlight) hotkey handling is unchanged. Fixes: dee82409a883 ("platform/x86: msi-wmi: Reformat msi_wmi_notify()") Signed-off-by: Patryk Pilichowski Link: https://patch.msgid.link/20260925213333.198058-1-pat.pilichowski@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index c9db750fe5ae..05c35133a1b3 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -236,9 +236,9 @@ static void msi_wmi_notify(union acpi_object *obj, void *context) } /* Brightness is served via acpi video driver */ - if (key->type == KE_KEY && - (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || - key->code == MSI_KEY_BRIGHTNESSDOWN))) + if (key->type == KE_KEY && !backlight && + (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN)) return; pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); -- cgit v1.2.3 From 91fd0e5a4efbcbbe37f0831a5ad32d6b029dfeb4 Mon Sep 17 00:00:00 2001 From: Bruno BOI Date: Mon, 28 Sep 2026 18:06:07 +0200 Subject: platform/x86/amd/pmc: Add Thinkpad L14 Gen3 (21C5) to quirk_s2idle_bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Thinkpad L14 Gen3 with DMI product name 21C5 is affected by the same s2idle resume stall as the already listed 21C6 variant: resuming from s2idle blocks for several seconds before the kernel resumes. DMI_MATCH() performs substring matching, so a "21C5" entry also covers SKUs such as 21C5S05800. Add the 21C5 model to the existing quirk_s2idle_bug table. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222070 Link: https://bugzilla.kernel.org/show_bug.cgi?id=221273 Signed-off-by: Bruno BOI Reviewed-by: Mario Limonciello (AMD) Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260928160607.679319-1-bruno.boi@bobtopus.eu Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc-quirks.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index e662b123ecee..1592a45315cb 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -254,6 +254,15 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21C6"), } }, + /* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */ + { + .ident = "Thinkpad L14 Gen3", + .driver_data = &quirk_s2idle_bug, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21C5"), + } + }, /* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */ { .ident = "Lenovo Yoga 6 13ALC6", -- cgit v1.2.3 From c9ba86f5411730ebd333d94b9e1aa8f652dc743f Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Sep 2026 21:26:09 +0530 Subject: platform/x86/amd/pmc: clear devm-managed pointers on remove to prevent stale reuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver stores its state in a single file-scope static struct amd_pmc_dev, which is reused on every bind. Its regbase, smu_virt_addr, stb_virt_addr and fch_virt_addr members are MMIO mappings created with devm_ioremap(). On unbind the driver core unmaps these mappings, but it does not clear the pointers in the static struct. Since the struct is not freed, the members keep pointing at the now-unmapped addresses. On the next bind, the probe path re-maps a region only when its pointer is still NULL, so the stale pointer is reused and the following memcpy_fromio()/ioread32() touches freed MMIO, causing use-after-unmap crash. Clear these pointers in amd_pmc_remove() so the next probe re-maps them instead of reusing stale ones. Signed-off-by: Shyam Sundar S K Reviewed-by: Mario Limonciello (AMD) Fixes: 156ec4731cb22b ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") Link: https://patch.msgid.link/20260929155609.1712638-1-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 2bb61a65e1cb..b7ece156ee64 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -957,6 +957,10 @@ static void amd_pmc_remove(struct platform_device *pdev) pci_dev_put(dev->rdev); if (IS_ENABLED(CONFIG_AMD_MP2_STB)) amd_mp2_stb_deinit(dev); + dev->regbase = NULL; + dev->smu_virt_addr = NULL; + dev->stb_virt_addr = NULL; + dev->fch_virt_addr = NULL; } static const struct acpi_device_id amd_pmc_acpi_ids[] = { -- cgit v1.2.3 From 3d9d391f42ec3cd32e23b02c4b2443d7c3b3e9b8 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Sep 2026 21:23:17 +0530 Subject: platform/x86/amd/pmc: Add PMC driver support for AMD 1Ah Family DCh SoC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the PCI root complex device ID (0x1b20) for AMD Family 1Ah model DCh to the PMC driver. This new model shares the same PMC configuration as the existing Family 1Ah model 80h, so reuse amd_1ah_m80_cpu_info for the device data. Signed-off-by: Shyam Sundar S K Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260929155317.1710272-1-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 1 + drivers/platform/x86/amd/pmc/pmc.h | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index b7ece156ee64..ef07b24bfeba 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -198,6 +198,7 @@ static const struct pci_device_id pmc_pci_ids[] = { { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, NULL) }, { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, NULL) }, { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_1ah_m80_cpu_info) }, + { PCI_DEVICE_DATA(AMD, 1AH_MDCH_ROOT, &amd_1ah_m80_cpu_info) }, { } }; diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h index 6973a639d7e3..4633abf2cca1 100644 --- a/drivers/platform/x86/amd/pmc/pmc.h +++ b/drivers/platform/x86/amd/pmc/pmc.h @@ -194,6 +194,7 @@ void amd_mp2_stb_deinit(struct amd_pmc_dev *dev); #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 #define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b +#define PCI_DEVICE_ID_AMD_1AH_MDCH_ROOT 0x1b20 #define PCI_DEVICE_ID_AMD_MP2_STB 0x172c int amd_stb_s2d_init(struct amd_pmc_dev *dev); -- cgit v1.2.3 From 38998529f45c02edcf96b0a3ddd3c83de6d11bbe Mon Sep 17 00:00:00 2001 From: Guixiong Wei Date: Wed, 30 Sep 2026 00:31:34 +0800 Subject: platform/x86/intel-uncore-freq: Continue restoring limits on resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PM notifier iterates over every uncore frequency instance to restore user-configured MSR_UNCORE_RATIO_LIMIT values after resume. Entries that have never been configured legitimately have stored_uncore_data set to zero. The refactoring that split common and enumeration code changed the iteration from active control CPUs to all allocated instances. It also changed the skip for an invalid or unconfigured entry from continue to return, causing that entry to terminate the entire restore operation. On a multi-package or multi-die system, configuring only a later instance therefore leaves its limit unrestored after suspend, hibernation, or restore if an earlier entry is invalid or unconfigured. Use continue to skip only the current entry and restore all remaining configured instances. Fixes: dbce412a7733 ("platform/x86/intel-uncore-freq: Split common and enumeration part") Cc: stable@vger.kernel.org Tested-by: Yijia Wang Signed-off-by: Guixiong Wei Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260929163133.14326-2-weiguixiong@bytedance.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 3bc57b553eff..3602b370177e 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -211,7 +211,7 @@ static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, struct uncore_data *data = &uncore_instances[i]; if (!data || !data->valid || !data->stored_uncore_data) - return 0; + continue; wrmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, data->stored_uncore_data); -- cgit v1.2.3 From dea72765985fc49519dcfcfe6a81e291e7242da1 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Tue, 29 Sep 2026 20:16:55 +0200 Subject: platform/x86/amd/pmc: Add spurious_8042 to ASUS ROG Flow Z13 GZ302EA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ASUS ROG Flow Z13 (GZ302EA/GZ302EAC) wakes up from suspend-to-idle without any user input. With PM debug messages enabled, each unexpected wakeup shows the EC raising IRQ1 at the same time as the ACPI SCI: PM: Triggering wakeup from IRQ 9 PM: Triggering wakeup from IRQ 1 ACPI: EC: Query(0xa3) started ACPI: PM: Wakeup after ACPI Notify sync EC query 0xa3 only notifies the battery of a status change, which happens when it transitions between Charging and Full while on AC. The ACPI core correctly treats that as a non-wakeup event and cancels the SCI wakeup, but the IRQ1 wakeup remains pending and resumes the system. i8042 debugging confirms that the keyboard controller has no data at that point ("Interrupt 1, without any data"). The detachable keyboard on this device is a USB device, so disabling the i8042 wakeup source does not result in any functional loss. With serio0 wakeup disabled, the same battery event is processed during suspend-to-idle and the system stays asleep. Add the spurious_8042 quirk for it. The GZ302EAC reports sys_vendor "ASUS" while the GZ302EA reports "ASUSTeK COMPUTER INC.", so match on "ASUS" and on the "GZ302EA" board name to cover both models. Signed-off-by: Matthew Schwartz Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260929181655.26105-1-matthew.schwartz@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc-quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 1592a45315cb..fed6c51879fc 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -143,6 +143,14 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_BOARD_NAME, "RC73YA"), } }, + { + .ident = "ASUS ROG Flow Z13 GZ302EA", + .driver_data = &quirk_spurious_8042, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"), + } + }, /* https://bugzilla.kernel.org/show_bug.cgi?id=218024 */ { .ident = "V14 G4 AMN", -- cgit v1.2.3 From d21c5d267ec0ccae7e629286d3ef51224494267c Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Thu, 1 Oct 2026 10:46:36 +0300 Subject: platform: arm64: qcom-hamoa-ec: Disable the SCI IRQ during suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EC SCI events require I2C transfers to retrieve the event information. During system resume, an SCI interrupt can be delivered before the I2C controller is ready to handle transfers, resulting in failed event reads: geni_i2c a84000.i2c: error turning SE resources:-13 qcom-hamoa-ec 3-0076: Failed to read EC SCI Event: -13 Keep SCI interrupt handling disabled across system sleep so that EC events are processed only while the I2C controller is available. This prevents event reads from racing with the controller's suspend and resume transitions. Fixes: 5c44f48e91de ("platform: arm64: Add driver for EC found on Qualcomm reference devices") Reviewed-by: Anvesh Jain P Signed-off-by: Abel Vesa Link: https://patch.msgid.link/20261001-qcom-hamoa-ec-suspend-irq-v2-1-ec71a7cdec79@oss.qualcomm.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/qcom-hamoa-ec.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c index 4d2ad042a7f8..d34afd901324 100644 --- a/drivers/platform/arm64/qcom-hamoa-ec.c +++ b/drivers/platform/arm64/qcom-hamoa-ec.c @@ -331,17 +331,28 @@ static const struct thermal_cooling_device_ops qcom_ec_thermal_ops = { static int qcom_ec_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, + EC_MODERN_STANDBY_EXIT); + enable_irq(client->irq); - return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, - EC_MODERN_STANDBY_EXIT); + return ret; } static int qcom_ec_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + int ret; + + /* Keep SCI reads from racing the I2C controller's system sleep. */ + disable_irq(client->irq); + ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, + EC_MODERN_STANDBY_ENTER); + if (ret) + enable_irq(client->irq); - return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, - EC_MODERN_STANDBY_ENTER); + return ret; } static int qcom_ec_probe(struct i2c_client *client) -- cgit v1.2.3 From 3678e39c6aaeb9aa380c7357b24f7d55955543f8 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 1 Oct 2026 15:03:47 -0700 Subject: platform/x86/intel/vsec: Fix inverted walk_header() test in get_features() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit intel_vsec_walk_header() used to return a bool that was true when devices were found. It was converted to return 0 on success and a negative errno on failure, but the boolean test in intel_vsec_get_features() was left unchanged, so its meaning is now inverted. A successful walk returns 0 and leaves found set to false, while a failed walk returns an error that evaluates to true. When no other capabilities are present, intel_vsec_pci_init() then returns -ENODEV even though the auxiliary devices were created, and the driver core tears them back down. This affects platforms that have no DVSEC or VSEC capabilities and describe their features through device_data instead, which today means DG1. Test the return value for success explicitly. Fixes: a6ce8bf3c993 ("platform/x86/intel/vsec: Return real error codes from registration path") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: David E. Box Reviewed-by: Michael J. Ruhl Link: https://patch.msgid.link/20261001220351.1592138-3-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index f86149e9ba16..f6b0177b555c 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -648,7 +648,7 @@ static bool intel_vsec_get_features(struct pci_dev *pdev, found = true; if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) && - intel_vsec_walk_header(&pdev->dev, info)) + !intel_vsec_walk_header(&pdev->dev, info)) found = true; return found; -- cgit v1.2.3 From 81cdaedb97199f31d8c29733cca74cdc0ef0a0a5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 9 Oct 2026 20:18:24 -0700 Subject: platform/x86: thinkpad_acpi: fix deadlock in hotkey input open/close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit d6479c25ecef ("platform/x86: thinkpad_acpi: Use input_device_enabled()") changed hotkey_poll_setup() to acquire tpacpi_inputdev->mutex and call input_device_enabled(tpacpi_inputdev). However, the input core already holds dev->mutex when invoking a device's open() and close() callbacks. Because hotkey_inputdev_open() and hotkey_inputdev_close() call into hotkey_poll_setup(), attempting to acquire tpacpi_inputdev->mutex again results in an immediate self-deadlock. Additionally, locking tpacpi_inputdev->mutex inside hotkey_poll_setup() while holding hotkey_mutex introduces an ABBA lock ordering inversion with hotkey_inputdev_open() and hotkey_inputdev_close(), which acquire hotkey_mutex while holding tpacpi_inputdev->mutex. Finally, input_inhibit_device() calls dev->close() before setting dev->inhibited, so input_device_enabled() still returns true inside hotkey_inputdev_close(). Track whether the input device is active in a driver-local boolean protected by hotkey_mutex instead of locking tpacpi_inputdev->mutex and querying input_device_enabled(). Fixes: d6479c25ecef ("platform/x86: thinkpad_acpi: Use input_device_enabled()") Reported-by: Ali Ahmet Memiş Closes: https://lore.kernel.org/all/20260930075829.53484-1-aliamemis@disroot.org/ Reported-by: Qiling Zhu Closes: https://lore.kernel.org/all/asDVVtAXt8YVbZ3C@ThinkPad/ Reported-by: Oleksii Kryvenko Closes: https://lore.kernel.org/all/20261010002413.30007-1-krivenko@itstep.org/ Tested-by: Ali Ahmet Memiş Assisted-by: LLM Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20261010031827.3306077-1-dmitry.torokhov@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 848551099436..97d2d3c6ed04 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -1885,6 +1885,7 @@ static u32 hotkey_reserved_mask; /* events better left disabled */ static u32 hotkey_driver_mask; /* events needed by the driver */ static u32 hotkey_user_mask; /* events visible to userspace */ static u32 hotkey_acpi_mask; /* events enabled in firmware */ +static bool hotkey_input_enabled; static bool tpacpi_driver_event(const unsigned int hkey_event); static void hotkey_poll_setup(const bool may_warn); @@ -2542,10 +2543,8 @@ static void hotkey_poll_setup(const bool may_warn) lockdep_assert_held(&hotkey_mutex); - guard(mutex)(&tpacpi_inputdev->mutex); if (hotkey_poll_freq > 0 && - (poll_driver_mask || - (poll_user_mask && input_device_enabled(tpacpi_inputdev)))) { + (poll_driver_mask || (poll_user_mask && hotkey_input_enabled))) { if (!tpacpi_hotkey_task) { tpacpi_hotkey_task = kthread_run(hotkey_kthread, NULL, TPACPI_NVRAM_KTHREAD_NAME); @@ -2600,7 +2599,10 @@ static int hotkey_inputdev_open(struct input_dev *dev) switch (tpacpi_lifecycle) { case TPACPI_LIFE_INIT: case TPACPI_LIFE_RUNNING: - hotkey_poll_setup_safe(false); + scoped_guard(mutex, &hotkey_mutex) { + hotkey_input_enabled = true; + hotkey_poll_setup(false); + } return 0; case TPACPI_LIFE_EXITING: return -EBUSY; @@ -2613,10 +2615,14 @@ static int hotkey_inputdev_open(struct input_dev *dev) static void hotkey_inputdev_close(struct input_dev *dev) { + guard(mutex)(&hotkey_mutex); + + hotkey_input_enabled = false; + /* disable hotkey polling when possible */ if (tpacpi_lifecycle != TPACPI_LIFE_EXITING && !(hotkey_source_mask & hotkey_driver_mask)) - hotkey_poll_setup_safe(false); + hotkey_poll_setup(false); } /* sysfs hotkey enable ------------------------------------------------- */ -- cgit v1.2.3 From 25c9c1c69ef86b86f28218feef0a0479c42ef9e8 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 5 Oct 2026 18:31:31 +0200 Subject: platform/x86: thinkpad_acpi: Use bool for local is_thinkpad variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use bool for the local is_thinkpad variable because it only stores a boolean value. Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20261005163130.20375-2-blum@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 97d2d3c6ed04..eb3c078db8b4 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -11960,7 +11960,7 @@ static int __must_check __init get_thinkpad_model_data( static int __init probe_for_thinkpad(void) { - int is_thinkpad; + bool is_thinkpad; if (acpi_disabled) return -ENODEV; -- cgit v1.2.3 From ea40f1e9f07aab937b9a217633be7a1f66260394 Mon Sep 17 00:00:00 2001 From: Mattias Andersson <86.mattias@gmail.com> Date: Mon, 5 Oct 2026 21:29:15 +0200 Subject: platform/x86/intel/hid: Enable 5 button array on HP EliteBook x360 1030 G2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP EliteBook x360 1030 G2 is a 2-in-1 convertible with volume up and volume down buttons on the side of the chassis. These buttons are reported through the intel-hid 5 button array, but the array is not enabled by default on this model, so pressing them produces no input events at all. Loading the driver with enable_5_button_array=1 makes both buttons work as KEY_VOLUMEUP and KEY_VOLUMEDOWN. Add the model to button_array_table so the array is enabled automatically. Signed-off-by: Mattias Andersson <86.mattias@gmail.com> Link: https://patch.msgid.link/20261005192915.25392-1-86.mattias@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/hid.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index acd42e639fa4..6024bf29c4df 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -165,6 +165,13 @@ static const struct dmi_system_id button_array_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 440 G1"), }, }, + { + .ident = "HP EliteBook x360 1030 G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook x360 1030 G2"), + }, + }, { } }; -- cgit v1.2.3 From 574245e62cb09e159b4c37e1a9091c100448b5c3 Mon Sep 17 00:00:00 2001 From: Shang En Sim Date: Wed, 30 Sep 2026 22:10:11 -0500 Subject: platform/x86: hp-wmi: Fix hwmon keep-alive teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_wmi_hwmon_init() initialises keep_alive_dwork with INIT_DELAYED_WORK() and relies on hp_wmi_bios_remove() to cancel it. Nothing cancels the work if probe fails after hp_wmi_hwmon_init() has run, so the devm-managed priv can be freed while the work is still pending. The ordering is also wrong on both ends. The work is initialised only after the hwmon device is registered, so a sysfs write to pwm1_enable can schedule an uninitialised work item. On removal, the work is cancelled in .remove(), before devres unregisters the hwmon device, so a sysfs write can requeue it after the cancel. Use devm_delayed_work_autocancel() and set it up before registering the hwmon device. devres then cancels the work on every teardown path, after the hwmon sysfs interface has been removed and before priv and its mutex are released. Drop the now-unneeded cancel in hp_wmi_bios_remove() and the platform drvdata that was only used for it. Fixes: c203c59fb5de ("platform/x86: hp-wmi: implement fan keep-alive") Suggested-by: Ilpo Järvinen Signed-off-by: Shang En Sim Link: https://patch.msgid.link/20261001031012.48473-2-sim@shangen.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 4bb69914d0c8..b180452dc55a 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -2571,7 +2572,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) static void __exit hp_wmi_bios_remove(struct platform_device *device) { int i; - struct hp_wmi_hwmon_priv *priv; for (i = 0; i < rfkill2_count; i++) { rfkill_unregister(rfkill2[i].rfkill); @@ -2590,10 +2590,6 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device) rfkill_unregister(wwan_rfkill); rfkill_destroy(wwan_rfkill); } - - priv = platform_get_drvdata(device); - if (priv) - cancel_delayed_work_sync(&priv->keep_alive_dwork); } static int hp_wmi_resume_handler(struct device *device) @@ -2941,6 +2937,17 @@ static int hp_wmi_hwmon_init(void) ret = hp_wmi_setup_fan_settings(priv); if (ret) return ret; + + /* + * Set up the work before registering hwmon so that, on teardown, + * it is cancelled only after the sysfs writers that schedule it + * are gone. + */ + ret = devm_delayed_work_autocancel(dev, &priv->keep_alive_dwork, + hp_wmi_hwmon_keep_alive_handler); + if (ret) + return ret; + hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv, &chip_info, NULL); @@ -2949,8 +2956,6 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } - INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler); - platform_set_drvdata(hp_wmi_platform_dev, priv); ret = hp_wmi_apply_fan_settings(priv); if (ret) dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret); -- cgit v1.2.3 From 929433336e0a6e5014af7b8849f369712d2c8964 Mon Sep 17 00:00:00 2001 From: Shang En Sim Date: Wed, 30 Sep 2026 22:10:12 -0500 Subject: platform/x86: hp-wmi: add charge_behaviour support for Spectre 8C15/8C17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Spectre x360 16-aa0xxx (board 8C17) exposes battery charge mode control through WMI command 0x2b (GBCO/SBCO). Inhibit charge, force discharge, and restoring automatic charging all work through that command. The Spectre x360 14-eu0xxx (board 8C15) shares the same platform and the same GBCO/SBCO implementation. Unlike some other HP designs that restore automatic charging through WMI command 0x1f (SBCC), that path fails on this BIOS. Keep the quirk limited to boards 8C15 and 8C17 and drive all three behaviours through 0x2b only. Expose auto / inhibit-charge / force-discharge through the standard power_supply charge_behaviour property. Seed the reported policy from GBCO at setup because firmware retains inhibit across reboot. Cache that policy afterwards: GBCO also encodes live status, so 0x01 means actively charging and 0x02 is ambiguous between force-discharge and running on battery alone. Observed firmware mapping on this board: - write 0x00 -> auto; write 0x02 -> force-discharge; write 0x05 -> inhibit - read 0x00/0x01 -> auto (0x01 = actively charging) - read 0x02 -> force-discharge (also seen on battery alone) - read 0x04/0x05/0x06 -> inhibit Tested on HP Spectre x360 16-aa0xxx (board 8C17, BIOS F.16): - inhibit stops charging on AC; GBCO reads 0x04 - force-discharge discharges on AC; GBCO reads 0x02 - restore-auto via 0x2b mode 0 resumes charging; GBCO reads 0x01 - inhibit persists across reboot (status Not charging, GBCO 0x04) Board 8C15 was reported working by Alain Cousinie. Suggested-by: Alain Cousinie Signed-off-by: Shang En Sim Link: https://patch.msgid.link/20261001031012.48473-3-sim@shangen.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 246 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index b180452dc55a..1dec4565bbdc 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,8 @@ #include #include +#include + MODULE_AUTHOR("Matthew Garrett "); MODULE_DESCRIPTION("HP laptop WMI driver"); MODULE_LICENSE("GPL"); @@ -429,6 +432,7 @@ enum hp_wmi_commandtype { HPWMI_FEATURE2_QUERY = 0x0d, HPWMI_WIRELESS2_QUERY = 0x1b, HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_BATTERY_CHARGE_MODE = 0x2b, HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, HPWMI_GRAPHICS_MUX_QUERY = 0x52, @@ -498,6 +502,30 @@ enum hp_wireless2_bits { #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW) #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +#define HPWMI_CHARGE_BEHAVIOURS \ + (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \ + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)) + +/* + * SBCO reads the requested mode from bits 15:8 of the input value. + * GBCO returns literal status values in bits 7:0. + */ +#define HPWMI_CHARGE_MODE_MASK GENMASK(15, 8) +#define HPWMI_CHARGE_MODE_AUTO 0x00 +#define HPWMI_CHARGE_MODE_FORCE_DISCHARGE 0x02 +#define HPWMI_CHARGE_MODE_INHIBIT 0x05 + +#define HPWMI_CHARGE_MODE_READ_MASK GENMASK(7, 0) +#define HPWMI_CHARGE_MODE_READ_AUTO 0x00 +#define HPWMI_CHARGE_MODE_READ_CHARGING 0x01 +#define HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE 0x02 +#define HPWMI_CHARGE_MODE_READ_INHIBIT 0x04 +#define HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM 0x05 +#define HPWMI_CHARGE_MODE_READ_INHIBIT_EXT 0x06 +#endif + struct bios_rfkill2_device_state { u8 radio_type; u8 bus_type; @@ -584,6 +612,26 @@ static const char * const tablet_chassis_types[] = { "32" /* Detachable */ }; +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +static const struct dmi_system_id hp_wmi_charge_control_quirks[] __initconst = { + { + /* HP Spectre x360 16-aa0xxx */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8C17"), + }, + }, + { + /* HP Spectre x360 14-eu0xxx */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8C15"), + }, + }, + {} +}; +#endif + #define DEVICE_MODE_TABLET 0x06 #define CPU_FAN 0 @@ -814,6 +862,200 @@ static int hp_wmi_read_int(int query) return val; } +#if IS_REACHABLE(CONFIG_ACPI_BATTERY) +/* Protects hp_wmi_charge_behaviour */ +static DEFINE_MUTEX(hp_wmi_charge_lock); +static enum power_supply_charge_behaviour hp_wmi_charge_behaviour = + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + +static int hp_wmi_charge_mode_to_behaviour(int mode, + enum power_supply_charge_behaviour *behaviour) +{ + switch (mode) { + case HPWMI_CHARGE_MODE_READ_AUTO: + case HPWMI_CHARGE_MODE_READ_CHARGING: + /* + * GBCO 0x01 reports active charging, rather than a separate + * charge policy. Treat it as automatic charging. + */ + *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + return 0; + case HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE: + /* + * Firmware also returns 0x02 while running on battery alone. + * Treat it as force-discharge so a persisted AC policy is not + * lost across reboot. + */ + *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + return 0; + case HPWMI_CHARGE_MODE_READ_INHIBIT: + case HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM: + case HPWMI_CHARGE_MODE_READ_INHIBIT_EXT: + *behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + return 0; + default: + return -EINVAL; + } +} + +static int hp_wmi_charge_mode_read(void) +{ + int val = 0, ret; + + ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_READ, + &val, zero_if_sup(val), sizeof(val)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return FIELD_GET(HPWMI_CHARGE_MODE_READ_MASK, val); +} + +static int hp_wmi_charge_mode_write(u32 mode) +{ + int ret; + + mode = FIELD_PREP(HPWMI_CHARGE_MODE_MASK, mode); + ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_WRITE, + &mode, sizeof(mode), 0); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return 0; +} + +static int hp_wmi_charge_set_behaviour(enum power_supply_charge_behaviour behaviour) +{ + int ret; + + guard(mutex)(&hp_wmi_charge_lock); + + switch (behaviour) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_AUTO); + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_INHIBIT); + break; + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_FORCE_DISCHARGE); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + hp_wmi_charge_behaviour = behaviour; + + return 0; +} + +static int hp_wmi_charge_get_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) + return -EINVAL; + + guard(mutex)(&hp_wmi_charge_lock); + val->intval = hp_wmi_charge_behaviour; + + return 0; +} + +static int hp_wmi_charge_set_property(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR) + return -EINVAL; + + return hp_wmi_charge_set_behaviour(val->intval); +} + +static int hp_wmi_charge_property_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR; +} + +static const enum power_supply_property hp_wmi_charge_properties[] = { + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +static const struct power_supply_ext hp_wmi_charge_extension = { + .name = "hp-wmi-charge-control", + .properties = hp_wmi_charge_properties, + .num_properties = ARRAY_SIZE(hp_wmi_charge_properties), + .charge_behaviours = HPWMI_CHARGE_BEHAVIOURS, + .get_property = hp_wmi_charge_get_property, + .set_property = hp_wmi_charge_set_property, + .property_is_writeable = hp_wmi_charge_property_is_writeable, +}; + +static int hp_wmi_charge_add_battery(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + return power_supply_register_extension(battery, &hp_wmi_charge_extension, + &hp_wmi_platform_dev->dev, NULL); +} + +static int hp_wmi_charge_remove_battery(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &hp_wmi_charge_extension); + return 0; +} + +static struct acpi_battery_hook hp_wmi_charge_battery_hook = { + .name = "HP WMI charge control", + .add_battery = hp_wmi_charge_add_battery, + .remove_battery = hp_wmi_charge_remove_battery, +}; + +static int __init hp_wmi_charge_control_setup(struct device *dev) +{ + enum power_supply_charge_behaviour behaviour; + int ret; + + if (!dmi_check_system(hp_wmi_charge_control_quirks)) + return 0; + + /* + * Firmware retains inhibit across reboot, so seed from GBCO rather + * than assuming auto. + */ + ret = hp_wmi_charge_mode_read(); + if (ret < 0) + return 0; + + ret = hp_wmi_charge_mode_to_behaviour(ret, &behaviour); + if (ret) + return 0; + + scoped_guard(mutex, &hp_wmi_charge_lock) + hp_wmi_charge_behaviour = behaviour; + + return devm_battery_hook_register(dev, &hp_wmi_charge_battery_hook); +} +#else +static int __init hp_wmi_charge_control_setup(struct device *dev) +{ + return 0; +} +#endif + static int hp_wmi_get_dock_state(void) { int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); @@ -2564,6 +2806,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) if (err < 0) return err; + err = hp_wmi_charge_control_setup(&device->dev); + if (err) + return err; + thermal_profile_setup(device); return 0; -- cgit v1.2.3 From e93536b1fcf775356b06ddc845461e64d4c87313 Mon Sep 17 00:00:00 2001 From: Gopikrishna Garmidi Date: Tue, 6 Oct 2026 02:50:29 -0700 Subject: platform/surface: aggregator_registry: Add Surface Laptop 8th Edition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Mahua-based Surface Laptop 13.8" 8th Edition exposes the same SAM client devices as the Surface Laptop 7: keyboard, fan and platform profile. Battery and AC status come from pmic-glink here as well, so the Surface Laptop 7 node group can be reused as is. Signed-off-by: Gopikrishna Garmidi Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20261006-mahua-surface-laptop-v1-3-406fc042162a@oss.qualcomm.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_registry.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 061d4a567a06..cf3ae252b1c4 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -517,6 +517,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { /* Surface Laptop 7 */ { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, + /* Surface Laptop 8th Edition (13.8") */ + { .compatible = "microsoft,surface-laptop-8-13in", (void *)ssam_node_group_sl7 }, { }, }; -- cgit v1.2.3 From 2cd10a35f3f09c49698956d32c6374fbdc03245d Mon Sep 17 00:00:00 2001 From: Shuwen Bao Date: Wed, 7 Oct 2026 11:08:36 +0800 Subject: platform/x86: hp-wmi: add support for OMEN 16-am2xxx boards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP HyperX OMEN 16-am2xxx (DMI board 8F1C, 26C1 platform cycle, Arrow Lake-HXR) is listed in the OMEN Command Center device capability table with DisplayName "HyperX OMEN 16" and the "PerformanceControl" feature, but is missing from the kernel's hp_wmi_feature_boards[] list. Add 8F1C to the hp_wmi_feature_boards[] list, using omen_v1_legacy_board_params as driver_data. Tested on a HyperX OMEN 16-am2xxx (kernel 7.3.0-rc6): after loading the patched hp-wmi module, the platform_profile interface is exposed, and switching to the performance profile raises the CPU from a ~2.9 GHz (P-core) / 2.6 GHz (E-core) cap to ~5 GHz / ~3.5 GHz under a multi-thread load (stress-ng). The GPU cap is raised to ~114W, tested with gpu_burn. Suggested-by: Krishna Chomal Signed-off-by: Shuwen Bao Link: https://patch.msgid.link/SI2PR04MB589508E55CCCD8E26E53E4DBA2942@SI2PR04MB5895.apcprd04.prod.outlook.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 1dec4565bbdc..dded3dfff1b3 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -371,6 +371,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E41") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8F1C") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, {}, }; -- cgit v1.2.3 From 7a267229b0bc162a465f6b95afdcd6adc29acf10 Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Wed, 7 Oct 2026 10:01:47 +0200 Subject: platform/x86/tuxedo: Don't use device driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WMI driver for TUXEDO Sirius devices used to rely on the device driver data through dev_set_drvdata even though it is only a virtual low level HID driver. For this purpose, it is better to use the driver_data of the hid_device struct to avoid interfering with high level device drivers. Reviewed-by: Werner Sembach Signed-off-by: Aaron Erhardt Link: https://patch.msgid.link/20261007080202.81694-2-aer@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/tuxedo/nb04/wmi_ab.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 32d7756022c2..72205de72256 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -448,7 +448,7 @@ static int tux_ll_start(struct hid_device *hdev) } driver_data->next_lamp_id = 0; - dev_set_drvdata(&hdev->dev, driver_data); + hdev->driver_data = driver_data; return ret; } @@ -485,7 +485,7 @@ struct __packed lamp_array_attributes_report_t { static int handle_lamp_array_attributes_report(struct hid_device *hdev, struct lamp_array_attributes_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; rep->lamp_count = driver_data->lamp_count; rep->bounding_box_width_in_micrometers = 368000; @@ -510,7 +510,7 @@ struct __packed lamp_attributes_request_report_t { static int handle_lamp_attributes_request_report(struct hid_device *hdev, struct lamp_attributes_request_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; if (rep->lamp_id < driver_data->lamp_count) driver_data->next_lamp_id = rep->lamp_id; @@ -539,7 +539,7 @@ struct __packed lamp_attributes_response_report_t { static int handle_lamp_attributes_response_report(struct hid_device *hdev, struct lamp_attributes_response_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; u16 lamp_id = driver_data->next_lamp_id; rep->lamp_id = lamp_id; @@ -598,7 +598,7 @@ struct __packed lamp_multi_update_report_t { static int handle_lamp_multi_update_report(struct hid_device *hdev, struct lamp_multi_update_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in; struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j; struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); @@ -683,7 +683,7 @@ struct __packed lamp_range_update_report_t { static int handle_lamp_range_update_report(struct hid_device *hdev, struct lamp_range_update_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; struct lamp_multi_update_report_t lamp_multi_update_report = { .report_id = LAMP_MULTI_UPDATE_REPORT_ID, }; -- cgit v1.2.3 From b6bdd22c3b92abe6f192ab533b843b52346a7640 Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Wed, 7 Oct 2026 10:01:48 +0200 Subject: platform/x86/tuxedo: Set HID report ID on success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While not strictly necessary due to the synchronous handling of HID requests, it is better to set the ID of the HID report before returning the buffer. This also better complies with the HID spec and avoids problems with userspace tools expecting a report ID. Reviewed-by: Werner Sembach Signed-off-by: Aaron Erhardt Link: https://patch.msgid.link/20261007080202.81694-3-aer@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/tuxedo/nb04/wmi_ab.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 72205de72256..11babc7c7767 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -766,6 +766,8 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, size_t len, unsigned char rtype, int reqtype) { + int ret = -EINVAL; + if (rtype != HID_FEATURE_REPORT) return -EINVAL; @@ -775,13 +777,15 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: if (len != sizeof(struct lamp_array_attributes_report_t)) return -EINVAL; - return handle_lamp_array_attributes_report(hdev, + ret = handle_lamp_array_attributes_report(hdev, (struct lamp_array_attributes_report_t *)buf); + break; case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: if (len != sizeof(struct lamp_attributes_response_report_t)) return -EINVAL; - return handle_lamp_attributes_response_report(hdev, + ret = handle_lamp_attributes_response_report(hdev, (struct lamp_attributes_response_report_t *)buf); + break; } break; case HID_REQ_SET_REPORT: @@ -789,28 +793,36 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: if (len != sizeof(struct lamp_attributes_request_report_t)) return -EINVAL; - return handle_lamp_attributes_request_report(hdev, + ret = handle_lamp_attributes_request_report(hdev, (struct lamp_attributes_request_report_t *)buf); + break; case LAMP_MULTI_UPDATE_REPORT_ID: if (len != sizeof(struct lamp_multi_update_report_t)) return -EINVAL; - return handle_lamp_multi_update_report(hdev, + ret = handle_lamp_multi_update_report(hdev, (struct lamp_multi_update_report_t *)buf); + break; case LAMP_RANGE_UPDATE_REPORT_ID: if (len != sizeof(struct lamp_range_update_report_t)) return -EINVAL; - return handle_lamp_range_update_report(hdev, + ret = handle_lamp_range_update_report(hdev, (struct lamp_range_update_report_t *)buf); + break; case LAMP_ARRAY_CONTROL_REPORT_ID: if (len != sizeof(struct lamp_array_control_report_t)) return -EINVAL; - return handle_lamp_array_control_report(hdev, + ret = handle_lamp_array_control_report(hdev, (struct lamp_array_control_report_t *)buf); + break; } break; } - return -EINVAL; + /* Set report number on success */ + if (ret > 0) + buf[0] = reportnum; + + return ret; } static const struct hid_ll_driver tux_ll_driver = { -- cgit v1.2.3 From ef6ff9aebf5b8797590e4fa586114a2688106a4d Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Wed, 7 Oct 2026 10:01:49 +0200 Subject: platform/x86/tuxedo: Use intensity according to HID spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, this driver assumed incorrectly that the HID spec requires an 8-bit intensity value to be honored. The spec actually allows multiple intensities for RGB lamps, but suggest to rather set brightness through the individual channels, which is also what the Microsoft MacroPad reference implementation does. Accordingly, this commit simplifies intensity handling by offering only two intensity values for turning LEDs on and off. All other color values are submitted through the color channels individually, thus avoiding duplicated handling of brightness. Additionally, the incorrect comments explaining the deviation from the MacroPad reference implementation were removed. Reviewed-by: Werner Sembach Signed-off-by: Aaron Erhardt Link: https://patch.msgid.link/20261007080202.81694-4-aer@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/tuxedo/nb04/wmi_ab.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 11babc7c7767..8f1ffca0430d 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -553,7 +553,7 @@ static int handle_lamp_attributes_response_report(struct hid_device *hdev, rep->red_level_count = 0xff; rep->green_level_count = 0xff; rep->blue_level_count = 0xff; - rep->intensity_level_count = 0xff; + rep->intensity_level_count = 0x1; rep->is_programmable = 1; if (driver_data->kbl_map[lamp_id].code <= 0xe8) { @@ -640,22 +640,23 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, j + 1; rgb_configs_j->key_id = key_id; /* - * While this driver respects update_channel.intensity - * according to "HID Usage Tables v1.5" also on RGB - * leds, the Microsoft MacroPad reference implementation + * This driver uses update_channel.intensity according to + * "Color Attributes Examples" in "HID Usage Tables v1.7". + * Only two intensity values are allowed for turning LEDs + * on or off, while color and brightness can be controlled + * through the RGB values. This is also identical to the + * Microsoft MacroPad reference implementation * (https://github.com/microsoft/RP2040MacropadHidSample - * 1d6c3ad) does not and ignores it. If it turns out - * that Windows writes intensity = 0 for RGB leds - * instead of intensity = 255, this driver should also - * ignore the update_channel.intensity. + * 1d6c3ad). */ - intensity_i = rep->update_channels[i].intensity; + intensity_i = min(1, rep->update_channels[i].intensity); red_i = rep->update_channels[i].red; green_i = rep->update_channels[i].green; blue_i = rep->update_channels[i].blue; - rgb_configs_j->red = red_i * intensity_i / 0xff; - rgb_configs_j->green = green_i * intensity_i / 0xff; - rgb_configs_j->blue = blue_i * intensity_i / 0xff; + + rgb_configs_j->red = red_i * intensity_i; + rgb_configs_j->green = green_i * intensity_i; + rgb_configs_j->blue = blue_i * intensity_i; break; } -- cgit v1.2.3 From 5c9c54dfa28c9a47c9d81322d16585f9a64d4c69 Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Wed, 7 Oct 2026 10:01:50 +0200 Subject: platform/x86/tuxedo: Fix keyboard LED map ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LED mappings in sirius_16_[iso|ansii]_kbl_map did contain a flipped line that caused LEDs to turn on in the wrong order when counting up the Lamp ID. Fixes: cfd84b3f419b ("platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices") Cc: stable@vger.kernel.org Reviewed-by: Werner Sembach Signed-off-by: Aaron Erhardt Link: https://patch.msgid.link/20261007080202.81694-5-aer@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/tuxedo/nb04/wmi_ab.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 8f1ffca0430d..2b985b030197 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -232,8 +232,8 @@ static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = { { 0x2e, { 246000, 67500, 5250 } }, { 0x2a, { 269500, 67500, 5250 } }, { 0x53, { 294500, 67500, 5250 } }, - { 0x55, { 311200, 67500, 5250 } }, - { 0x54, { 327900, 67500, 5250 } }, + { 0x54, { 311200, 67500, 5250 } }, + { 0x55, { 327900, 67500, 5250 } }, { 0x56, { 344600, 67500, 5250 } }, { 0x2b, { 31000, 85500, 5500 } }, { 0x14, { 51500, 85500, 5500 } }, @@ -337,8 +337,8 @@ static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = { { 0x2e, { 246000, 67500, 5250 } }, { 0x2a, { 269500, 67500, 5250 } }, { 0x53, { 294500, 67500, 5250 } }, - { 0x55, { 311200, 67500, 5250 } }, - { 0x54, { 327900, 67500, 5250 } }, + { 0x54, { 311200, 67500, 5250 } }, + { 0x55, { 327900, 67500, 5250 } }, { 0x56, { 344600, 67500, 5250 } }, { 0x2b, { 31000, 85500, 5500 } }, { 0x14, { 51500, 85500, 5500 } }, -- cgit v1.2.3 From bb47fafd94656a0fd2ea6cb636b0fe3fd141e459 Mon Sep 17 00:00:00 2001 From: Aaron Erhardt Date: Wed, 7 Oct 2026 10:01:51 +0200 Subject: platform/x86/tuxedo: Update and extend documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove an incorrect comment about the Microsoft MacroPad reference implementation allegedly deviating from the spec and add more information about the module and some other minor improvements. Reviewed-by: Werner Sembach Signed-off-by: Aaron Erhardt Link: https://patch.msgid.link/20261007080202.81694-6-aer@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/tuxedo/nb04/wmi_ab.c | 51 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 2b985b030197..3e5ba524fe58 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -1,9 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * This driver implements the WMI AB device found on TUXEDO notebooks with board - * vendor NB04. + * vendor NB04. This enables keyboard backlight control via a virtual HID + * LampArray device. + * + * The device will be available through the regular HID interfaces, such as + * hidraw and can be used by any userspace program that implements the HID + * LampArray standard. * * Copyright (C) 2024-2025 Werner Sembach + * Copyright (C) 2026 Aaron Erhardt */ #include @@ -488,12 +494,14 @@ static int handle_lamp_array_attributes_report(struct hid_device *hdev, struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; rep->lamp_count = driver_data->lamp_count; + + // Physical dimensions of the Sirius 16 keyboard rep->bounding_box_width_in_micrometers = 368000; rep->bounding_box_height_in_micrometers = 266000; rep->bounding_box_depth_in_micrometers = 30000; /* * LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of - * "HID Usage Tables v1.5" + * "HID Usage Tables v1.7" */ rep->lamp_array_kind = 1; // Some guessed value for interval microseconds @@ -547,7 +555,7 @@ static int handle_lamp_attributes_response_report(struct hid_device *hdev, rep->update_latency_in_microseconds = 100; /* * LampPurposeControl, see "26.3.1 LampPurposes Flags" of - * "HID Usage Tables v1.5" + * "HID Usage Tables v1.7" */ rep->lamp_purpose = 1; rep->red_level_count = 0xff; @@ -560,8 +568,8 @@ static int handle_lamp_attributes_response_report(struct hid_device *hdev, rep->input_binding = driver_data->kbl_map[lamp_id].code; } else { /* - * Everything bigger is reserved/undefined, see - * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5" + * Everything bigger than 0xe8 is reserved/undefined, see + * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.7" * and should return 0, see "26.8.3 Lamp Attributes" of the same * document. */ @@ -606,10 +614,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i; int ret; - /* - * Catching misformatted lamp_multi_update_report and fail silently - * according to "HID Usage Tables v1.5" - */ + // Catch bad reports and fail silently according to "HID Usage Tables v1.7" for (unsigned int i = 0; i < rep->lamp_count; ++i) { if (rep->lamp_id[i] > driver_data->lamp_count) { hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n"); @@ -624,6 +629,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, } } + // Fill kbl_set_multiple_keys_in update buffer for (unsigned int i = 0; i < rep->lamp_count; ++i) { key_id = driver_data->kbl_map[rep->lamp_id[i]].code; @@ -632,6 +638,8 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, ++j) { rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j]; key_id_j = rgb_configs_j->key_id; + + // Search for existing or empty entry if (key_id_j != 0x00 && key_id_j != key_id) continue; @@ -691,10 +699,7 @@ static int handle_lamp_range_update_report(struct hid_device *hdev, struct lamp_rgbi_tuple_t *update_channels_j; int ret; - /* - * Catching misformatted lamp_range_update_report and fail silently - * according to "HID Usage Tables v1.5" - */ + // Catch bad reports and fail silently according to "HID Usage Tables v1.7" if (rep->lamp_id_start > rep->lamp_id_end) { hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); return sizeof(*rep); @@ -706,8 +711,8 @@ static int handle_lamp_range_update_report(struct hid_device *hdev, } /* - * Break handle_lamp_range_update_report call down to multiple - * handle_lamp_multi_update_report calls to easily ensure that mixing + * Break handle_lamp_range_update_report call down into multiple + * handle_lamp_multi_update_report calls to ensure that mixing * handle_lamp_range_update_report and handle_lamp_multi_update_report * does not break things. */ @@ -750,15 +755,12 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu struct lamp_array_control_report_t *rep) { /* - * The keyboards firmware doesn't have any built in controls and the - * built in effects are not implemented so this is a NOOP. - * According to the HID Documentation (HID Usage Tables v1.5) this + * The keyboard's firmware doesn't have any built-in controls and the + * built-in effects are not implemented so this is a NOOP. + * According to the HID Documentation (HID Usage Tables v1.7) this * function is optional and can be removed from the HID Report * Descriptor, but it should first be confirmed that userspace respects - * this possibility too. The Microsoft MacroPad reference implementation - * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) - * already deviates from the spec at another point, see - * handle_lamp_*_update_report. + * this possibility too. */ return sizeof(*rep); @@ -894,8 +896,8 @@ static struct wmi_driver tuxedo_nb04_wmi_tux_driver = { }; /* - * We don't know if the WMI API is stable and how unique the GUID is for this - * ODM. To be on the safe side we therefore only run this driver on tested + * We don't know whether the WMI API is stable and how unique the GUID is for + * this ODM. To be on the safe side we therefore only run this driver on tested * devices defined by this list. */ static const struct dmi_system_id tested_devices_dmi_table[] __initconst = { @@ -933,4 +935,5 @@ module_exit(tuxedo_nb04_wmi_tux_exit); MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices"); MODULE_AUTHOR("Werner Sembach "); +MODULE_AUTHOR("Aaron Erhardt "); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 176b38b84ae5e075850b3ebb25f906d66f95a46d Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:03 +0500 Subject: platform/x86: hp-bioscfg: fix OOB reads in hp_get_string_from_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The escape prescan loop uses 'size' as both its bound and its accumulator, so each escape character found extends the loop and reads past the end of src[]. Use the original u16 count as the loop bound. Also include the 2-byte length prefix in the bounds check, pass the u16 count instead of the byte count to utf16s_to_utf8s(), and advance the buffer by the bytes actually consumed instead of the escape-inflated count. Tested on HP EliteBook 840 G2 (Fedora 44, Linux 7.2.7). Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20261007134711.473857-2-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index dd51a4017d8e..41690c4c46a4 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -54,8 +54,8 @@ int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer) int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size) { u16 *src = (u16 *)*buffer; + u16 orig_size; u16 src_size; - u16 size; int i; int conv_dst_size; @@ -63,17 +63,21 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ if (*buffer_size < sizeof(u16)) return -EINVAL; - src_size = *(src++); - /* size value in u16 chars */ - size = src_size / sizeof(u16); + src_size = *src; - /* Ensure there is enough space remaining to read and convert - * the string + /* + * Ensure there is enough space remaining for the length prefix + * just read plus the string data it describes. */ - if (*buffer_size < src_size) + if (*buffer_size < sizeof(u16) + src_size) return -EINVAL; - for (i = 0; i < size; i++) + src++; + /* size value in u16 chars */ + orig_size = src_size / sizeof(u16); + size = orig_size; + + for (i = 0; i < orig_size; i++) if (src[i] == '\\' || src[i] == '\r' || src[i] == '\n' || @@ -91,7 +95,7 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ /* * convert from UTF-16 unicode to ASCII */ - utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size); + utf16s_to_utf8s(src, orig_size, UTF16_HOST_ENDIAN, dst, conv_dst_size); dst[conv_dst_size] = 0; for (i = 0; i < conv_dst_size; i++) { @@ -117,8 +121,8 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ src++; } - *buffer = (u8 *)src; - *buffer_size -= size * sizeof(u16); + *buffer += sizeof(u16) + src_size; + *buffer_size -= sizeof(u16) + src_size; return size; } -- cgit v1.2.3 From 6b8d2374b32c35efa2a431b3cd2fe346f8917dcc Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:04 +0500 Subject: platform/x86: hp-bioscfg: fix non-ASCII truncation in hp_get_string_from_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit utf16s_to_utf8s() writes into dst, but the loop that follows overwrites dst from the raw UTF-16 code units with truncating casts, so any character above U+007F is mangled. For example, U+00E9 is stored as 0xe9 instead of 0xc3 0xa9. Convert into a scratch buffer first, then escape '\\', '\r', '\n' and '\t' into dst with string_escape_mem(). Quotes are not escaped, so the existing strreplace() still handles them. Tested on HP EliteBook 840 G2 (Fedora 44, Linux 7.2.7). Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Suggested-by: Ilpo Järvinen Signed-off-by: Muhammad Bilal Link: https://lore.kernel.org/all/20261007134711.473857-1-meatuni001@gmail.com/ Link: https://patch.msgid.link/20261007134711.473857-3-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 62 +++++++--------------------- 1 file changed, 14 insertions(+), 48 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 41690c4c46a4..876fc35d035b 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "bioscfg.h" #include "../../firmware_attributes_class.h" @@ -53,12 +53,12 @@ int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer) int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size) { + char utf8_buf[MAX_BUFF_SIZE]; u16 *src = (u16 *)*buffer; + int escaped_len; u16 orig_size; u16 src_size; - u16 size; - int i; - int conv_dst_size; + int utf8_len; if (*buffer_size < sizeof(u16)) return -EINVAL; @@ -75,56 +75,22 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ src++; /* size value in u16 chars */ orig_size = src_size / sizeof(u16); - size = orig_size; - - for (i = 0; i < orig_size; i++) - if (src[i] == '\\' || - src[i] == '\r' || - src[i] == '\n' || - src[i] == '\t') - size++; - /* - * Conversion is limited to destination string max number of - * bytes. - */ - conv_dst_size = size; - if (size >= dst_size) - conv_dst_size = dst_size - 1; + utf8_len = utf16s_to_utf8s(src, orig_size, UTF16_HOST_ENDIAN, + utf8_buf, sizeof(utf8_buf)); - /* - * convert from UTF-16 unicode to ASCII - */ - utf16s_to_utf8s(src, orig_size, UTF16_HOST_ENDIAN, dst, conv_dst_size); - dst[conv_dst_size] = 0; - - for (i = 0; i < conv_dst_size; i++) { - if (*src == '\\' || - *src == '\r' || - *src == '\n' || - *src == '\t') { - dst[i++] = '\\'; - if (i == conv_dst_size) - break; - } - - if (*src == '\r') - dst[i] = 'r'; - else if (*src == '\n') - dst[i] = 'n'; - else if (*src == '\t') - dst[i] = 't'; - else if (*src == '"') - dst[i] = '\''; - else - dst[i] = *src; - src++; - } + escaped_len = string_escape_mem(utf8_buf, utf8_len, dst, dst_size - 1, + ESCAPE_SPACE | ESCAPE_SPECIAL, + "\\\r\n\t"); + if (escaped_len > dst_size - 1) + escaped_len = dst_size - 1; + dst[escaped_len] = '\0'; + strreplace(dst, '"', '\''); *buffer += sizeof(u16) + src_size; *buffer_size -= sizeof(u16) + src_size; - return size; + return escaped_len; } int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, -- cgit v1.2.3 From a4f19c589b21e18567d3c0ac7b9294f81e29b1de Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:05 +0500 Subject: platform/x86: hp-bioscfg: return -E2BIG from validate_password_input() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validate_password_input() returns the positive INVALID_BIOS_AUTH on rejection. store_password_instance() skips the store on nonzero ret, but its final "return ret < 0 ? ret : count" treats the positive value as success, so userspace sees count instead of an error. Return -E2BIG for an invalid password length. Tested on HP EliteBook 840 G2 (Fedora 44, Linux 7.2.7): writing 999 bytes to current_password returned 999 before this change. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://lore.kernel.org/all/20261007134711.473857-1-meatuni001@gmail.com/ Link: https://patch.msgid.link/20261007134711.473857-4-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 95fa0aeba920..58a66b7c8454 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -70,12 +70,13 @@ static int validate_password_input(int instance_id, const char *buf) length--; if (length > MAX_PASSWD_SIZE) - return INVALID_BIOS_AUTH; + return -E2BIG; if (password_data->min_password_length > length || password_data->max_password_length < length) - return INVALID_BIOS_AUTH; - return SUCCESS; + return -E2BIG; + + return 0; } ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password); -- cgit v1.2.3 From acada379d5c4dd3a86d48fc69802e769eff29f7e Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:06 +0500 Subject: platform/x86: hp-bioscfg: allow clearing current_password MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ABI documents echo "" > current_password as the way to end a session, but validate_password_input() rejects an empty string when min_password_length is nonzero, so the password stays cached. Skip the length check for an empty current_password so the write clears it. Compile tested only. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://lore.kernel.org/all/20261007134711.473857-1-meatuni001@gmail.com/ Link: https://patch.msgid.link/20261007134711.473857-5-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 58a66b7c8454..b8bf2f82199f 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -60,7 +60,8 @@ int hp_get_password_instance_for_type(const char *name) return -EINVAL; } -static int validate_password_input(int instance_id, const char *buf) +static int validate_password_input(int instance_id, const char *buf, + bool is_current) { int length; struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; @@ -69,6 +70,10 @@ static int validate_password_input(int instance_id, const char *buf) if (length > 0 && buf[length - 1] == '\n') length--; + /* An empty current_password clears the session password */ + if (is_current && !length) + return 0; + if (length > MAX_PASSWD_SIZE) return -E2BIG; @@ -97,7 +102,7 @@ static int store_password_instance(struct kobject *kobj, const char *buf, id = get_password_instance_id(kobj); if (id >= 0) - ret = validate_password_input(id, buf_cp); + ret = validate_password_input(id, buf_cp, is_current); } if (!ret) { -- cgit v1.2.3 From 0c22178524864af85ea380be4b2d36078c0edc9c Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:07 +0500 Subject: platform/x86: hp-bioscfg: fix off-by-one in password length check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit current_password and new_password hold MAX_PASSWD_SIZE bytes including the NUL, but validate_password_input() accepts a password of exactly MAX_PASSWD_SIZE characters when the firmware limits allow it. strscpy() then truncates it and fails with -E2BIG, which store_password_instance() ignores, so the write reports success with a truncated password stored. For example, with a max_password_length of 64 or more, writing 64 characters succeeds but only 63 are stored. Reject lengths of MAX_PASSWD_SIZE or more. Compile tested only. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Signed-off-by: Muhammad Bilal Link: https://lore.kernel.org/all/20261007134711.473857-1-meatuni001@gmail.com/ Link: https://patch.msgid.link/20261007134711.473857-6-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index b8bf2f82199f..e7921d9295e0 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -74,7 +74,7 @@ static int validate_password_input(int instance_id, const char *buf, if (is_current && !length) return 0; - if (length > MAX_PASSWD_SIZE) + if (length >= MAX_PASSWD_SIZE) return -E2BIG; if (password_data->min_password_length > length || -- cgit v1.2.3 From 35fb43f1576e1352077afc33a535007e0568480c Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:09 +0500 Subject: platform/x86: hp-bioscfg: validate SPM state returned by firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit status_show() indexes the 3-entry spm_state_types[] with the state byte returned by firmware. update_spm_state() stores the same byte, which key_mechanism_show() uses to index the 3-entry spm_mechanism_types[]. Neither is range checked, so a state above 2 makes the world-readable status and key_mechanism files print a string from a pointer read past the array. Return -EIO for an out of range state. Compile tested only. Fixes: b2715aa2e135 ("platform/x86: hp-bioscfg: spmobj-attributes") Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20261007134711.473857-8-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index abf8ce20fe15..2486f92655e3 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -6,6 +6,7 @@ * Copyright (c) 2022 HP Development Company, L.P. */ +#include #include "bioscfg.h" static const char * const spm_state_types[] = { @@ -115,6 +116,9 @@ static ssize_t update_spm_state(void) if (ret < 0) return ret; + if (data.state >= ARRAY_SIZE(spm_mechanism_types)) + return -EIO; + bioscfg_drv.spm_data.mechanism = data.state; if (bioscfg_drv.spm_data.mechanism) bioscfg_drv.spm_data.is_enabled = 1; @@ -150,6 +154,9 @@ static ssize_t status_show(struct kobject *kobj, struct kobj_attribute if (ret < 0) return ret; + if (data.state >= ARRAY_SIZE(spm_state_types)) + return -EIO; + /* * 'status' is a read-only file that returns ASCII text in * JSON format reporting the status information. -- cgit v1.2.3 From f2769668959f6411ac69e83080a240f8a8785e05 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:10 +0500 Subject: platform/x86: hp-bioscfg: NUL-terminate the SPM auth token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auth_token_store() copies the token with kmemdup(), which does not NUL terminate it, but hp_calculate_security_buffer() and hp_populate_security_buffer() use it as a C string and read past the allocation. A lone newline (echo > auth_token) is worse: kmemdup() of 0 bytes returns ZERO_SIZE_PTR, which passes the NULL checks, so a later attribute write passes it to strlen(). A userspace replica of the two helpers under ASan reports a heap buffer overflow in hp_calculate_security_buffer() for the unterminated token and a fault at address 0x10 for the lone newline, and is clean with this change. The token is a string, so copy it with kstrndup(), which always NUL terminates and allocates at least one byte. Fixes: b2715aa2e135 ("platform/x86: hp-bioscfg: spmobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20261007134711.473857-9-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index 2486f92655e3..343b2356943e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -314,7 +314,7 @@ static ssize_t auth_token_store(struct kobject *kobj, length--; /* allocate space and copy current auth token */ - bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL); + bioscfg_drv.spm_data.auth_token = kstrndup(buf, length, GFP_KERNEL); if (!bioscfg_drv.spm_data.auth_token) { ret = -ENOMEM; goto exit_token; -- cgit v1.2.3 From fd7545cd398adc09ec2f9b603d72671560ee6bf9 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 7 Oct 2026 18:47:11 +0500 Subject: platform/x86: hp-bioscfg: fix oops on short integer attribute buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_populate_integer_elements_from_buffer() ignores the return value of hp_get_string_from_buffer(). When fewer than 2 bytes are left in the buffer, dst_size is 0, kcalloc() returns ZERO_SIZE_PTR and hp_get_string_from_buffer() fails without writing to it, so kstrtoint() faults on address 0x10. Return the error instead. Compile tested only. Fixes: 6f2c06d5a467 ("platform/x86: hp-bioscfg: int-attributes") Signed-off-by: Muhammad Bilal Link: https://lore.kernel.org/all/20261007134711.473857-1-meatuni001@gmail.com/ Link: https://patch.msgid.link/20261007134711.473857-10-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/int-attributes.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index f2774eff613b..07654e805791 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -284,7 +284,12 @@ static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_ // VALUE: integer_data->current_value = 0; - hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size); + ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size); + if (ret < 0) { + kfree(dst); + return ret; + } + ret = kstrtoint(dst, 10, &integer_data->current_value); if (ret) pr_warn("Unable to convert string to integer: %s\n", dst); -- cgit v1.2.3 From f6f1e70a9d241d523a8f1f95b0869a25338c9aca Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Wed, 14 Oct 2026 15:23:14 +0200 Subject: platform/arm64: add Lenovo Yoga Slim 7x Gen 11 EC driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo Yoga Slim 7x Gen 11 (Qualcomm glymur / Snapdragon X2 Elite) carries a Compal EF06 embedded controller on I2C at address 0x70. The platform firmware describes it as an ACPI GenericSerialBus region, but the machine boots from device tree, so nothing otherwise talks to it. Commands are exchanged with two raw I2C block transfers: a 7 byte write to register 0x02 carrying the command and up to six arguments, then a 6 byte read of register 0x01 for the result, with a short delay in between before the result register is valid. The EC offers no way to match a result to its command; the delay is the one the vendor ACPI code uses. Expose what the EC offers that the rest of the kernel does not already provide: - the board thermistors that report a value at probe, five on this machine, as hwmon temperature channels. These are charger, CPU voltage regulator, 5 V rail and ambient sensors, not die sensors, and read considerably cooler than tsens - CPU fan speed in RPM - the keyboard backlight as an LED, off/low/high - Fn-lock as a read/write sysfs attribute, matching the ideapad-laptop interface. The EC reports Fn+Esc as an event but keeps no state of its own, so the driver owns the bit and toggles it on the key - the mic-mute and airplane-mode keys as KEY_MICMUTE and KEY_RFKILL. These reach the host only as EC events; they are not on the keyboard's HID interface and have no GPIO of their own The EC signals pending events on a dedicated line, and keeps pulsing it until they are read - so the queue must be drained completely on every interrupt, including codes this driver ignores. Leaving one behind makes the controller pulse indefinitely. Fan speed is reported but not controlled. The EC accepts a manual setpoint, however it enforces no thermal floor over it: with the fan pinned at roughly 2000 RPM under full load the SoC rose from 69 to 91 degrees in 30 seconds without the EC ever overriding. Exposing pwm1_enable or fan1_target would therefore let userspace overheat the machine, so neither is implemented. Note this is the Gen 11 machine specifically. Its EC differs from the Qualcomm reference EC that the reference designs place at address 0x76, which is not populated here, and so EC_QCOM_HAMOA does not drive it. Signed-off-by: Oleg Keri Link: https://patch.msgid.link/20261014132315.4659-3-okerixx@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/Kconfig | 24 + drivers/platform/arm64/Makefile | 1 + .../platform/arm64/lenovo-yoga-slim7x-gen11-ec.c | 530 +++++++++++++++++++++ 3 files changed, 555 insertions(+) create mode 100644 drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c (limited to 'drivers') diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index e32e01b2a9bd..de45c5e101a4 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -90,6 +90,30 @@ config EC_LENOVO_THINKPAD_T14S Say M or Y here to include this support. +config EC_LENOVO_YOGA_SLIM7X_GEN11 + tristate "Lenovo Yoga Slim 7x Gen 11 Embedded Controller driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on I2C + depends on INPUT + depends on HWMON + select NEW_LEDS + select LEDS_CLASS + help + Say Y here to enable the EC driver for the (Snapdragon X2 Elite, + glymur-based) Lenovo Yoga Slim 7x Gen 11 laptop. The EC provides + thermistors and CPU fan speed as hwmon channels, the keyboard + backlight as an LED, Fn-lock as a sysfs attribute, and the mic-mute + and airplane-mode keys. + + This is the Gen 11 (glymur) machine specifically. Its EC is a Compal + design at i2c address 0x70 and speaks a different protocol from the + Qualcomm reference EC that the reference designs place at 0x76, so + EC_QCOM_HAMOA does not drive it - and conversely this driver does not + cover the X1E-based Yoga Slim 7x Gen 9. + + To compile this driver as a module, choose M here: the module will + be called lenovo-yoga-slim7x-gen11-ec. + config EC_QCOM_HAMOA tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile index 7681be4a46e9..605cca4d42a9 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -9,4 +9,5 @@ obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o +obj-$(CONFIG_EC_LENOVO_YOGA_SLIM7X_GEN11) += lenovo-yoga-slim7x-gen11-ec.o obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o diff --git a/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c new file mode 100644 index 000000000000..257d64eb0e56 --- /dev/null +++ b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SLIM7X11_EC_BLOCK_READ 0x01 +#define SLIM7X11_EC_BLOCK_WRITE 0x02 +#define SLIM7X11_EC_RESP_LEN 6 +#define SLIM7X11_EC_REQ_LEN 7 + +#define SLIM7X11_EC_CMD_RAM_READ 0xb0 +#define SLIM7X11_EC_CMD_RAM_WRITE 0xb1 +#define SLIM7X11_EC_CMD_FAN 0x46 +#define SLIM7X11_EC_FAN_GET_SPEED 0x81 + +#define SLIM7X11_EC_CMD_EVENT 0x84 +#define SLIM7X11_EC_EVT_MAX_DRAIN 64 +#define SLIM7X11_EC_EVT_FN_LOCK 0x0f +#define SLIM7X11_EC_EVT_MIC_MUTE 0x18 +#define SLIM7X11_EC_EVT_FLIGHT_MODE 0x19 + +#define SLIM7X11_EC_SCUK 0x1c +#define SLIM7X11_EC_SCUK_MEDIA BIT(7) +#define SLIM7X11_EC_SCUK_LED_OFF BIT(5) +#define SLIM7X11_EC_SCUK_MEDIA_MASK (SLIM7X11_EC_SCUK_MEDIA | SLIM7X11_EC_SCUK_LED_OFF) + +#define SLIM7X11_EC_KBLT 0x03 +#define SLIM7X11_EC_KBLT_LEVEL GENMASK(1, 0) +#define SLIM7X11_EC_KBLT_AUTO 3 +#define SLIM7X11_EC_KBLT_MAX 2 + +#define SLIM7X11_EC_TEMP_FIRST 0x12 +#define SLIM7X11_EC_NUM_TEMP 6 +#define SLIM7X11_EC_TEMP_MAX 120 + +struct slim7x11_ec { + struct i2c_client *client; + /* serializes all EC access */ + struct mutex lock; + unsigned long temp_present; + struct led_classdev kbd_led; + u8 kblt_level; + bool kblt_restore; + struct input_dev *keys; +}; + +static int slim7x11_ec_command(struct slim7x11_ec *ec, u8 cmd, u8 arg0, u8 arg1, + u8 resp[SLIM7X11_EC_RESP_LEN]) +{ + u8 req[SLIM7X11_EC_REQ_LEN] = { cmd, arg0, arg1 }; + int ret; + + lockdep_assert_held(&ec->lock); + + ret = i2c_smbus_write_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_WRITE, sizeof(req), req); + if (ret < 0) + return ret; + + usleep_range(2000, 3000); + + ret = i2c_smbus_read_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_READ, + SLIM7X11_EC_RESP_LEN, resp); + if (ret < 0) + return ret; + if (ret != SLIM7X11_EC_RESP_LEN) + return -EIO; + + return 0; +} + +static int slim7x11_ec_ram_read(struct slim7x11_ec *ec, u8 addr, u8 *val) +{ + u8 resp[SLIM7X11_EC_RESP_LEN]; + int ret; + + ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_READ, addr, 0, resp); + if (ret) + return ret; + + *val = resp[0]; + return 0; +} + +static int slim7x11_ec_ram_write(struct slim7x11_ec *ec, u8 addr, u8 val) +{ + u8 resp[SLIM7X11_EC_RESP_LEN]; + + return slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_WRITE, addr, val, resp); +} + +static int slim7x11_ec_fan_read(struct slim7x11_ec *ec, long *rpm) +{ + u8 resp[SLIM7X11_EC_RESP_LEN]; + int ret; + + ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_FAN, SLIM7X11_EC_FAN_GET_SPEED, 0, resp); + if (ret) + return ret; + + *rpm = resp[0] * 100; + return 0; +} + +static umode_t slim7x11_ec_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct slim7x11_ec *ec = drvdata; + + switch (type) { + case hwmon_temp: + if (!(ec->temp_present & BIT(channel))) + return 0; + return 0444; + case hwmon_fan: + return 0444; + default: + return 0; + } +} + +static int slim7x11_ec_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + struct slim7x11_ec *ec = dev_get_drvdata(dev); + int ret; + u8 raw; + + guard(mutex)(&ec->lock); + + switch (type) { + case hwmon_temp: + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + channel, &raw); + if (ret) + return ret; + if (!raw) + return -ENODATA; + if (raw > SLIM7X11_EC_TEMP_MAX) + return -EIO; + *val = raw * MILLIDEGREE_PER_DEGREE; + return 0; + case hwmon_fan: + return slim7x11_ec_fan_read(ec, val); + default: + return -EOPNOTSUPP; + } +} + +static const char * const slim7x11_ec_temp_labels[SLIM7X11_EC_NUM_TEMP] = { + "Charger A", + "CPU VR", + "5V rail", + "Ambient", + "Charger B", + "Thermistor 6", +}; + +static int slim7x11_ec_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = slim7x11_ec_temp_labels[channel]; + return 0; + case hwmon_fan: + *str = "CPU fan"; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static enum led_brightness slim7x11_ec_kbd_led_get(struct led_classdev *led) +{ + struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led); + u8 raw; + + guard(mutex)(&ec->lock); + + if (slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw)) + return 0; + + raw = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw); + + if (raw == SLIM7X11_EC_KBLT_AUTO) + return SLIM7X11_EC_KBLT_MAX; + + return raw; +} + +static int slim7x11_ec_kbd_led_level(struct slim7x11_ec *ec, u8 level, u8 *old) +{ + u8 raw; + int ret; + + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw); + if (ret) + return ret; + + if (old) + *old = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw); + + FIELD_MODIFY(SLIM7X11_EC_KBLT_LEVEL, &raw, level); + + return slim7x11_ec_ram_write(ec, SLIM7X11_EC_KBLT, raw); +} + +static int slim7x11_ec_kbd_led_set(struct led_classdev *led, enum led_brightness brightness) +{ + struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led); + + guard(mutex)(&ec->lock); + + return slim7x11_ec_kbd_led_level(ec, brightness, NULL); +} + +static const struct hwmon_ops slim7x11_ec_hwmon_ops = { + .is_visible = slim7x11_ec_is_visible, + .read = slim7x11_ec_read, + .read_string = slim7x11_ec_read_string, +}; + +static const struct hwmon_channel_info * const slim7x11_ec_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL), + NULL +}; + +static const struct hwmon_chip_info slim7x11_ec_chip_info = { + .ops = &slim7x11_ec_hwmon_ops, + .info = slim7x11_ec_hwmon_info, +}; + +static int slim7x11_ec_fn_lock_toggle(struct slim7x11_ec *ec) +{ + u8 raw; + int ret; + + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw); + if (ret) + return ret; + + if (raw & SLIM7X11_EC_SCUK_MEDIA) + raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK; + else + raw |= SLIM7X11_EC_SCUK_MEDIA_MASK; + + return slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw); +} + +static void slim7x11_ec_report_key(struct slim7x11_ec *ec, unsigned int code) +{ + if (!ec->keys) + return; + + input_report_key(ec->keys, code, 1); + input_sync(ec->keys); + input_report_key(ec->keys, code, 0); + input_sync(ec->keys); +} + +static irqreturn_t slim7x11_ec_irq(int irq, void *data) +{ + struct slim7x11_ec *ec = data; + unsigned int i; + + guard(mutex)(&ec->lock); + + for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) { + u8 resp[SLIM7X11_EC_RESP_LEN]; + + if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp)) + break; + if (!resp[0]) + break; + + switch (resp[0]) { + case SLIM7X11_EC_EVT_FN_LOCK: + if (!slim7x11_ec_fn_lock_toggle(ec)) + sysfs_notify(&ec->client->dev.kobj, NULL, "fn_lock"); + break; + case SLIM7X11_EC_EVT_MIC_MUTE: + slim7x11_ec_report_key(ec, KEY_MICMUTE); + break; + case SLIM7X11_EC_EVT_FLIGHT_MODE: + slim7x11_ec_report_key(ec, KEY_RFKILL); + break; + default: + break; + } + } + + return IRQ_HANDLED; +} + +static void slim7x11_ec_register_keys(struct slim7x11_ec *ec) +{ + struct device *dev = &ec->client->dev; + struct input_dev *input; + int ret; + + input = devm_input_allocate_device(dev); + if (!input) { + dev_warn(dev, "no memory for the hotkey device\n"); + return; + } + + input->name = "Lenovo Yoga Slim 7x hotkeys"; + input->phys = "lenovo-yoga-slim7x-gen11-ec/input0"; + input->id.bustype = BUS_I2C; + input_set_capability(input, EV_KEY, KEY_MICMUTE); + input_set_capability(input, EV_KEY, KEY_RFKILL); + + ret = input_register_device(input); + if (ret) { + dev_warn(dev, "cannot register the hotkey device: %d\n", ret); + return; + } + + ec->keys = input; +} + +static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct slim7x11_ec *ec = dev_get_drvdata(dev); + u8 raw; + int ret; + + scoped_guard(mutex, &ec->lock) { + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw); + if (ret) + return ret; + } + + return sysfs_emit(buf, "%u\n", !(raw & SLIM7X11_EC_SCUK_MEDIA)); +} + +static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct slim7x11_ec *ec = dev_get_drvdata(dev); + bool enable; + u8 raw; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + guard(mutex)(&ec->lock); + + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw); + if (ret) + return ret; + + if (enable) + raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK; + else + raw |= SLIM7X11_EC_SCUK_MEDIA_MASK; + + ret = slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(fn_lock); + +static struct attribute *slim7x11_ec_attrs[] = { + &dev_attr_fn_lock.attr, + NULL +}; +ATTRIBUTE_GROUPS(slim7x11_ec); + +static int slim7x11_ec_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon; + struct slim7x11_ec *ec; + unsigned int i; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return dev_err_probe(dev, -ENODEV, "adapter lacks SMBus block transfers\n"); + + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); + if (!ec) + return -ENOMEM; + + ec->client = client; + i2c_set_clientdata(client, ec); + + ret = devm_mutex_init(dev, &ec->lock); + if (ret) + return ret; + + scoped_guard(mutex, &ec->lock) { + for (i = 0; i < SLIM7X11_EC_NUM_TEMP; i++) { + u8 raw; + + ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + i, &raw); + if (ret) + return dev_err_probe(dev, ret, "failed to read thermistor %u\n", i); + + if (raw > 0 && raw <= SLIM7X11_EC_TEMP_MAX) + ec->temp_present |= BIT(i); + } + } + + if (!ec->temp_present) + return dev_err_probe(dev, -ENODEV, "no thermistor reported a usable value\n"); + + dev_dbg(dev, "thermistors present: %#lx\n", ec->temp_present); + + hwmon = devm_hwmon_device_register_with_info(dev, "yoga_slim7x_ec", ec, + &slim7x11_ec_chip_info, NULL); + if (IS_ERR(hwmon)) + return PTR_ERR(hwmon); + + ec->kbd_led.name = "platform::kbd_backlight"; + ec->kbd_led.max_brightness = SLIM7X11_EC_KBLT_MAX; + ec->kbd_led.brightness_get = slim7x11_ec_kbd_led_get; + ec->kbd_led.brightness_set_blocking = slim7x11_ec_kbd_led_set; + + ret = devm_led_classdev_register(dev, &ec->kbd_led); + if (ret) + return dev_err_probe(dev, ret, "failed to register keyboard backlight\n"); + + if (client->irq) { + slim7x11_ec_register_keys(ec); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, slim7x11_ec_irq, + IRQF_ONESHOT, "lenovo-yoga-slim7x-gen11-ec", ec); + if (ret) + return dev_err_probe(dev, ret, "cannot request the EC interrupt\n"); + + scoped_guard(mutex, &ec->lock) { + for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) { + u8 resp[SLIM7X11_EC_RESP_LEN]; + + if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp)) + break; + if (!resp[0]) + break; + } + } + } + + return 0; +} + +static int slim7x11_ec_suspend(struct device *dev) +{ + struct slim7x11_ec *ec = dev_get_drvdata(dev); + + guard(mutex)(&ec->lock); + ec->kblt_restore = !slim7x11_ec_kbd_led_level(ec, 0, &ec->kblt_level); + + return 0; +} + +static int slim7x11_ec_resume(struct device *dev) +{ + struct slim7x11_ec *ec = dev_get_drvdata(dev); + + guard(mutex)(&ec->lock); + if (ec->kblt_restore) { + ec->kblt_restore = false; + slim7x11_ec_kbd_led_level(ec, ec->kblt_level, NULL); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(slim7x11_ec_pm_ops, slim7x11_ec_suspend, slim7x11_ec_resume); + +static const struct of_device_id slim7x11_ec_of_match[] = { + { .compatible = "lenovo,yoga-slim7x-gen11-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, slim7x11_ec_of_match); + +static const struct i2c_device_id slim7x11_ec_i2c_id[] = { + { .name = "slim7x-gen11-ec" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, slim7x11_ec_i2c_id); + +static struct i2c_driver slim7x11_ec_driver = { + .driver = { + .name = "lenovo-yoga-slim7x-gen11-ec", + .of_match_table = slim7x11_ec_of_match, + .dev_groups = slim7x11_ec_groups, + .pm = pm_sleep_ptr(&slim7x11_ec_pm_ops), + }, + .probe = slim7x11_ec_probe, + .id_table = slim7x11_ec_i2c_id, +}; +module_i2c_driver(slim7x11_ec_driver); + +MODULE_AUTHOR("Oleg Keri "); +MODULE_DESCRIPTION("Lenovo Yoga Slim 7x Gen 11 embedded controller"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 42ae226dee4e506bec95f497421b3235a66a4341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Erdo=C4=9Fan?= Date: Thu, 15 Oct 2026 01:15:41 +0300 Subject: platform/x86: hp-wmi: Add Victus 16-d1xxx board 8A26 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Victus 16-d1xxx ships with at least two DMI board names. 8A25 is already listed in victus_thermal_profile_boards, but 8A26 is not, so on those machines the driver falls back to the generic thermal policy. Selecting a platform profile then never issues the Victus performance mode WMI call (HPWMI_SET_PERFORMANCE_MODE via HPWMI_GM), and the firmware keeps the GPU's +15 W configurable TGP offset disabled. Add 8A26 to the list so these machines use the same Victus thermal profile path as 8A25. Assisted-by: claude-opus-5-5 Signed-off-by: Mert Erdoğan Link: https://patch.msgid.link/20261015-hp-wmi-victus-8a26-v2-1-3a9e40e00e64@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index dded3dfff1b3..8fd5acd01e91 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -239,6 +239,7 @@ static const char * const omen_timed_thermal_profile_boards[] = { static const char * const victus_thermal_profile_boards[] = { "88F8", "8A25", + "8A26", }; /* DMI board-specific feature data for Omen and Victus laptops. */ -- cgit v1.2.3 From ae4710d689c86029f6bea51fc444616c266190ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Erdo=C4=9Fan?= Date: Thu, 15 Oct 2026 01:15:42 +0300 Subject: platform/x86: hp-wmi: Add Victus 16-e0xxx board 88EE support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP Victus 16-e0xxx (board 88EE) is not in any of the Omen or Victus board lists, so it falls back to the generic thermal profile handling and the GPU cTGP offset is never enabled. Its firmware handles HPWMI_SET_PERFORMANCE_MODE like the 8A26 one: the mode is stored in the EC register at offset 0x95, which is what the Victus path reads back, and cTGP is enabled in performance mode. Add 88EE to victus_thermal_profile_boards and update the comment above the list, which only mentioned the Victus 16-d. Tested-by: Ege Kundakçıoğlu Assisted-by: claude-opus-5-5 Signed-off-by: Mert Erdoğan Link: https://patch.msgid.link/20261015-hp-wmi-victus-8a26-v2-2-3a9e40e00e64@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 8fd5acd01e91..0eac5e1d9cdb 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -235,8 +235,9 @@ static const char * const omen_timed_thermal_profile_boards[] = { "8BAD", }; -/* DMI Board names of Victus 16-d laptops */ +/* DMI Board names of Victus 16-d and 16-e laptops */ static const char * const victus_thermal_profile_boards[] = { + "88EE", "88F8", "8A25", "8A26", -- cgit v1.2.3 From bf74797a91459b80f02ea6a7e969fabcd8f38d88 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:16 -0700 Subject: platform/x86/intel/tpmi: Use static strings for the feature device names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_create_device() builds the auxiliary device name in an on-stack buffer and hands it to intel_vsec_add_aux(), which stores the pointer in auxdev->name without copying the string. The auxiliary device outlives tpmi_create_device(), so auxdev->name is left pointing into a stack frame that has already been torn down. Nothing dereferences auxdev->name after intel_vsec_add_aux() returns today: auxiliary_device_init() only checks it for NULL and both dev_set_name() calls copy into dev->kobj.name, while driver matching and the modalias use dev_name() instead. So this is a latent problem rather than a live one, but the auxiliary bus expects the name to outlive the device, and the other intel_vsec_add_aux() caller already passes a string literal. Convert intel_tpmi_name() into a lookup of a static array of string literals that already include the "tpmi-" prefix, and pass its return value straight to intel_vsec_add_aux(). Using a static array instead of a switch statement makes the lifetime of the returned strings obvious: they are valid for as long as the module is loaded. The snprintf() along with its on-stack buffer and length define are no longer needed. The resulting device names are unchanged, so the feature drivers keep matching on "intel_vsec.tpmi-" as before. No functional change intended. Reviewed-by: Andy Shevchenko Reviewed-by: Srinivas Pandruvada Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-2-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 41 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index fbe9c8af4aee..9b2bd0c8cbe2 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -47,6 +47,7 @@ */ #include +#include #include #include #include @@ -579,33 +580,31 @@ static void tpmi_set_control_base(struct auxiliary_device *auxdev, tpmi_info->tpmi_control_mem = mem; } +/* + * The TPMI IDs are sparse, so the unused entries are left NULL and are + * rejected by the caller like any other unsupported feature. + */ +static const char * const intel_tpmi_names[] = { + [TPMI_ID_RAPL] = "tpmi-rapl", + [TPMI_ID_PEM] = "tpmi-pem", + [TPMI_ID_UNCORE] = "tpmi-uncore", + [TPMI_ID_SST] = "tpmi-sst", + [TPMI_ID_PLR] = "tpmi-plr", +}; + static const char *intel_tpmi_name(enum intel_tpmi_id id) { - switch (id) { - case TPMI_ID_RAPL: - return "rapl"; - case TPMI_ID_PEM: - return "pem"; - case TPMI_ID_UNCORE: - return "uncore"; - case TPMI_ID_SST: - return "sst"; - case TPMI_ID_PLR: - return "plr"; - default: + if (id >= ARRAY_SIZE(intel_tpmi_names)) return NULL; - } -} -/* String Length for tpmi-"feature_name(up to 8 bytes)" */ -#define TPMI_FEATURE_NAME_LEN 14 + return intel_tpmi_names[id]; +} static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, struct intel_tpmi_pm_feature *pfs, u64 pfs_start) { struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev; - char feature_id_name[TPMI_FEATURE_NAME_LEN]; struct intel_vsec_device *feature_vsec_dev; struct tpmi_feature_state feature_state; struct resource *res, *tmp; @@ -634,8 +633,6 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; res = feature_vsec_dev->resource; - snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name); - for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); @@ -654,9 +651,11 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, * delete is required on error or on module unload. * feature_vsec_dev and res memory are also freed as part of * device deletion. + * + * "name" must outlive the auxiliary device, as the auxiliary bus + * stores the pointer rather than a copy of the string. */ - return intel_vsec_add_aux(&vsec_dev->auxdev.dev, - feature_vsec_dev, feature_id_name); + return intel_vsec_add_aux(&vsec_dev->auxdev.dev, feature_vsec_dev, name); } static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) -- cgit v1.2.3 From c8fa7ebd1bac336413cade8d32e48f60dea3f507 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:17 -0700 Subject: platform/x86/intel/vsec: Pass a struct device to intel_vsec_set_mapping() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit intel_vsec_set_mapping() takes an intel_vsec_device pointer and only reads the struct device out of it. A caller that has the device but no intel_vsec_device would have to build one just to make this call. Pass the struct device instead. TPMI is the only user of this interface and it is being prepared for enumeration methods other than a PCI VSEC capability, where no intel_vsec_device exists. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-3-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 7 +++---- drivers/platform/x86/intel/vsec_tpmi.c | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index f6b0177b555c..380d8231549f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -724,15 +724,14 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id return intel_vsec_pci_init(pdev); } -int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, - struct intel_vsec_device *vsec_dev) +int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct device *dev) { struct vsec_priv *priv; - if (!dev_is_pci(vsec_dev->dev)) + if (!dev_is_pci(dev)) return -ENODEV; - priv = pci_get_drvdata(to_pci_dev(vsec_dev->dev)); + priv = pci_get_drvdata(to_pci_dev(dev)); if (!priv) return -EINVAL; diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 9b2bd0c8cbe2..edeae3fd23a1 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -799,7 +799,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (ret) return ret; - ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev); + ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev->dev); if (ret) return ret; } -- cgit v1.2.3 From 9880967d2c5276e731fd4a208a138b4f9e7ff210 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:18 -0700 Subject: platform/x86/intel/tpmi: Remove unused vsec_dev from intel_tpmi_pm_feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The intel_vsec_device pointer stored in every PFS entry is never read back. Remove the field and its assignment. No functional change. Reviewed-by: Andy Shevchenko Reviewed-by: Srinivas Pandruvada Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-4-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index edeae3fd23a1..fdca799f7cf9 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -93,14 +93,12 @@ struct intel_tpmi_pfs_entry { * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially * this offset = "Address" from VSEC header + PFS Capability * offset for this feature entry. - * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device * * Represents TPMI instance information for one TPMI ID. */ struct intel_tpmi_pm_feature { struct intel_tpmi_pfs_entry pfs_header; u64 vsec_offset; - struct intel_vsec_device *vsec_dev; }; /** @@ -767,7 +765,6 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) int size, ret; pfs = &tpmi_info->tpmi_features[i]; - pfs->vsec_dev = vsec_dev; res = &vsec_dev->resource[i]; if (!res) -- cgit v1.2.3 From 82e084332d983908b81c9d23bebcce376fda62f8 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:19 -0700 Subject: platform/x86/intel/tpmi: Get tpmi_info directly from the parent device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_get_feature_status() and tpmi_get_debugfs_dir() are called by the TPMI feature drivers with their own auxiliary device. Both convert the parent device into an intel_vsec_device and then read the driver data of the auxiliary device it was derived from, which is the parent device they started with. Read the driver data from the parent device directly. Apart from being shorter, this drops an assumption. dev_to_ivdev() is a container_of(), so it only holds while the device the TPMI driver binds to is an auxiliary device created by the Intel VSEC driver. Another enumeration method binds to a different kind of device and the cast would hand out a bogus intel_vsec_device. The driver data is set on that device in either case. The TPMI feature devices are not affected. They remain intel_vsec_device instances allocated by the TPMI driver, since the auxiliary bus is how the feature drivers are reached and not a property of the enumeration. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-5-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index fdca799f7cf9..d254dc6a4d0d 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -356,8 +356,7 @@ err_unlock: int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, bool *read_blocked, bool *write_blocked) { - struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent); - struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev); + struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); struct tpmi_feature_state feature_state; int ret; @@ -374,8 +373,7 @@ EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI"); struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev) { - struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent); - struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev); + struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); return tpmi_info->dbgfs_dir; } -- cgit v1.2.3 From 6440396a3787037a34afcc5fdbfd67287136a935 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:20 -0700 Subject: platform/x86/intel/tpmi: Keep the feature resources in intel_tpmi_info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PFS walk and tpmi_create_devices() go back to the intel_vsec_device for the MMIO resource array and its count, although the count is already cached in intel_tpmi_info. Cache the resource array as well and use it everywhere. The array belongs to the enumerating device and outlives the TPMI instance, so holding a pointer to it is safe. Fewer places need an intel_vsec_device, which enumeration methods other than PCI VSEC do not have. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-6-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index d254dc6a4d0d..b462fd0319e5 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -110,6 +110,7 @@ struct intel_tpmi_pm_feature { * @plat_info: Stores platform info which can be used by the client drivers * @tpmi_control_mem: Memory mapped IO for getting control information * @dbgfs_dir: debugfs entry pointer + * @resource: Array of feature_count MMIO resources, one per PFS entry * * Stores the information for all TPMI devices enumerated from a single PCI device. */ @@ -121,6 +122,7 @@ struct intel_tpmi_info { struct oobmsm_plat_info plat_info; void __iomem *tpmi_control_mem; struct dentry *dbgfs_dir; + struct resource *resource; }; /** @@ -656,10 +658,9 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) { - struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev; int ret, i; - for (i = 0; i < vsec_dev->num_resources; i++) { + for (i = 0; i < tpmi_info->feature_count; i++) { ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], tpmi_info->pfs_start); /* @@ -748,15 +749,16 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) tpmi_info->vsec_dev = vsec_dev; tpmi_info->feature_count = vsec_dev->num_resources; + tpmi_info->resource = vsec_dev->resource; tpmi_info->plat_info.bus_number = pci_dev->bus->number; - tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, vsec_dev->num_resources, + tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, tpmi_info->feature_count, sizeof(*tpmi_info->tpmi_features), GFP_KERNEL); if (!tpmi_info->tpmi_features) return -ENOMEM; - for (i = 0; i < vsec_dev->num_resources; i++) { + for (i = 0; i < tpmi_info->feature_count; i++) { struct intel_tpmi_pm_feature *pfs; struct resource *res; u64 res_start; @@ -764,7 +766,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) pfs = &tpmi_info->tpmi_features[i]; - res = &vsec_dev->resource[i]; + res = &tpmi_info->resource[i]; if (!res) continue; -- cgit v1.2.3 From d6c7960a7737e531a14e2875d4273eda9de9c119 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:21 -0700 Subject: platform/x86/intel/tpmi: Describe a TPMI instance by its two devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The intel_vsec_device pointer in intel_tpmi_info is by now only used to reach two devices, the device the TPMI driver is bound to and the device TPMI was enumerated from. Store those two directly and call them tpmi_dev and parent. The devm allocations, the driver data and the TPMI feature devices all hang off tpmi_dev. The parent device names the debugfs directory, is recorded in every TPMI feature device so that the enumerating bus can find those devices again during PCI error recovery, and is the device intel_vsec_set_mapping() registers the mapping for. Both are assigned once during init and used everywhere below it, so intel_tpmi_info no longer refers to an intel_vsec_device. tpmi_to_dev() went away with its only user. There is no functional change, tpmi_dev is &auxdev->dev and parent is vsec_dev->dev, which is what the affected call sites used before. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-7-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index b462fd0319e5..f3cd8aa30af0 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -104,25 +104,33 @@ struct intel_tpmi_pm_feature { /** * struct intel_tpmi_info - TPMI information for all IDs in an instance * @tpmi_features: Pointer to a list of TPMI feature instances - * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device * @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features * @pfs_start: Start of PFS offset for the TPMI instances in this device * @plat_info: Stores platform info which can be used by the client drivers * @tpmi_control_mem: Memory mapped IO for getting control information * @dbgfs_dir: debugfs entry pointer * @resource: Array of feature_count MMIO resources, one per PFS entry + * @tpmi_dev: Device this TPMI instance is bound to. It backs the devm + * allocations, holds this structure as its driver data and + * the TPMI feature devices are created under it. + * @parent: Device this TPMI instance was enumerated from. It names + * the debugfs directory and becomes intel_vsec_device::dev + * of every TPMI feature device, so that the enumerating bus + * can find them again, for example during PCI error + * recovery. * * Stores the information for all TPMI devices enumerated from a single PCI device. */ struct intel_tpmi_info { struct intel_tpmi_pm_feature *tpmi_features; - struct intel_vsec_device *vsec_dev; int feature_count; u64 pfs_start; struct oobmsm_plat_info plat_info; void __iomem *tpmi_control_mem; struct dentry *dbgfs_dir; struct resource *resource; + struct device *tpmi_dev; + struct device *parent; }; /** @@ -534,14 +542,12 @@ static const struct file_operations mem_write_ops = { .release = single_release, }; -#define tpmi_to_dev(info) ((info)->vsec_dev->dev) - static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) { char name[64]; int i; - snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_to_dev(tpmi_info))); + snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); @@ -570,7 +576,7 @@ static void tpmi_set_control_base(struct auxiliary_device *auxdev, if (!size) return; - mem = devm_ioremap(&auxdev->dev, pfs->vsec_offset, size); + mem = devm_ioremap(tpmi_info->tpmi_dev, pfs->vsec_offset, size); if (!mem) return; @@ -602,7 +608,6 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, struct intel_tpmi_pm_feature *pfs, u64 pfs_start) { - struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev; struct intel_vsec_device *feature_vsec_dev; struct tpmi_feature_state feature_state; struct resource *res, *tmp; @@ -639,7 +644,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, tmp->flags = IORESOURCE_MEM; } - feature_vsec_dev->dev = vsec_dev->dev; + feature_vsec_dev->dev = tpmi_info->parent; feature_vsec_dev->priv_data = &tpmi_info->plat_info; feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info); feature_vsec_dev->ida = &intel_vsec_tpmi_ida; @@ -653,7 +658,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, * "name" must outlive the auxiliary device, as the auxiliary bus * stores the pointer rather than a copy of the string. */ - return intel_vsec_add_aux(&vsec_dev->auxdev.dev, feature_vsec_dev, name); + return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name); } static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) @@ -747,7 +752,8 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (!tpmi_info) return -ENOMEM; - tpmi_info->vsec_dev = vsec_dev; + tpmi_info->tpmi_dev = &auxdev->dev; + tpmi_info->parent = vsec_dev->dev; tpmi_info->feature_count = vsec_dev->num_resources; tpmi_info->resource = vsec_dev->resource; tpmi_info->plat_info.bus_number = pci_dev->bus->number; @@ -796,7 +802,8 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (ret) return ret; - ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev->dev); + ret = intel_vsec_set_mapping(&tpmi_info->plat_info, + tpmi_info->parent); if (ret) return ret; } -- cgit v1.2.3 From ef60181ce8b0a44bb0825cc8409e409b60552981 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:22 -0700 Subject: platform/x86/intel/tpmi: Drop unused arg from tpmi_set_control_base() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_set_control_base() takes the device for its devm_ioremap() from tpmi_info->tpmi_dev, so the auxiliary device argument is unused. Remove it. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-8-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index f3cd8aa30af0..f1dfe2b23c9c 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -565,8 +565,7 @@ static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) } } -static void tpmi_set_control_base(struct auxiliary_device *auxdev, - struct intel_tpmi_info *tpmi_info, +static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info, struct intel_tpmi_pm_feature *pfs) { void __iomem *mem; @@ -809,7 +808,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) } if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) - tpmi_set_control_base(auxdev, tpmi_info, pfs); + tpmi_set_control_base(tpmi_info, pfs); } tpmi_info->pfs_start = pfs_start; -- cgit v1.2.3 From 1f0e16fd229d99983f3d633f1f83b5204553a562 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:23 -0700 Subject: platform/x86/intel/tpmi: Do not assume TPMI is enumerated from PCI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The init path converts the enumerating device into a pci_dev without checking and always registers the OOBMSM package to PCI device mapping. That works today because TPMI is only enumerated from a PCI VSEC capability, but ACPI based enumeration is being worked on and the device it enumerates from is not a PCI device. Do both only for a PCI device. The bus number read from the pci_dev is a fallback that tpmi_process_info() overwrites with the value reported by the TPMI_INFO feature, so there is nothing to do without a pci_dev. The mapping stored by intel_vsec_set_mapping() is looked up by pci_dev and cannot be consumed by another enumeration method, and calling it anyway would fail the probe with -ENODEV. While here, stop describing intel_tpmi_info as holding the TPMI devices of one PCI device. No functional change for PCI enumerated TPMI. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-9-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index f1dfe2b23c9c..f96b94ba29f8 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -119,7 +119,7 @@ struct intel_tpmi_pm_feature { * can find them again, for example during PCI error * recovery. * - * Stores the information for all TPMI devices enumerated from a single PCI device. + * Stores the information for all TPMI devices of one TPMI instance. */ struct intel_tpmi_info { struct intel_tpmi_pm_feature *tpmi_features; @@ -742,7 +742,8 @@ static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, i static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) { struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - struct pci_dev *pci_dev = to_pci_dev(vsec_dev->dev); + struct pci_dev *pci_dev = dev_is_pci(vsec_dev->dev) ? + to_pci_dev(vsec_dev->dev) : NULL; struct intel_tpmi_info *tpmi_info; u64 pfs_start = 0; int ret, i; @@ -755,7 +756,14 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) tpmi_info->parent = vsec_dev->dev; tpmi_info->feature_count = vsec_dev->num_resources; tpmi_info->resource = vsec_dev->resource; - tpmi_info->plat_info.bus_number = pci_dev->bus->number; + + /* + * Seed the bus number from the enumerating device. It is only a + * fallback, tpmi_process_info() replaces it with the value the + * TPMI_INFO feature reports. + */ + if (pci_dev) + tpmi_info->plat_info.bus_number = pci_dev->bus->number; tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, tpmi_info->feature_count, sizeof(*tpmi_info->tpmi_features), @@ -801,10 +809,16 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (ret) return ret; - ret = intel_vsec_set_mapping(&tpmi_info->plat_info, - tpmi_info->parent); - if (ret) - return ret; + /* + * The mapping is looked up by pci_dev, so it is only + * meaningful for PCI enumerated TPMI instances. + */ + if (pci_dev) { + ret = intel_vsec_set_mapping(&tpmi_info->plat_info, + &pci_dev->dev); + if (ret) + return ret; + } } if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) -- cgit v1.2.3 From 1cab9841792e35f73b8e1f21bcc3f9f920bc1995 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:24 -0700 Subject: platform/x86/intel/tpmi: Split out enumeration independent init and exit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once the two devices and the MMIO resources are known, the rest of what the init path does is independent of how the TPMI instance was found. Turn that part into intel_tpmi_init(), taking them as arguments, and add intel_tpmi_deinit() for what removal has to undo. The auxiliary bus and intel_vsec_device handling stays in tpmi_probe() and tpmi_remove(). intel_tpmi_info is allocated by the caller instead of by intel_tpmi_init(), so that an enumeration method can embed it in its own state and get back to that with container_of(). The TPMI_CORE_INIT and TPMI_CORE_EXIT notifications now pass the struct device of the TPMI instance instead of its auxiliary device, so that the generic code does not have to know about the auxiliary bus. No in tree notifier callback looks at that argument and one that needs the auxiliary device can use to_auxiliary_dev(). The driver data is set with dev_set_drvdata() on the same device that auxiliary_set_drvdata() used, so tpmi_remove() still finds it. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-10-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec_tpmi.c | 48 ++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index f96b94ba29f8..3274a24152ed 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -739,23 +739,18 @@ static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, i #define TPMI_CAP_OFFSET_UNIT 1024 -static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) +static int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, + struct device *parent, struct resource *resource, + int feature_count) { - struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - struct pci_dev *pci_dev = dev_is_pci(vsec_dev->dev) ? - to_pci_dev(vsec_dev->dev) : NULL; - struct intel_tpmi_info *tpmi_info; + struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL; u64 pfs_start = 0; int ret, i; - tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL); - if (!tpmi_info) - return -ENOMEM; - - tpmi_info->tpmi_dev = &auxdev->dev; - tpmi_info->parent = vsec_dev->dev; - tpmi_info->feature_count = vsec_dev->num_resources; - tpmi_info->resource = vsec_dev->resource; + tpmi_info->tpmi_dev = tpmi_dev; + tpmi_info->parent = parent; + tpmi_info->feature_count = feature_count; + tpmi_info->resource = resource; /* * Seed the bus number from the enumerating device. It is only a @@ -765,7 +760,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) if (pci_dev) tpmi_info->plat_info.bus_number = pci_dev->bus->number; - tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, tpmi_info->feature_count, + tpmi_info->tpmi_features = devm_kcalloc(tpmi_info->tpmi_dev, tpmi_info->feature_count, sizeof(*tpmi_info->tpmi_features), GFP_KERNEL); if (!tpmi_info->tpmi_features) @@ -827,7 +822,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) tpmi_info->pfs_start = pfs_start; - auxiliary_set_drvdata(auxdev, tpmi_info); + dev_set_drvdata(tpmi_info->tpmi_dev, tpmi_info); /* * Allow debugfs when security policy allows. Everything this debugfs @@ -844,24 +839,37 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) return ret; } - blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, auxdev); + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, tpmi_info->tpmi_dev); return 0; } +static void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info) +{ + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, tpmi_info->tpmi_dev); + + debugfs_remove_recursive(tpmi_info->dbgfs_dir); +} + static int tpmi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { - return intel_vsec_tpmi_init(auxdev); + struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); + struct intel_tpmi_info *tpmi_info; + + tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL); + if (!tpmi_info) + return -ENOMEM; + + return intel_tpmi_init(tpmi_info, &auxdev->dev, vsec_dev->dev, + vsec_dev->resource, vsec_dev->num_resources); } static void tpmi_remove(struct auxiliary_device *auxdev) { struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev); - blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, auxdev); - - debugfs_remove_recursive(tpmi_info->dbgfs_dir); + intel_tpmi_deinit(tpmi_info); } static const struct auxiliary_device_id tpmi_id_table[] = { -- cgit v1.2.3 From 32cb2f81b0afca8060a07ca2933dae5e833ffa54 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:25 -0700 Subject: platform/x86/intel/tpmi: Split off the PCI VSEC enumeration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything the TPMI driver does once the PFS table has been located is independent of how the TPMI MMIO region was discovered, from walking the PFS to creating the per feature devices. Only the entry point is VSEC specific and it is now reduced to filling in the arguments of intel_tpmi_init(). Move the enumeration independent code to a new intel-tpmi_common module and leave the auxiliary driver binding to the "intel_vsec.tpmi" device created by the Intel VSEC driver in vsec_tpmi.c. The code is moved as is, apart from a stray blank line and a typo in a comment that are fixed in passing. tpmi_common.h exposes what an enumeration method needs, which is intel_tpmi_init(), intel_tpmi_deinit() and struct intel_tpmi_info. The structure is a complete type because the enumeration method allocates it and is expected to extend it with its own data. The PFS, TPMI_INFO and feature state layouts stay private to tpmi_common.c, only the hardware parsing touches them. CONFIG_INTEL_TPMI_COMMON is a hidden symbol selected by the enumeration method, in the same way as CONFIG_INTEL_TPMI_POWER_DOMAINS. The TPMI client API is now exported by intel-tpmi_common, so the client drivers depend on that module instead of intel-vsec_tpmi. That is what allows a second, non PCI enumeration method to be added later without the client drivers knowing which one is loaded. No functional change. Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-11-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/Kconfig | 4 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/tpmi_common.c | 829 ++++++++++++++++++++++++++++++ drivers/platform/x86/intel/tpmi_common.h | 52 ++ drivers/platform/x86/intel/vsec_tpmi.c | 836 +------------------------------ 5 files changed, 894 insertions(+), 828 deletions(-) create mode 100644 drivers/platform/x86/intel/tpmi_common.c create mode 100644 drivers/platform/x86/intel/tpmi_common.h (limited to 'drivers') diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 2900407d6095..ea6dc7b50d90 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -209,11 +209,15 @@ config INTEL_SMARTCONNECT config INTEL_TPMI_POWER_DOMAINS tristate +config INTEL_TPMI_COMMON + tristate + config INTEL_TPMI tristate "Intel Topology Aware Register and PM Capsule Interface (TPMI)" depends on INTEL_VSEC depends on X86_64 select INTEL_TPMI_POWER_DOMAINS + select INTEL_TPMI_COMMON help The Intel Topology Aware Register and PM Capsule Interface (TPMI), provides enumerable MMIO interface for power management features. diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 138b13756158..d2c63a136e51 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -40,6 +40,7 @@ intel-target-$(CONFIG_INTEL_PUNIT_IPC) += punit_ipc.o # TPMI drivers intel-target-$(CONFIG_INTEL_PLR_TPMI) += plr_tpmi.o intel-target-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += tpmi_power_domains.o +intel-target-$(CONFIG_INTEL_TPMI_COMMON) += tpmi_common.o intel-target-$(CONFIG_INTEL_TPMI) += vsec_tpmi.o # Intel Uncore drivers diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c new file mode 100644 index 000000000000..0db766309529 --- /dev/null +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Enumeration independent part of the Intel TPMI driver + * + * Copyright (c) 2026, Intel Corporation. + * All Rights Reserved. + * + * The TPMI (Topology Aware Register and PM Capsule Interface) provides a + * flexible, extendable and PCIe enumerable MMIO interface for PM features. + * + * For example Intel RAPL (Running Average Power Limit) provides a MMIO + * interface using TPMI. This has advantage over traditional MSR + * (Model Specific Register) interface, where a thread needs to be scheduled + * on the target CPU to read or write. Also the RAPL features vary between + * CPU models, and hence lot of model specific code. Here TPMI provides an + * architectural interface by providing hierarchical tables and fields, + * which will not need any model specific implementation. + * + * The TPMI specification defines a PFS (PM Feature Structure) table. + * This table is present in the TPMI MMIO region. Each TPMI PM feature + * has one entry in the PFS with a unique TPMI ID and its access details. + * + * The names of the devices created for the PM features start with the + * "intel_vsec.tpmi-" prefix which is followed by a specific name of the + * given PM feature (for example, "intel_vsec.tpmi-rapl.0"). + * + * The device nodes are create by using interface "intel_vsec_add_aux()" + * provided by the Intel VSEC driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpmi_common.h" + +/** + * struct intel_tpmi_pfs_entry - TPMI PM Feature Structure (PFS) entry + * @tpmi_id: TPMI feature identifier (what the feature is and its data format). + * @num_entries: Number of feature interface instances present in the PFS. + * This represents the maximum number of Power domains in the SoC. + * @entry_size: Interface instance entry size in 32-bit words. + * @cap_offset: Offset from the PM_Features base address to the base of the PM VSEC + * register bank in KB. + * @attribute: Feature attribute: 0=BIOS. 1=OS. 2-3=Reserved. + * @reserved: Bits for use in the future. + * + * Represents one TPMI feature entry data in the PFS retrieved as is + * from the hardware. + */ +struct intel_tpmi_pfs_entry { + u64 tpmi_id:8; + u64 num_entries:8; + u64 entry_size:16; + u64 cap_offset:16; + u64 attribute:2; + u64 reserved:14; +} __packed; + +/** + * struct intel_tpmi_pm_feature - TPMI PM Feature information for a TPMI ID + * @pfs_header: PFS header retireved from the hardware. + * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially + * this offset = "Address" from VSEC header + PFS Capability + * offset for this feature entry. + * + * Represents TPMI instance information for one TPMI ID. + */ +struct intel_tpmi_pm_feature { + struct intel_tpmi_pfs_entry pfs_header; + u64 vsec_offset; +}; + +/** + * struct tpmi_info_header - CPU package ID to PCI device mapping information + * @fn: PCI function number + * @dev: PCI device number + * @bus: PCI bus number + * @pkg: CPU Package id + * @segment: PCI segment id + * @partition: Package Partition id + * @cdie_mask: Bitmap of compute dies in the current partition + * @reserved: Reserved for future use + * @lock: When set to 1 the register is locked and becomes read-only + * until next reset. Not for use by the OS driver. + * + * The structure to read hardware provided mapping information. + */ +struct tpmi_info_header { + u64 fn:3; + u64 dev:5; + u64 bus:8; + u64 pkg:8; + u64 segment:8; + u64 partition:2; + u64 cdie_mask:16; + u64 reserved:13; + u64 lock:1; +} __packed; + +/** + * struct tpmi_feature_state - Structure to read hardware state of a feature + * @enabled: Enable state of a feature, 1: enabled, 0: disabled + * @reserved_1: Reserved for future use + * @write_blocked: Writes are blocked means all write operations are ignored + * @read_blocked: Reads are blocked means will read 0xFFs + * @pcs_select: Interface used by out of band software, not used in OS + * @reserved_2: Reserved for future use + * @id: TPMI ID of the feature + * @reserved_3: Reserved for future use + * @locked: When set to 1, OS can't change this register. + * + * The structure is used to read hardware state of a TPMI feature. This + * information is used for debug and restricting operations for this feature. + */ +struct tpmi_feature_state { + u32 enabled:1; + u32 reserved_1:3; + u32 write_blocked:1; + u32 read_blocked:1; + u32 pcs_select:1; + u32 reserved_2:1; + u32 id:8; + u32 reserved_3:15; + u32 locked:1; +} __packed; + +/* + * The size from hardware is in u32 units. This size is from a trusted hardware, + * but better to verify for pre silicon platforms. Set size to 0, when invalid. + */ +#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \ +({ \ + pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \ +}) + +/* Used during auxbus device creation */ +static DEFINE_IDA(intel_vsec_tpmi_ida); + +static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list); + +int tpmi_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&tpmi_notify_list, nb); +} +EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI"); + +int tpmi_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&tpmi_notify_list, nb); +} +EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI"); + +struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev) +{ + struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); + + return vsec_dev->priv_data; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_platform_data, "INTEL_TPMI"); + +int tpmi_get_resource_count(struct auxiliary_device *auxdev) +{ + struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); + + if (vsec_dev) + return vsec_dev->num_resources; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_count, "INTEL_TPMI"); + +struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index) +{ + struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); + + if (vsec_dev && index < vsec_dev->num_resources) + return &vsec_dev->resource[index]; + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, "INTEL_TPMI"); + +/* TPMI Control Interface */ + +#define TPMI_CONTROL_STATUS_OFFSET 0x00 +#define TPMI_COMMAND_OFFSET 0x08 +#define TMPI_CONTROL_DATA_VAL_OFFSET 0x0c + +/* + * Spec is calling for max 1 seconds to get ownership at the worst + * case. Read at 10 ms timeouts and repeat up to 1 second. + */ +#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC) +#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC) + +#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC) +#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC + +/* TPMI Control status register defines */ + +#define TPMI_CONTROL_STATUS_RB BIT_ULL(0) + +#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4) +#define TPMI_OWNER_NONE 0 +#define TPMI_OWNER_IN_BAND 1 + +#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6) +#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8) +#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16) + +#define TPMI_CMD_PKT_LEN 2 +#define TPMI_CMD_STATUS_SUCCESS 0x40 + +/* TPMI command data registers */ +#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0) +#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40) + +/* Command to send via control interface */ +#define TPMI_CONTROL_GET_STATE_CMD 0x10 + +#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40) + +#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16) + +/* Mutex to complete get feature status without interruption */ +static DEFINE_MUTEX(tpmi_dev_lock); + +static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner) +{ + u64 control; + + return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, + control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control), + TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US); +} + +static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id, + struct tpmi_feature_state *feature_state) +{ + u64 control, data; + int ret; + + if (!tpmi_info->tpmi_control_mem) + return -EFAULT; + + mutex_lock(&tpmi_dev_lock); + + /* Wait for owner bit set to 0 (none) */ + ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE); + if (ret) + goto err_unlock; + + /* set command id to 0x10 for TPMI_GET_STATE */ + data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); + + /* 32 bits for DATA offset and +8 for feature_id field */ + data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id); + + /* Write at command offset for qword access */ + writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET); + + /* Wait for owner bit set to in-band */ + ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND); + if (ret) + goto err_unlock; + + /* Set Run Busy and packet length of 2 dwords */ + control = TPMI_CONTROL_STATUS_RB; + control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN); + + /* Write at status offset for qword access */ + writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); + + /* Wait for Run Busy clear */ + ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, + control, !(control & TPMI_CONTROL_STATUS_RB), + TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US); + if (ret) + goto done_proc; + + control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control); + if (control != TPMI_CMD_STATUS_SUCCESS) { + ret = -EBUSY; + goto done_proc; + } + + /* Response is ready */ + memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET, + sizeof(*feature_state)); + + ret = 0; + +done_proc: + /* Set CPL "completion" bit */ + writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); + +err_unlock: + mutex_unlock(&tpmi_dev_lock); + + return ret; +} + +int tpmi_get_feature_status(struct auxiliary_device *auxdev, + int feature_id, bool *read_blocked, bool *write_blocked) +{ + struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); + struct tpmi_feature_state feature_state; + int ret; + + ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state); + if (ret) + return ret; + + *read_blocked = feature_state.read_blocked; + *write_blocked = feature_state.write_blocked; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI"); + +struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev) +{ + struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); + + return tpmi_info->dbgfs_dir; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_debugfs_dir, "INTEL_TPMI"); + +static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused) +{ + struct intel_tpmi_info *tpmi_info = s->private; + int locked, disabled, read_blocked, write_blocked; + struct tpmi_feature_state feature_state; + struct intel_tpmi_pm_feature *pfs; + int ret, i; + + seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start); + seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n"); + for (i = 0; i < tpmi_info->feature_count; ++i) { + pfs = &tpmi_info->tpmi_features[i]; + ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); + if (ret) { + locked = 'U'; + disabled = 'U'; + read_blocked = 'U'; + write_blocked = 'U'; + } else { + disabled = feature_state.enabled ? 'N' : 'Y'; + locked = feature_state.locked ? 'Y' : 'N'; + read_blocked = feature_state.read_blocked ? 'Y' : 'N'; + write_blocked = feature_state.write_blocked ? 'Y' : 'N'; + } + seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%016llx\t%c\t%c\t\t%c\t\t%c\n", + pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries, + pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset, + pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled, + read_blocked, write_blocked); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg); + +#define MEM_DUMP_COLUMN_COUNT 8 + +static int tpmi_mem_dump_show(struct seq_file *s, void *unused) +{ + size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32); + struct intel_tpmi_pm_feature *pfs = s->private; + int count, ret = 0; + void __iomem *mem; + u32 size; + u64 off; + u8 *buffer; + + size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + if (!size) + return -EIO; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + off = pfs->vsec_offset; + + mutex_lock(&tpmi_dev_lock); + + for (count = 0; count < pfs->pfs_header.num_entries; ++count) { + seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off); + + mem = ioremap(off, size); + if (!mem) { + ret = -ENOMEM; + break; + } + + memcpy_fromio(buffer, mem, size); + + seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size, + false); + + iounmap(mem); + + off += size; + } + + mutex_unlock(&tpmi_dev_lock); + + kfree(buffer); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); + +static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos) +{ + struct seq_file *m = file->private_data; + struct intel_tpmi_pm_feature *pfs = m->private; + u32 addr, value, punit, size; + u32 num_elems; + void __iomem *mem; + int ret; + + size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + if (!size) + return -EIO; + + u32 *array __free(kfree) = NULL; + ret = parse_int_array_user(userbuf, len, (int **)&array); + if (ret < 0) + return ret; + + num_elems = *array; + if (num_elems != 3) + return -EINVAL; + + punit = array[1]; + addr = array[2]; + value = array[3]; + + if (!IS_ALIGNED(addr, sizeof(u32))) + return -EINVAL; + + if (punit >= pfs->pfs_header.num_entries) + return -EINVAL; + + if (addr >= size) + return -EINVAL; + + guard(mutex)(&tpmi_dev_lock); + + mem = ioremap(pfs->vsec_offset + punit * size, size); + if (!mem) + return -ENOMEM; + + writel(value, mem + addr); + + iounmap(mem); + + return len; +} + +static int mem_write_show(struct seq_file *s, void *unused) +{ + return 0; +} + +static int mem_write_open(struct inode *inode, struct file *file) +{ + return single_open(file, mem_write_show, inode->i_private); +} + +static const struct file_operations mem_write_ops = { + .open = mem_write_open, + .read = seq_read, + .write = mem_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) +{ + char name[64]; + int i; + + snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); + tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); + + debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); + + for (i = 0; i < tpmi_info->feature_count; ++i) { + struct intel_tpmi_pm_feature *pfs; + struct dentry *dir; + + pfs = &tpmi_info->tpmi_features[i]; + snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id); + dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir); + + debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops); + debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops); + } +} + +static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info, + struct intel_tpmi_pm_feature *pfs) +{ + void __iomem *mem; + u32 size; + + size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + if (!size) + return; + + mem = devm_ioremap(tpmi_info->tpmi_dev, pfs->vsec_offset, size); + if (!mem) + return; + + /* mem is pointing to TPMI CONTROL base */ + tpmi_info->tpmi_control_mem = mem; +} + +/* + * The TPMI IDs are sparse, so the unused entries are left NULL and are + * rejected by the caller like any other unsupported feature. + */ +static const char * const intel_tpmi_names[] = { + [TPMI_ID_RAPL] = "tpmi-rapl", + [TPMI_ID_PEM] = "tpmi-pem", + [TPMI_ID_UNCORE] = "tpmi-uncore", + [TPMI_ID_SST] = "tpmi-sst", + [TPMI_ID_PLR] = "tpmi-plr", +}; + +static const char *intel_tpmi_name(enum intel_tpmi_id id) +{ + if (id >= ARRAY_SIZE(intel_tpmi_names)) + return NULL; + + return intel_tpmi_names[id]; +} + +static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, + struct intel_tpmi_pm_feature *pfs, + u64 pfs_start) +{ + struct intel_vsec_device *feature_vsec_dev; + struct tpmi_feature_state feature_state; + struct resource *res, *tmp; + const char *name; + int i, ret; + + ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); + if (ret) + return ret; + + /* + * If not enabled, continue to look at other features in the PFS, so return -EOPNOTSUPP. + * This will not cause failure of loading of this driver. + */ + if (!feature_state.enabled) + return -EOPNOTSUPP; + + name = intel_tpmi_name(pfs->pfs_header.tpmi_id); + if (!name) + return -EOPNOTSUPP; + + feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries); + if (!feature_vsec_dev) + return -ENOMEM; + + feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; + res = feature_vsec_dev->resource; + + for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { + u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); + + tmp->start = pfs->vsec_offset + entry_size_bytes * i; + tmp->end = tmp->start + entry_size_bytes - 1; + tmp->flags = IORESOURCE_MEM; + } + + feature_vsec_dev->dev = tpmi_info->parent; + feature_vsec_dev->priv_data = &tpmi_info->plat_info; + feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info); + feature_vsec_dev->ida = &intel_vsec_tpmi_ida; + + /* + * intel_vsec_add_aux() is resource managed, no explicit + * delete is required on error or on module unload. + * feature_vsec_dev and res memory are also freed as part of + * device deletion. + * + * "name" must outlive the auxiliary device, as the auxiliary bus + * stores the pointer rather than a copy of the string. + */ + return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name); +} + +static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) +{ + int ret, i; + + for (i = 0; i < tpmi_info->feature_count; i++) { + ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], + tpmi_info->pfs_start); + /* + * Fail, if the supported features fails to create device, + * otherwise, continue. Even if one device failed to create, + * fail the loading of driver. Since intel_vsec_add_aux() + * is resource managed, no clean up is required for the + * successfully created devices. + */ + if (ret && ret != -EOPNOTSUPP) + return ret; + } + + return 0; +} + +#define TPMI_INFO_BUS_INFO_OFFSET 0x08 +#define TPMI_INFO_MAJOR_VERSION 0x00 +#define TPMI_INFO_MINOR_VERSION 0x02 + +static int tpmi_process_info(struct intel_tpmi_info *tpmi_info, + struct intel_tpmi_pm_feature *pfs) +{ + struct tpmi_info_header header; + void __iomem *info_mem; + u64 feature_header; + int ret = 0; + + info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32)); + if (!info_mem) + return -ENOMEM; + + feature_header = readq(info_mem); + if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) { + ret = -ENODEV; + goto error_info_header; + } + + memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header)); + + tpmi_info->plat_info.package_id = header.pkg; + tpmi_info->plat_info.bus_number = header.bus; + tpmi_info->plat_info.device_number = header.dev; + tpmi_info->plat_info.function_number = header.fn; + + if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) { + tpmi_info->plat_info.cdie_mask = header.cdie_mask; + tpmi_info->plat_info.partition = header.partition; + tpmi_info->plat_info.segment = header.segment; + } + +error_info_header: + iounmap(info_mem); + + return ret; +} + +static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size) +{ + void __iomem *pfs_mem; + + pfs_mem = ioremap(start, size); + if (!pfs_mem) + return -ENOMEM; + + memcpy_fromio(&pfs->pfs_header, pfs_mem, sizeof(pfs->pfs_header)); + + iounmap(pfs_mem); + + return 0; +} + +#define TPMI_CAP_OFFSET_UNIT 1024 + +/** + * intel_tpmi_init() - Bring up a TPMI instance + * @tpmi_info: Caller allocated and zeroed state of the TPMI instance + * @tpmi_dev: Device representing the TPMI instance. Owns the devm + * allocations and parents the TPMI feature devices + * @parent: Device the TPMI instance was enumerated from + * @resource: Array of @feature_count MMIO resources, one per PFS entry + * @feature_count: Number of entries in @resource + * + * Walk the PFS of the TPMI instance described by @resource, register the + * debugfs interface and create a device for each supported PM feature. + * + * Return: 0 on success, negative errno otherwise. + */ +int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, + struct device *parent, struct resource *resource, int feature_count) +{ + struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL; + u64 pfs_start = 0; + int ret, i; + + tpmi_info->tpmi_dev = tpmi_dev; + tpmi_info->parent = parent; + tpmi_info->feature_count = feature_count; + tpmi_info->resource = resource; + + /* + * Seed the bus number from the enumerating device. It is only a + * fallback, tpmi_process_info() replaces it with the value the + * TPMI_INFO feature reports. + */ + if (pci_dev) + tpmi_info->plat_info.bus_number = pci_dev->bus->number; + + tpmi_info->tpmi_features = devm_kcalloc(tpmi_info->tpmi_dev, tpmi_info->feature_count, + sizeof(*tpmi_info->tpmi_features), + GFP_KERNEL); + if (!tpmi_info->tpmi_features) + return -ENOMEM; + + for (i = 0; i < tpmi_info->feature_count; i++) { + struct intel_tpmi_pm_feature *pfs; + struct resource *res; + u64 res_start; + int size, ret; + + pfs = &tpmi_info->tpmi_features[i]; + + res = &tpmi_info->resource[i]; + if (!res) + continue; + + res_start = res->start; + size = resource_size(res); + if (size < 0) + continue; + + ret = tpmi_fetch_pfs_header(pfs, res_start, size); + if (ret) + continue; + + if (!pfs_start) + pfs_start = res_start; + + pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset * TPMI_CAP_OFFSET_UNIT; + + /* + * Process TPMI_INFO to get PCI device to CPU package ID. + * Device nodes for TPMI features are not created in this + * for loop. So, the mapping information will be available + * when actual device nodes created outside this + * loop via tpmi_create_devices(). + */ + if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) { + ret = tpmi_process_info(tpmi_info, pfs); + if (ret) + return ret; + + /* + * The mapping is looked up by pci_dev, so it is only + * meaningful for PCI enumerated TPMI instances. + */ + if (pci_dev) { + ret = intel_vsec_set_mapping(&tpmi_info->plat_info, + &pci_dev->dev); + if (ret) + return ret; + } + } + + if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) + tpmi_set_control_base(tpmi_info, pfs); + } + + tpmi_info->pfs_start = pfs_start; + + dev_set_drvdata(tpmi_info->tpmi_dev, tpmi_info); + + /* + * Allow debugfs when security policy allows. Everything this debugfs + * interface provides, can also be done via /dev/mem access. If + * /dev/mem interface is locked, don't allow debugfs to present any + * information. Also check for CAP_SYS_RAWIO as /dev/mem interface. + */ + if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO)) + tpmi_dbgfs_register(tpmi_info); + + ret = tpmi_create_devices(tpmi_info); + if (ret) { + debugfs_remove_recursive(tpmi_info->dbgfs_dir); + return ret; + } + + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, tpmi_info->tpmi_dev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(intel_tpmi_init, "INTEL_TPMI"); + +/** + * intel_tpmi_deinit() - Tear a TPMI instance down + * @tpmi_info: State of the TPMI instance, as passed to intel_tpmi_init() + * + * The TPMI feature devices and the devm allocations are released by the + * device core when the TPMI device goes away. + */ +void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info) +{ + blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, tpmi_info->tpmi_dev); + + debugfs_remove_recursive(tpmi_info->dbgfs_dir); +} +EXPORT_SYMBOL_NS_GPL(intel_tpmi_deinit, "INTEL_TPMI"); + +MODULE_IMPORT_NS("INTEL_VSEC"); +MODULE_DESCRIPTION("Intel TPMI core support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/tpmi_common.h b/drivers/platform/x86/intel/tpmi_common.h new file mode 100644 index 000000000000..dd4bbe517e1c --- /dev/null +++ b/drivers/platform/x86/intel/tpmi_common.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Intel TPMI core interface for TPMI enumeration methods + * + * Copyright (c) 2026, Intel Corporation. + * All Rights Reserved. + */ + +#ifndef _TPMI_COMMON_H_ +#define _TPMI_COMMON_H_ + +#include + +struct intel_tpmi_pm_feature; + +/** + * struct intel_tpmi_info - TPMI information for all IDs in an instance + * @tpmi_features: Pointer to a list of TPMI feature instances + * @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features + * @pfs_start: Start of PFS offset for the TPMI instances in this device + * @plat_info: Stores platform info which can be used by the client drivers + * @tpmi_control_mem: Memory mapped IO for getting control information + * @dbgfs_dir: debugfs entry pointer + * @resource: Array of feature_count MMIO resources, one per PFS entry + * @tpmi_dev: Device this TPMI instance is bound to. It backs the devm + * allocations, holds this structure as its driver data and + * the TPMI feature devices are created under it. + * @parent: Device this TPMI instance was enumerated from. It names + * the debugfs directory and becomes intel_vsec_device::dev + * of every TPMI feature device, so that the enumerating bus + * can find them again, for example during PCI error + * recovery. + * + * Stores the information for all TPMI devices of one TPMI instance. + */ +struct intel_tpmi_info { + struct intel_tpmi_pm_feature *tpmi_features; + int feature_count; + u64 pfs_start; + struct oobmsm_plat_info plat_info; + void __iomem *tpmi_control_mem; + struct dentry *dbgfs_dir; + struct resource *resource; + struct device *tpmi_dev; + struct device *parent; +}; + +int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, + struct device *parent, struct resource *resource, int feature_count); +void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info); + +#endif /* _TPMI_COMMON_H_ */ diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 3274a24152ed..053fe0f58641 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -1,21 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Driver to enumerate TPMI features and create devices + * Enumerate TPMI features from a PCI VSEC structure * * Copyright (c) 2023, Intel Corporation. * All Rights Reserved. * - * The TPMI (Topology Aware Register and PM Capsule Interface) provides a - * flexible, extendable and PCIe enumerable MMIO interface for PM features. - * - * For example Intel RAPL (Running Average Power Limit) provides a MMIO - * interface using TPMI. This has advantage over traditional MSR - * (Model Specific Register) interface, where a thread needs to be scheduled - * on the target CPU to read or write. Also the RAPL features vary between - * CPU models, and hence lot of model specific code. Here TPMI provides an - * architectural interface by providing hierarchical tables and fields, - * which will not need any model specific implementation. - * * The TPMI interface uses a PCI VSEC structure to expose the location of * MMIO region. * @@ -29,827 +18,18 @@ * This TPMI driver will bind to the TPMI auxiliary device object created * by the Intel VSEC driver. * - * The TPMI specification defines a PFS (PM Feature Structure) table. - * This table is present in the TPMI MMIO region. The starting address - * of PFS is derived from the tBIR (Bar Indicator Register) and "Address" - * field from the VSEC header. - * - * Each TPMI PM feature has one entry in the PFS with a unique TPMI - * ID and its access details. The TPMI driver creates device nodes - * for the supported PM features. - * - * The names of the devices created by the TPMI driver start with the - * "intel_vsec.tpmi-" prefix which is followed by a specific name of the - * given PM feature (for example, "intel_vsec.tpmi-rapl.0"). - * - * The device nodes are create by using interface "intel_vsec_add_aux()" - * provided by the Intel VSEC driver. + * The starting address of the PFS table in the TPMI MMIO region is derived + * from the tBIR (Bar Indicator Register) and "Address" field from the VSEC + * header by the Intel VSEC driver, which passes the result on as the + * resources of the auxiliary device. Everything that happens from there on + * is enumeration independent and lives in tpmi_common.c. */ -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include -#include -#include -#include - -/** - * struct intel_tpmi_pfs_entry - TPMI PM Feature Structure (PFS) entry - * @tpmi_id: TPMI feature identifier (what the feature is and its data format). - * @num_entries: Number of feature interface instances present in the PFS. - * This represents the maximum number of Power domains in the SoC. - * @entry_size: Interface instance entry size in 32-bit words. - * @cap_offset: Offset from the PM_Features base address to the base of the PM VSEC - * register bank in KB. - * @attribute: Feature attribute: 0=BIOS. 1=OS. 2-3=Reserved. - * @reserved: Bits for use in the future. - * - * Represents one TPMI feature entry data in the PFS retrieved as is - * from the hardware. - */ -struct intel_tpmi_pfs_entry { - u64 tpmi_id:8; - u64 num_entries:8; - u64 entry_size:16; - u64 cap_offset:16; - u64 attribute:2; - u64 reserved:14; -} __packed; - -/** - * struct intel_tpmi_pm_feature - TPMI PM Feature information for a TPMI ID - * @pfs_header: PFS header retireved from the hardware. - * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially - * this offset = "Address" from VSEC header + PFS Capability - * offset for this feature entry. - * - * Represents TPMI instance information for one TPMI ID. - */ -struct intel_tpmi_pm_feature { - struct intel_tpmi_pfs_entry pfs_header; - u64 vsec_offset; -}; - -/** - * struct intel_tpmi_info - TPMI information for all IDs in an instance - * @tpmi_features: Pointer to a list of TPMI feature instances - * @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features - * @pfs_start: Start of PFS offset for the TPMI instances in this device - * @plat_info: Stores platform info which can be used by the client drivers - * @tpmi_control_mem: Memory mapped IO for getting control information - * @dbgfs_dir: debugfs entry pointer - * @resource: Array of feature_count MMIO resources, one per PFS entry - * @tpmi_dev: Device this TPMI instance is bound to. It backs the devm - * allocations, holds this structure as its driver data and - * the TPMI feature devices are created under it. - * @parent: Device this TPMI instance was enumerated from. It names - * the debugfs directory and becomes intel_vsec_device::dev - * of every TPMI feature device, so that the enumerating bus - * can find them again, for example during PCI error - * recovery. - * - * Stores the information for all TPMI devices of one TPMI instance. - */ -struct intel_tpmi_info { - struct intel_tpmi_pm_feature *tpmi_features; - int feature_count; - u64 pfs_start; - struct oobmsm_plat_info plat_info; - void __iomem *tpmi_control_mem; - struct dentry *dbgfs_dir; - struct resource *resource; - struct device *tpmi_dev; - struct device *parent; -}; - -/** - * struct tpmi_info_header - CPU package ID to PCI device mapping information - * @fn: PCI function number - * @dev: PCI device number - * @bus: PCI bus number - * @pkg: CPU Package id - * @segment: PCI segment id - * @partition: Package Partition id - * @cdie_mask: Bitmap of compute dies in the current partition - * @reserved: Reserved for future use - * @lock: When set to 1 the register is locked and becomes read-only - * until next reset. Not for use by the OS driver. - * - * The structure to read hardware provided mapping information. - */ -struct tpmi_info_header { - u64 fn:3; - u64 dev:5; - u64 bus:8; - u64 pkg:8; - u64 segment:8; - u64 partition:2; - u64 cdie_mask:16; - u64 reserved:13; - u64 lock:1; -} __packed; - -/** - * struct tpmi_feature_state - Structure to read hardware state of a feature - * @enabled: Enable state of a feature, 1: enabled, 0: disabled - * @reserved_1: Reserved for future use - * @write_blocked: Writes are blocked means all write operations are ignored - * @read_blocked: Reads are blocked means will read 0xFFs - * @pcs_select: Interface used by out of band software, not used in OS - * @reserved_2: Reserved for future use - * @id: TPMI ID of the feature - * @reserved_3: Reserved for future use - * @locked: When set to 1, OS can't change this register. - * - * The structure is used to read hardware state of a TPMI feature. This - * information is used for debug and restricting operations for this feature. - */ -struct tpmi_feature_state { - u32 enabled:1; - u32 reserved_1:3; - u32 write_blocked:1; - u32 read_blocked:1; - u32 pcs_select:1; - u32 reserved_2:1; - u32 id:8; - u32 reserved_3:15; - u32 locked:1; -} __packed; - -/* - * The size from hardware is in u32 units. This size is from a trusted hardware, - * but better to verify for pre silicon platforms. Set size to 0, when invalid. - */ -#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \ -({ \ - pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \ -}) - -/* Used during auxbus device creation */ -static DEFINE_IDA(intel_vsec_tpmi_ida); - -static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list); - -int tpmi_register_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&tpmi_notify_list, nb); -} -EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI"); - -int tpmi_unregister_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&tpmi_notify_list, nb); -} -EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI"); - -struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev) -{ - struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - - return vsec_dev->priv_data; -} -EXPORT_SYMBOL_NS_GPL(tpmi_get_platform_data, "INTEL_TPMI"); - -int tpmi_get_resource_count(struct auxiliary_device *auxdev) -{ - struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - - if (vsec_dev) - return vsec_dev->num_resources; - - return 0; -} -EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_count, "INTEL_TPMI"); - -struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index) -{ - struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - - if (vsec_dev && index < vsec_dev->num_resources) - return &vsec_dev->resource[index]; - - return NULL; -} -EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, "INTEL_TPMI"); - -/* TPMI Control Interface */ - -#define TPMI_CONTROL_STATUS_OFFSET 0x00 -#define TPMI_COMMAND_OFFSET 0x08 -#define TMPI_CONTROL_DATA_VAL_OFFSET 0x0c - -/* - * Spec is calling for max 1 seconds to get ownership at the worst - * case. Read at 10 ms timeouts and repeat up to 1 second. - */ -#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC) -#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC) - -#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC) -#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC - -/* TPMI Control status register defines */ - -#define TPMI_CONTROL_STATUS_RB BIT_ULL(0) - -#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4) -#define TPMI_OWNER_NONE 0 -#define TPMI_OWNER_IN_BAND 1 - -#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6) -#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8) -#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16) - -#define TPMI_CMD_PKT_LEN 2 -#define TPMI_CMD_STATUS_SUCCESS 0x40 - -/* TPMI command data registers */ -#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0) -#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40) - -/* Command to send via control interface */ -#define TPMI_CONTROL_GET_STATE_CMD 0x10 - -#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40) - -#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16) - -/* Mutex to complete get feature status without interruption */ -static DEFINE_MUTEX(tpmi_dev_lock); - -static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner) -{ - u64 control; - - return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, - control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control), - TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US); -} - -static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id, - struct tpmi_feature_state *feature_state) -{ - u64 control, data; - int ret; - - if (!tpmi_info->tpmi_control_mem) - return -EFAULT; - - mutex_lock(&tpmi_dev_lock); - - /* Wait for owner bit set to 0 (none) */ - ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE); - if (ret) - goto err_unlock; - - /* set command id to 0x10 for TPMI_GET_STATE */ - data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); - - /* 32 bits for DATA offset and +8 for feature_id field */ - data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id); - - /* Write at command offset for qword access */ - writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET); - - /* Wait for owner bit set to in-band */ - ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND); - if (ret) - goto err_unlock; - - /* Set Run Busy and packet length of 2 dwords */ - control = TPMI_CONTROL_STATUS_RB; - control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN); - - /* Write at status offset for qword access */ - writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); - - /* Wait for Run Busy clear */ - ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, - control, !(control & TPMI_CONTROL_STATUS_RB), - TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US); - if (ret) - goto done_proc; - - control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control); - if (control != TPMI_CMD_STATUS_SUCCESS) { - ret = -EBUSY; - goto done_proc; - } - - /* Response is ready */ - memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET, - sizeof(*feature_state)); - - ret = 0; - -done_proc: - /* Set CPL "completion" bit */ - writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); - -err_unlock: - mutex_unlock(&tpmi_dev_lock); - - return ret; -} - -int tpmi_get_feature_status(struct auxiliary_device *auxdev, - int feature_id, bool *read_blocked, bool *write_blocked) -{ - struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); - struct tpmi_feature_state feature_state; - int ret; - - ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state); - if (ret) - return ret; - - *read_blocked = feature_state.read_blocked; - *write_blocked = feature_state.write_blocked; - - return 0; -} -EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI"); - -struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev) -{ - struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent); - - return tpmi_info->dbgfs_dir; -} -EXPORT_SYMBOL_NS_GPL(tpmi_get_debugfs_dir, "INTEL_TPMI"); - -static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused) -{ - struct intel_tpmi_info *tpmi_info = s->private; - int locked, disabled, read_blocked, write_blocked; - struct tpmi_feature_state feature_state; - struct intel_tpmi_pm_feature *pfs; - int ret, i; - - - seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start); - seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n"); - for (i = 0; i < tpmi_info->feature_count; ++i) { - pfs = &tpmi_info->tpmi_features[i]; - ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); - if (ret) { - locked = 'U'; - disabled = 'U'; - read_blocked = 'U'; - write_blocked = 'U'; - } else { - disabled = feature_state.enabled ? 'N' : 'Y'; - locked = feature_state.locked ? 'Y' : 'N'; - read_blocked = feature_state.read_blocked ? 'Y' : 'N'; - write_blocked = feature_state.write_blocked ? 'Y' : 'N'; - } - seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%016llx\t%c\t%c\t\t%c\t\t%c\n", - pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries, - pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset, - pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled, - read_blocked, write_blocked); - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg); - -#define MEM_DUMP_COLUMN_COUNT 8 - -static int tpmi_mem_dump_show(struct seq_file *s, void *unused) -{ - size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32); - struct intel_tpmi_pm_feature *pfs = s->private; - int count, ret = 0; - void __iomem *mem; - u32 size; - u64 off; - u8 *buffer; - - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); - if (!size) - return -EIO; - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - off = pfs->vsec_offset; - - mutex_lock(&tpmi_dev_lock); - - for (count = 0; count < pfs->pfs_header.num_entries; ++count) { - seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off); - - mem = ioremap(off, size); - if (!mem) { - ret = -ENOMEM; - break; - } - - memcpy_fromio(buffer, mem, size); - - seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size, - false); - iounmap(mem); - - off += size; - } - - mutex_unlock(&tpmi_dev_lock); - - kfree(buffer); - - return ret; -} -DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); - -static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos) -{ - struct seq_file *m = file->private_data; - struct intel_tpmi_pm_feature *pfs = m->private; - u32 addr, value, punit, size; - u32 num_elems; - void __iomem *mem; - int ret; - - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); - if (!size) - return -EIO; - - u32 *array __free(kfree) = NULL; - ret = parse_int_array_user(userbuf, len, (int **)&array); - if (ret < 0) - return ret; - - num_elems = *array; - if (num_elems != 3) - return -EINVAL; - - punit = array[1]; - addr = array[2]; - value = array[3]; - - if (!IS_ALIGNED(addr, sizeof(u32))) - return -EINVAL; - - if (punit >= pfs->pfs_header.num_entries) - return -EINVAL; - - if (addr >= size) - return -EINVAL; - - guard(mutex)(&tpmi_dev_lock); - - mem = ioremap(pfs->vsec_offset + punit * size, size); - if (!mem) - return -ENOMEM; - - writel(value, mem + addr); - - iounmap(mem); - - return len; -} - -static int mem_write_show(struct seq_file *s, void *unused) -{ - return 0; -} - -static int mem_write_open(struct inode *inode, struct file *file) -{ - return single_open(file, mem_write_show, inode->i_private); -} - -static const struct file_operations mem_write_ops = { - .open = mem_write_open, - .read = seq_read, - .write = mem_write, - .llseek = seq_lseek, - .release = single_release, -}; - -static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) -{ - char name[64]; - int i; - - snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); - tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); - - debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); - - for (i = 0; i < tpmi_info->feature_count; ++i) { - struct intel_tpmi_pm_feature *pfs; - struct dentry *dir; - - pfs = &tpmi_info->tpmi_features[i]; - snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id); - dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir); - - debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops); - debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops); - } -} - -static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info, - struct intel_tpmi_pm_feature *pfs) -{ - void __iomem *mem; - u32 size; - - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); - if (!size) - return; - - mem = devm_ioremap(tpmi_info->tpmi_dev, pfs->vsec_offset, size); - if (!mem) - return; - - /* mem is pointing to TPMI CONTROL base */ - tpmi_info->tpmi_control_mem = mem; -} - -/* - * The TPMI IDs are sparse, so the unused entries are left NULL and are - * rejected by the caller like any other unsupported feature. - */ -static const char * const intel_tpmi_names[] = { - [TPMI_ID_RAPL] = "tpmi-rapl", - [TPMI_ID_PEM] = "tpmi-pem", - [TPMI_ID_UNCORE] = "tpmi-uncore", - [TPMI_ID_SST] = "tpmi-sst", - [TPMI_ID_PLR] = "tpmi-plr", -}; - -static const char *intel_tpmi_name(enum intel_tpmi_id id) -{ - if (id >= ARRAY_SIZE(intel_tpmi_names)) - return NULL; - - return intel_tpmi_names[id]; -} - -static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, - struct intel_tpmi_pm_feature *pfs, - u64 pfs_start) -{ - struct intel_vsec_device *feature_vsec_dev; - struct tpmi_feature_state feature_state; - struct resource *res, *tmp; - const char *name; - int i, ret; - - ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); - if (ret) - return ret; - - /* - * If not enabled, continue to look at other features in the PFS, so return -EOPNOTSUPP. - * This will not cause failure of loading of this driver. - */ - if (!feature_state.enabled) - return -EOPNOTSUPP; - - name = intel_tpmi_name(pfs->pfs_header.tpmi_id); - if (!name) - return -EOPNOTSUPP; - - feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries); - if (!feature_vsec_dev) - return -ENOMEM; - - feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; - res = feature_vsec_dev->resource; - - for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { - u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); - - tmp->start = pfs->vsec_offset + entry_size_bytes * i; - tmp->end = tmp->start + entry_size_bytes - 1; - tmp->flags = IORESOURCE_MEM; - } - - feature_vsec_dev->dev = tpmi_info->parent; - feature_vsec_dev->priv_data = &tpmi_info->plat_info; - feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info); - feature_vsec_dev->ida = &intel_vsec_tpmi_ida; - - /* - * intel_vsec_add_aux() is resource managed, no explicit - * delete is required on error or on module unload. - * feature_vsec_dev and res memory are also freed as part of - * device deletion. - * - * "name" must outlive the auxiliary device, as the auxiliary bus - * stores the pointer rather than a copy of the string. - */ - return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name); -} - -static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) -{ - int ret, i; - - for (i = 0; i < tpmi_info->feature_count; i++) { - ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], - tpmi_info->pfs_start); - /* - * Fail, if the supported features fails to create device, - * otherwise, continue. Even if one device failed to create, - * fail the loading of driver. Since intel_vsec_add_aux() - * is resource managed, no clean up is required for the - * successfully created devices. - */ - if (ret && ret != -EOPNOTSUPP) - return ret; - } - - return 0; -} - -#define TPMI_INFO_BUS_INFO_OFFSET 0x08 -#define TPMI_INFO_MAJOR_VERSION 0x00 -#define TPMI_INFO_MINOR_VERSION 0x02 - -static int tpmi_process_info(struct intel_tpmi_info *tpmi_info, - struct intel_tpmi_pm_feature *pfs) -{ - struct tpmi_info_header header; - void __iomem *info_mem; - u64 feature_header; - int ret = 0; - - info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32)); - if (!info_mem) - return -ENOMEM; - - feature_header = readq(info_mem); - if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) { - ret = -ENODEV; - goto error_info_header; - } - - memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header)); - - tpmi_info->plat_info.package_id = header.pkg; - tpmi_info->plat_info.bus_number = header.bus; - tpmi_info->plat_info.device_number = header.dev; - tpmi_info->plat_info.function_number = header.fn; - - if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) { - tpmi_info->plat_info.cdie_mask = header.cdie_mask; - tpmi_info->plat_info.partition = header.partition; - tpmi_info->plat_info.segment = header.segment; - } - -error_info_header: - iounmap(info_mem); - - return ret; -} - -static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size) -{ - void __iomem *pfs_mem; - - pfs_mem = ioremap(start, size); - if (!pfs_mem) - return -ENOMEM; - - memcpy_fromio(&pfs->pfs_header, pfs_mem, sizeof(pfs->pfs_header)); - - iounmap(pfs_mem); - - return 0; -} - -#define TPMI_CAP_OFFSET_UNIT 1024 - -static int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, - struct device *parent, struct resource *resource, - int feature_count) -{ - struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL; - u64 pfs_start = 0; - int ret, i; - - tpmi_info->tpmi_dev = tpmi_dev; - tpmi_info->parent = parent; - tpmi_info->feature_count = feature_count; - tpmi_info->resource = resource; - - /* - * Seed the bus number from the enumerating device. It is only a - * fallback, tpmi_process_info() replaces it with the value the - * TPMI_INFO feature reports. - */ - if (pci_dev) - tpmi_info->plat_info.bus_number = pci_dev->bus->number; - - tpmi_info->tpmi_features = devm_kcalloc(tpmi_info->tpmi_dev, tpmi_info->feature_count, - sizeof(*tpmi_info->tpmi_features), - GFP_KERNEL); - if (!tpmi_info->tpmi_features) - return -ENOMEM; - - for (i = 0; i < tpmi_info->feature_count; i++) { - struct intel_tpmi_pm_feature *pfs; - struct resource *res; - u64 res_start; - int size, ret; - - pfs = &tpmi_info->tpmi_features[i]; - - res = &tpmi_info->resource[i]; - if (!res) - continue; - - res_start = res->start; - size = resource_size(res); - if (size < 0) - continue; - - ret = tpmi_fetch_pfs_header(pfs, res_start, size); - if (ret) - continue; - - if (!pfs_start) - pfs_start = res_start; - - pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset * TPMI_CAP_OFFSET_UNIT; - - /* - * Process TPMI_INFO to get PCI device to CPU package ID. - * Device nodes for TPMI features are not created in this - * for loop. So, the mapping information will be available - * when actual device nodes created outside this - * loop via tpmi_create_devices(). - */ - if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) { - ret = tpmi_process_info(tpmi_info, pfs); - if (ret) - return ret; - - /* - * The mapping is looked up by pci_dev, so it is only - * meaningful for PCI enumerated TPMI instances. - */ - if (pci_dev) { - ret = intel_vsec_set_mapping(&tpmi_info->plat_info, - &pci_dev->dev); - if (ret) - return ret; - } - } - - if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) - tpmi_set_control_base(tpmi_info, pfs); - } - - tpmi_info->pfs_start = pfs_start; - - dev_set_drvdata(tpmi_info->tpmi_dev, tpmi_info); - - /* - * Allow debugfs when security policy allows. Everything this debugfs - * interface provides, can also be done via /dev/mem access. If - * /dev/mem interface is locked, don't allow debugfs to present any - * information. Also check for CAP_SYS_RAWIO as /dev/mem interface. - */ - if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO)) - tpmi_dbgfs_register(tpmi_info); - - ret = tpmi_create_devices(tpmi_info); - if (ret) { - debugfs_remove_recursive(tpmi_info->dbgfs_dir); - return ret; - } - - blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, tpmi_info->tpmi_dev); - - return 0; -} - -static void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info) -{ - blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, tpmi_info->tpmi_dev); - - debugfs_remove_recursive(tpmi_info->dbgfs_dir); -} +#include "tpmi_common.h" static int tpmi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) @@ -886,6 +66,6 @@ static struct auxiliary_driver tpmi_aux_driver = { module_auxiliary_driver(tpmi_aux_driver); -MODULE_IMPORT_NS("INTEL_VSEC"); +MODULE_IMPORT_NS("INTEL_TPMI"); MODULE_DESCRIPTION("Intel TPMI enumeration module"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5ea00e1e4edb9d4ff3f288f9d4d2c0d1a5f0f432 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:26 -0700 Subject: platform/x86/intel/tpmi: Include the headers tpmi_common.c relies on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_common.c uses a number of interfaces whose headers it never includes directly and only gets by chance through other headers: BIT_ULL(), GENMASK_ULL() bits.h __packed compiler_attributes.h EXPORT_SYMBOL_NS_GPL() export.h DEFINE_IDA() idr.h IORESOURCE_MEM ioport.h DEFINE_MUTEX(), mutex_lock() mutex.h seq_printf(), seq_hex_dump() seq_file.h kmalloc(), kzalloc_flex() slab.h USEC_PER_MSEC, USEC_PER_SEC time64.h uXX types.h Include them, so that the file does not break when an unrelated header stops pulling them in. Drop io.h while at it, as iopoll.h already includes it, and sort the block alphabetically: debugfs.h and cleanup.h were the wrong way round. No functional change intended. Suggested-by: Andy Shevchenko Suggested-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-12-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 0db766309529..83362d4bb726 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -32,19 +32,28 @@ #include #include #include -#include +#include #include +#include +#include #include +#include +#include #include #include -#include #include +#include #include +#include #include #include #include +#include #include +#include #include +#include +#include #include "tpmi_common.h" -- cgit v1.2.3 From 70fb9d0d8177c0a2f9a0f2927fbf75a78c9de38b Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:27 -0700 Subject: platform/x86/intel/tpmi: Turn TPMI_GET_SINGLE_ENTRY_SIZE() into a function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macro computing a feature instance size has a few problems. Its comment says the hardware reports the size in u32 units, but the conversion to bytes is spelled as a shift by 2, so the comment and the code have to be read together to see that they agree. Its body is a single conditional expression wrapped in a statement expression that it never needed. And it evaluates its argument twice. Make it a plain static function, tpmi_get_single_entry_size(), which fixes all three: the conversion becomes a multiply by sizeof(u32), there is nothing left to wrap, and the argument is evaluated once and type checked. No functional change intended. Suggested-by: Andy Shevchenko Suggested-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-13-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 83362d4bb726..7f6cd0fb23d7 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -149,13 +149,18 @@ struct tpmi_feature_state { } __packed; /* - * The size from hardware is in u32 units. This size is from a trusted hardware, - * but better to verify for pre silicon platforms. Set size to 0, when invalid. + * Return the size of a single feature interface instance in bytes. The + * hardware reports it in u32 units. The value comes from trusted hardware, + * but is still worth verifying on pre silicon platforms, so return 0 when + * it is out of range. */ -#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \ -({ \ - pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \ -}) +static u32 tpmi_get_single_entry_size(const struct intel_tpmi_pm_feature *pfs) +{ + if (pfs->pfs_header.entry_size > SZ_1K) + return 0; + + return pfs->pfs_header.entry_size * sizeof(u32); +} /* Used during auxbus device creation */ static DEFINE_IDA(intel_vsec_tpmi_ida); @@ -397,7 +402,7 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) u64 off; u8 *buffer; - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + size = tpmi_get_single_entry_size(pfs); if (!size) return -EIO; @@ -445,7 +450,7 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l void __iomem *mem; int ret; - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + size = tpmi_get_single_entry_size(pfs); if (!size) return -EIO; @@ -531,7 +536,7 @@ static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info, void __iomem *mem; u32 size; - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); + size = tpmi_get_single_entry_size(pfs); if (!size) return; -- cgit v1.2.3 From 925ecbd683cb752ee90b2cb076cf5be9e3620454 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:28 -0700 Subject: platform/x86/intel/tpmi: Use guard() for tpmi_dev_lock in the status read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_read_feature_status() takes tpmi_dev_lock explicitly and unwinds through an err_unlock label, which is what forced the two error paths before the mailbox command is issued to be gotos rather than returns. Take the mutex with guard() and return directly from those two paths. The done_proc label stays, because the paths that reach it have already handed the mailbox to the hardware and still owe it the CPL write before dropping the lock. That is unchanged: the two early failures never wrote CPL before either. No functional change intended. Suggested-by: Andy Shevchenko Suggested-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-14-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 7f6cd0fb23d7..00f27ac0a753 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -272,12 +272,12 @@ static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int featu if (!tpmi_info->tpmi_control_mem) return -EFAULT; - mutex_lock(&tpmi_dev_lock); + guard(mutex)(&tpmi_dev_lock); /* Wait for owner bit set to 0 (none) */ ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE); if (ret) - goto err_unlock; + return ret; /* set command id to 0x10 for TPMI_GET_STATE */ data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); @@ -291,7 +291,7 @@ static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int featu /* Wait for owner bit set to in-band */ ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND); if (ret) - goto err_unlock; + return ret; /* Set Run Busy and packet length of 2 dwords */ control = TPMI_CONTROL_STATUS_RB; @@ -323,9 +323,6 @@ done_proc: /* Set CPL "completion" bit */ writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); -err_unlock: - mutex_unlock(&tpmi_dev_lock); - return ret; } -- cgit v1.2.3 From 29810cc46eb3726f08d3b71f10380539a75e33f8 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:29 -0700 Subject: platform/x86/intel/tpmi: Use scope based cleanup in the memory dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_mem_dump_show() frees its dump buffer and drops tpmi_dev_lock by hand at the end of the function, so the ioremap() failure in the middle of the loop cannot just return. It breaks out with an error stashed in ret, which is the only reason ret exists. Attach the buffer to __free(kfree) at its allocation and take the mutex with guard(), then return the error directly from the loop. mem_write() in this file already does both, so this makes the two debugfs paths consistent. No functional change intended. Suggested-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-15-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 00f27ac0a753..5332d30aac6c 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -393,32 +393,29 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) { size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32); struct intel_tpmi_pm_feature *pfs = s->private; - int count, ret = 0; void __iomem *mem; + int count; u32 size; u64 off; - u8 *buffer; size = tpmi_get_single_entry_size(pfs); if (!size) return -EIO; - buffer = kmalloc(size, GFP_KERNEL); + u8 *buffer __free(kfree) = kmalloc(size, GFP_KERNEL); if (!buffer) return -ENOMEM; off = pfs->vsec_offset; - mutex_lock(&tpmi_dev_lock); + guard(mutex)(&tpmi_dev_lock); for (count = 0; count < pfs->pfs_header.num_entries; ++count) { seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off); mem = ioremap(off, size); - if (!mem) { - ret = -ENOMEM; - break; - } + if (!mem) + return -ENOMEM; memcpy_fromio(buffer, mem, size); @@ -430,11 +427,7 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) off += size; } - mutex_unlock(&tpmi_dev_lock); - - kfree(buffer); - - return ret; + return 0; } DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); -- cgit v1.2.3 From f68ec4f3f1a072fd816b83c66e4f6d3ea4b87ff9 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:30 -0700 Subject: platform/x86/intel/tpmi: Build the TPMI control words in one expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both mailbox words in tpmi_read_feature_status() are assembled as an assignment followed by an or-assign of a second field, even though neither value is used in between. Build each of them in a single expression, so that a word and the fields it is made of read as one thing. The two comments on the command word described one value between them, so fold them together as well. No functional change intended. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-16-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 5332d30aac6c..a91938024d09 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -279,11 +279,12 @@ static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int featu if (ret) return ret; - /* set command id to 0x10 for TPMI_GET_STATE */ - data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); - - /* 32 bits for DATA offset and +8 for feature_id field */ - data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id); + /* + * Set command id to 0x10 for TPMI_GET_STATE, with the feature id 32 + * bits into the DATA offset, +8 for the feature_id field. + */ + data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD) | + FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id); /* Write at command offset for qword access */ writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET); @@ -294,8 +295,8 @@ static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int featu return ret; /* Set Run Busy and packet length of 2 dwords */ - control = TPMI_CONTROL_STATUS_RB; - control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN); + control = TPMI_CONTROL_STATUS_RB | + FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN); /* Write at status offset for qword access */ writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); -- cgit v1.2.3 From 769236d3c2265c942ca69b567b21dfcf6439eddd Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:31 -0700 Subject: platform/x86/intel/tpmi: Use DEFINE_SHOW_STORE_ATTRIBUTE() for mem_write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mem_write debugfs file open handler and file_operations are written out by hand, which is what DEFINE_SHOW_STORE_ATTRIBUTE() exists for. Use it, and rename the write callback to mem_write_write() to match the name the macro derives. The other two files in this driver already come from DEFINE_SHOW_ATTRIBUTE(), so this also gives mem_write the .owner = THIS_MODULE that the open coded file_operations was missing. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-17-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index a91938024d09..b4d361f0ec44 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -432,7 +432,8 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); -static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos) +static ssize_t mem_write_write(struct file *file, const char __user *userbuf, size_t len, + loff_t *ppos) { struct seq_file *m = file->private_data; struct intel_tpmi_pm_feature *pfs = m->private; @@ -484,19 +485,7 @@ static int mem_write_show(struct seq_file *s, void *unused) { return 0; } - -static int mem_write_open(struct inode *inode, struct file *file) -{ - return single_open(file, mem_write_show, inode->i_private); -} - -static const struct file_operations mem_write_ops = { - .open = mem_write_open, - .read = seq_read, - .write = mem_write, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_STORE_ATTRIBUTE(mem_write); static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) { @@ -517,7 +506,7 @@ static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir); debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops); - debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops); + debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_fops); } } -- cgit v1.2.3 From 596ce7f0d6fdd6426e331d7817fad9ef02bbeb72 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:32 -0700 Subject: platform/x86/intel/tpmi: Use scnprintf() to build the debugfs names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel has both snprintf() and scnprintf() for formatting into a fixed size buffer. The preference is to settle on scnprintf() and stop adding snprintf() users, so that only one of the two remains in use. Convert the two callers in tpmi_dbgfs_register(). Neither of them looks at the return value, so the difference in what the two functions return does not matter here. No functional change intended. Suggested-by: Ilpo Järvinen Signed-off-by: Kuppuswamy Sathyanarayanan Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20261013192935.2243291-18-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index b4d361f0ec44..097754365897 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -492,7 +492,7 @@ static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) char name[64]; int i; - snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); + scnprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); @@ -502,7 +502,7 @@ static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) struct dentry *dir; pfs = &tpmi_info->tpmi_features[i]; - snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id); + scnprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id); dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir); debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops); -- cgit v1.2.3 From 5f28895b74c778a02c1d0b8e90b804e0fc235c69 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:33 -0700 Subject: platform/x86/intel/tpmi: Use DEFINE_RES_MEM() for the feature resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpmi_create_device() fills in each feature resource field by field, deriving the end from the start and setting IORESOURCE_MEM by hand. That is what DEFINE_RES_MEM() exists for, so use it. The resources come from kzalloc_flex(), so the fields the macro sets that the open coded version left alone, name and desc, were already zero. No functional change intended. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-19-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 097754365897..91d8083fecc0 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -583,9 +583,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); - tmp->start = pfs->vsec_offset + entry_size_bytes * i; - tmp->end = tmp->start + entry_size_bytes - 1; - tmp->flags = IORESOURCE_MEM; + *tmp = DEFINE_RES_MEM(pfs->vsec_offset + entry_size_bytes * i, entry_size_bytes); } feature_vsec_dev->dev = tpmi_info->parent; -- cgit v1.2.3 From 84b0d0eb2d37a8e6e68af418dadfac5b4700527a Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:34 -0700 Subject: platform/x86/intel/tpmi: Declare the loop counters in their loops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare the loop counters in the for statements that use them, so that they are scoped to their loops. In tpmi_create_device() this also replaces the tmp cursor, which moved in step with the counter, with res[i]. While at it, use post-increment in all of them, as the other loops in the file already do. No functional change intended. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-20-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 91d8083fecc0..55c8e23dd2ce 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -359,11 +359,11 @@ static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused) int locked, disabled, read_blocked, write_blocked; struct tpmi_feature_state feature_state; struct intel_tpmi_pm_feature *pfs; - int ret, i; + int ret; seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start); seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n"); - for (i = 0; i < tpmi_info->feature_count; ++i) { + for (int i = 0; i < tpmi_info->feature_count; i++) { pfs = &tpmi_info->tpmi_features[i]; ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); if (ret) { @@ -395,7 +395,6 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32); struct intel_tpmi_pm_feature *pfs = s->private; void __iomem *mem; - int count; u32 size; u64 off; @@ -411,7 +410,7 @@ static int tpmi_mem_dump_show(struct seq_file *s, void *unused) guard(mutex)(&tpmi_dev_lock); - for (count = 0; count < pfs->pfs_header.num_entries; ++count) { + for (int count = 0; count < pfs->pfs_header.num_entries; count++) { seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off); mem = ioremap(off, size); @@ -490,14 +489,13 @@ DEFINE_SHOW_STORE_ATTRIBUTE(mem_write); static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) { char name[64]; - int i; scnprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent)); tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); - for (i = 0; i < tpmi_info->feature_count; ++i) { + for (int i = 0; i < tpmi_info->feature_count; i++) { struct intel_tpmi_pm_feature *pfs; struct dentry *dir; @@ -554,9 +552,9 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, { struct intel_vsec_device *feature_vsec_dev; struct tpmi_feature_state feature_state; - struct resource *res, *tmp; + struct resource *res; const char *name; - int i, ret; + int ret; ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); if (ret) @@ -580,10 +578,10 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, feature_vsec_dev->num_resources = pfs->pfs_header.num_entries; res = feature_vsec_dev->resource; - for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { + for (int i = 0; i < pfs->pfs_header.num_entries; i++) { u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); - *tmp = DEFINE_RES_MEM(pfs->vsec_offset + entry_size_bytes * i, entry_size_bytes); + res[i] = DEFINE_RES_MEM(pfs->vsec_offset + entry_size_bytes * i, entry_size_bytes); } feature_vsec_dev->dev = tpmi_info->parent; @@ -605,9 +603,9 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) { - int ret, i; + int ret; - for (i = 0; i < tpmi_info->feature_count; i++) { + for (int i = 0; i < tpmi_info->feature_count; i++) { ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], tpmi_info->pfs_start); /* @@ -701,7 +699,7 @@ int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, { struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL; u64 pfs_start = 0; - int ret, i; + int ret; tpmi_info->tpmi_dev = tpmi_dev; tpmi_info->parent = parent; @@ -722,7 +720,7 @@ int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev, if (!tpmi_info->tpmi_features) return -ENOMEM; - for (i = 0; i < tpmi_info->feature_count; i++) { + for (int i = 0; i < tpmi_info->feature_count; i++) { struct intel_tpmi_pm_feature *pfs; struct resource *res; u64 res_start; -- cgit v1.2.3 From 68691508ea41899fb98098148225694f402f3861 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Tue, 13 Oct 2026 12:29:35 -0700 Subject: platform/x86/intel/tpmi: Describe the device creation policy on the function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment explaining the error policy of tpmi_create_devices() sits inside the loop, between the call and the check it describes. Move it above the function, where it describes the policy as a whole. Also split the check into its two cases, skipping unsupported features and failing on any other error, so that each reads on its own. No functional change intended. Suggested-by: Andy Shevchenko Suggested-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20261013192935.2243291-21-sathyanarayanan.kuppuswamy@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/tpmi_common.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c index 55c8e23dd2ce..b6b811016dfd 100644 --- a/drivers/platform/x86/intel/tpmi_common.c +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -601,6 +601,14 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name); } +/* + * Create a device for every feature in the PFS that has one. + * + * A feature that is not supported is reported as -EOPNOTSUPP and skipped. + * Any other error fails the driver load. intel_vsec_add_aux() is resource + * managed, so the devices created before such a failure need no clean up + * here. + */ static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) { int ret; @@ -608,14 +616,9 @@ static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) for (int i = 0; i < tpmi_info->feature_count; i++) { ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], tpmi_info->pfs_start); - /* - * Fail, if the supported features fails to create device, - * otherwise, continue. Even if one device failed to create, - * fail the loading of driver. Since intel_vsec_add_aux() - * is resource managed, no clean up is required for the - * successfully created devices. - */ - if (ret && ret != -EOPNOTSUPP) + if (ret == -EOPNOTSUPP) + continue; + if (ret) return ret; } -- cgit v1.2.3 From 68f1b80d34d3a3d47d4de704485c071a6c29d023 Mon Sep 17 00:00:00 2001 From: Alexey Subbota Date: Wed, 14 Oct 2026 02:22:28 +0500 Subject: platform/x86: msi-ec: Add MSI Creator Z16 A11UE EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add EC firmware version 1571EMS1.106 (MSI Creator Z16 A11UE) to the list of firmwares handled by CONF14. The EC register layout matches CONF14 field for field (verified against the out-of-tree msi-ec configuration for this firmware, where it is part of CONF_G2_10). Tested on a Creator Z16 A11UE with this driver built against 7.2.9: the module loads, charge_control_start_threshold and charge_control_end_threshold read the values set by the EC, and writing charge_control_end_threshold is applied and reads back. Assisted-by: LLM Signed-off-by: Alexey Subbota Link: https://patch.msgid.link/20261013212228.17349-1-subbota-job@yandex.ru Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 86d4880474ad..5247c25fcdf6 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1134,6 +1134,7 @@ static struct msi_ec_conf CONF13 __initdata = { }; static const char * const ALLOWED_FW_14[] __initconst = { + "1571EMS1.106", // Creator Z16 A11UE "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) "182LIMS1.108", // Vector A18 HX A9WHG "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG -- cgit v1.2.3 From 8667bd30fd033295b3038ce374c551d1059538ba Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Tue, 13 Oct 2026 15:07:40 +0800 Subject: Revert "platform/x86: thinkpad_acpi: Add X9-14 Gen 1 to non-standard thermal/fan quirk tables" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit bb0f3a5d6b9844b70ad696b9470762028f6d86ec. Lenovo's firmware team has verified that the X9-14 Gen 1 uses EC register offsets that are unique to this platform, rather than the shared non-standard ECFW layout of the models already covered by the quirk tables. Applying the generic quirk would make the driver read the wrong registers and report bogus temperatures. Lenovo is working on the correct solution based on an updated EC register specification. Link: https://lore.kernel.org/all/000acb24-7aef-4718-89ea-0dffd1ab34d9@app.fastmail.com/ Signed-off-by: Huang Wei Link: https://patch.msgid.link/20261013070740.789934-1-huangwei@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index eb3c078db8b4..4f81855ad721 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -6083,7 +6083,6 @@ static const struct tpacpi_quirk thermal_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'T', true), /* 11e Gen5 GL*/ TPACPI_Q_LNV3('R', '1', 'D', true), /* 11e Gen5 GL-R*/ TPACPI_Q_LNV3('R', '0', 'V', true), /* 11e Gen5 KL-Y*/ - TPACPI_Q_LNV3('N', '4', 'D', true), /* X9-14 Gen 1 */ }; static enum thermal_access_mode thermal_read_mode; @@ -8901,7 +8900,6 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ - TPACPI_Q_LNV3('N', '4', 'D', TPACPI_FAN_NS), /* X9-14 Gen 1 */ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ TPACPI_Q_LNV('J', '4', TPACPI_FAN_NS), /* L440 / L540 */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ -- cgit v1.2.3 From af51cf46d4d3015543a65b77605566c5f37258c6 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 12 Oct 2026 14:24:04 +0530 Subject: platform/x86/amd/hsmp: Recognize Family 1Ah client platforms and hide server-only paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add is_client_platform(), based on the ACPI-reported PM profile and an AMD vendor check, to detect Family 1Ah client platforms, Models 80h-8Fh and E0h-E3h. Use is_client_platform() to: - Hide every ACPI sysfs device attribute except smu_fw_version and protocol_version on client platforms. - Hide the metrics_bin sysfs binary attribute on client platforms. - Skip hsmp_create_sensor() in init_acpi() on client platforms. - Exclude client platforms from the HSMP_GET_METRIC_TABLE_DRAM_ADDR lookup in init_acpi(). - Register /dev/hsmp at 0600 on client platforms, 0644 on server. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20261012085407.1202295-2-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 24 +++++++++++++++++++----- drivers/platform/x86/amd/hsmp/hsmp.c | 8 +++++++- drivers/platform/x86/amd/hsmp/hsmp.h | 24 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 8257cd1da48e..09dc8abd61a7 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -300,16 +300,26 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, * so that userspace which expects the file to exist gets a clear * -EOPNOTSUPP from the read handler instead of -ENOENT, and is * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. + * Hide metrics_bin on client platforms. */ - if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) + if (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) return battr->attr.mode; return 0; } +/* Defined below by HSMP_DEV_ATTR() */ +static struct hsmp_sys_attr hattr_smu_fw_version; +static struct hsmp_sys_attr hattr_protocol_version; + static umode_t hsmp_is_sock_dev_attr_visible(struct kobject *kobj, struct attribute *attr, int id) { + /* Hide every attribute except smu_fw_version and protocol_version on client platforms */ + if (is_client_platform() && attr != &hattr_smu_fw_version.dattr.attr && + attr != &hattr_protocol_version.dattr.attr) + return 0; + return attr->mode; } @@ -557,15 +567,19 @@ static int init_acpi(struct device *dev) return ret; } - if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + /* Exclude client platforms from the metric table DRAM base lookup */ + if (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) dev_info(dev, "Failed to init metric table\n"); } - ret = hsmp_create_sensor(dev, sock_ind); - if (ret) - dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); + /* Skip hwmon registration on client platforms */ + if (!is_client_platform()) { + ret = hsmp_create_sensor(dev, sock_ind); + if (ret) + dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); + } dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 5e123a4ecea9..d32d5a907aae 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -720,7 +720,13 @@ int hsmp_misc_register(struct device *dev) */ hsmp_pdev.mdev.parent = dev; hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; - hsmp_pdev.mdev.mode = 0644; + /* + * hsmp_msg_desc_table[] only describes server messages. On client + * platforms, msg_id mappings differ, making access checks unreliable. + * Restrict /dev/hsmp to root until client-specific validation is + * added. + */ + hsmp_pdev.mdev.mode = is_client_platform() ? 0600 : 0644; return misc_register(&hsmp_pdev.mdev); } diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 8dbff16a87b1..3302c1517369 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -10,6 +10,9 @@ #ifndef HSMP_H #define HSMP_H +#include + +#include #include #include #include @@ -17,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +36,26 @@ #define DRIVER_VERSION "2.6" +/* True when the ACPI-reported PM profile indicates a client platform */ +static inline bool is_client_platform(void) +{ + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return false; + +#if IS_ENABLED(CONFIG_ACPI) + switch (acpi_gbl_FADT.preferred_profile) { + case PM_DESKTOP: + case PM_MOBILE: + case PM_TABLET: + return true; + default: + return false; + } +#else + return false; +#endif +} + struct hsmp_mbaddr_info { u32 base_addr; u32 msg_id_off; -- cgit v1.2.3 From 7aa141d1fa13e79b970cb91712fac9bb667d935a Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 12 Oct 2026 14:24:05 +0530 Subject: platform/x86/amd/hsmp: Add HSMP client support for Family 1Ah MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add HSMP client support for the Family 1Ah client platforms, Models 80h-8Fh and E0h-E3h, recognized by the previous commit via is_client_platform(). These parts speak the Ryzen Master SMC (RMSMC) message set instead of the server HSMP messages, and probe via ACPI _CRS/_DSD like a server socket. Add the client message set to the UAPI header. Introduce struct hsmp_plat_desc to select the test message and protocol-version message at runtime, based on is_client_platform() rather than a fixed set of family/model ranges. The client set has no per-message descriptor table. validate_message() and hsmp_ioctl_msg() therefore skip msg_id range validation and the /dev/hsmp GET/SET fd-mode gate for client messages, leaving msg_id validity to firmware's response status; validate_message() still bounds client num_args and response_sz to HSMP_CLIENT_MAX_MSG_LEN. Server platforms keep every check, and get_msg_desc() now also enforces a lower msg_id bound there. get_msg_desc() sanitizes msg_id with array_index_nospec() against its own num_msgs bound before indexing the descriptor table, rather than depending on a caller-side clamp. Add hsmp_msg_response_sz(), returning the descriptor table entry on server and the client protocol max otherwise, used by hsmp_cache_proto_ver() since firmware returns only args[0] for client. validate_message() and hsmp_ioctl_msg() distinguish the two sets via the already-resolved hsmp_plat_desc/hsmp_msg_desc pointers, not a second is_client_platform() call. The /dev/hsmp 0600 restriction and every sysfs/hwmon attribute hidden on client platforms stay from the previous commit: hsmp_ioctl_msg() still has no GET/SET gate for client messages, so an unprivileged opener could otherwise issue destructive SET messages, and the hidden attributes still resolve against unrelated client commands. Lifting either restriction is deferred to the commits that make the corresponding client functionality safe to expose. Document the client models in amd_hsmp.rst. Signed-off-by: Muralidhara M K Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20261012085407.1202295-3-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.c | 226 +++++++++++++++++++++++++---------- 1 file changed, 166 insertions(+), 60 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index d32d5a907aae..08e00a7c329a 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -10,7 +10,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -45,8 +48,94 @@ */ #define CHECK_GET_BIT BIT(31) +/* Catch a table left out of sync with its enum at build time */ +static_assert(ARRAY_SIZE(hsmp_msg_desc_table) == HSMP_MSG_ID_MAX); + +/* Per-platform message set: which table to use, and driver-issued msg IDs */ +struct hsmp_plat_desc { + const struct hsmp_msg_desc *msg_desc; + u32 num_msgs; + u32 test_msg; + u32 proto_ver_msg; +}; + +static const struct hsmp_plat_desc hsmp_desc_server = { + .msg_desc = hsmp_msg_desc_table, + .num_msgs = HSMP_MSG_ID_MAX, + .test_msg = HSMP_TEST, + .proto_ver_msg = HSMP_GET_PROTO_VER, +}; + +/* + * The client drives a different mailbox with the Ryzen Master SMC message + * set. It has no per-message descriptor table: num_args/response_sz are + * bounded generically instead (see validate_message()), and an unsupported + * or malformed msg_id is rejected by firmware with its own SMU status code. + */ +static const struct hsmp_plat_desc hsmp_desc_client = { + .msg_desc = NULL, + .num_msgs = HSMP_CLIENT_MSG_ID_MAX, + .test_msg = HSMP_CLIENT_TEST, + .proto_ver_msg = HSMP_CLIENT_GET_INTERFACE_VER, +}; + static struct hsmp_plat_device hsmp_pdev; +/* Cached on first use; READ_ONCE()/WRITE_ONCE() avoid a torn access */ +static const struct hsmp_plat_desc *hsmp_desc_cache; + +static const struct hsmp_plat_desc *hsmp_desc(void) +{ + const struct hsmp_plat_desc *desc = READ_ONCE(hsmp_desc_cache); + + if (likely(desc)) + return desc; + + desc = is_client_platform() ? &hsmp_desc_client : &hsmp_desc_server; + WRITE_ONCE(hsmp_desc_cache, desc); + + return desc; +} + +/* Returns NULL if the platform has no descriptor table, or msg_id is out of range or reserved */ +static const struct hsmp_msg_desc *get_msg_desc(u32 msg_id) +{ + const struct hsmp_plat_desc *desc = hsmp_desc(); + + if (!desc->msg_desc) + return NULL; + + if (msg_id < desc->test_msg || msg_id >= desc->num_msgs) + return NULL; + + /* + * Sanitize msg_id here rather than trusting a caller-side clamp: a + * mispredicted branch upstream can still reach this function with + * an unclamped msg_id (Spectre v1, CVE-2017-5753). + */ + msg_id = array_index_nospec(msg_id, desc->num_msgs); + + if (desc->msg_desc[msg_id].type == HSMP_RSVD) + return NULL; + + return &desc->msg_desc[msg_id]; +} + +/* + * Response size for a message the driver issues directly, such as the + * probe-time test, protocol-version and metric-table messages: the + * descriptor table entry for server, since these fixed, well-known message + * IDs always have one; the client protocol max otherwise, since the client + * set has no descriptor table and firmware reports no response length. + * Callers only consume the response words they expect. + */ +static u32 hsmp_msg_response_sz(u32 msg_id) +{ + const struct hsmp_msg_desc *desc = get_msg_desc(msg_id); + + return desc ? desc->response_sz : HSMP_CLIENT_MAX_MSG_LEN; +} + /* * Gates the AMD HSMP data plane against socket bring-up and teardown. * @@ -184,30 +273,46 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms static int validate_message(struct hsmp_message *msg) { - /* msg_id against valid range of message IDs */ - if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX) - return -ENOMSG; + const struct hsmp_plat_desc *plat_desc = hsmp_desc(); + const struct hsmp_msg_desc *desc; + + if (!plat_desc->msg_desc) { + /* + * The client message set has no per-message descriptor + * table and no msg_id range to validate against; cap + * num_args/response_sz to what the Ryzen Master mailbox + * supports in each direction and leave msg_id correctness, + * such as an unsupported or out-of-range value, to the SMU + * status code __hsmp_send_message() already translates. + */ + if (msg->num_args > HSMP_CLIENT_MAX_MSG_LEN || + msg->response_sz > HSMP_CLIENT_MAX_MSG_LEN) + return -EINVAL; - /* msg_id is a reserved message ID */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD) + return 0; + } + + /* Out-of-range or reserved message ID for this platform */ + desc = get_msg_desc(msg->msg_id); + if (!desc) return -ENOMSG; /* * num_args passed by user should match the num_args specified in * message description table. */ - if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args) + if (msg->num_args != desc->num_args) return -EINVAL; /* * As the HSMP protocol evolves, newer platforms may define more * response arguments for existing messages. Use an upper-bound * check so that older userspace callers requesting fewer response - * words than what the current hsmp_msg_desc_table[] defines are - * still accepted, while rejecting requests that exceed the - * hardware capability. + * words than what the current descriptor table defines are still + * accepted, while rejecting requests that exceed the hardware + * capability. */ - if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) + if (msg->response_sz > desc->response_sz) return -EINVAL; return 0; @@ -316,7 +421,7 @@ int hsmp_test(u16 sock_ind, u32 value) * Test the hsmp port by performing TEST command. The test message * takes one argument and returns the value of that argument + 1. */ - msg.msg_id = HSMP_TEST; + msg.msg_id = hsmp_desc()->test_msg; msg.num_args = 1; msg.response_sz = 1; msg.args[0] = value; @@ -338,13 +443,12 @@ int hsmp_test(u16 sock_ind, u32 value) } EXPORT_SYMBOL_NS_GPL(hsmp_test, "AMD_HSMP"); -static bool is_get_msg(struct hsmp_message *msg) +static bool is_get_msg(const struct hsmp_msg_desc *desc, struct hsmp_message *msg) { - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_GET) + if (desc->type == HSMP_GET) return true; - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET && - (msg->args[0] & CHECK_GET_BIT)) + if (desc->type == HSMP_SET_GET && (msg->args[0] & CHECK_GET_BIT)) return true; return false; @@ -354,63 +458,65 @@ static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) { int __user *arguser = (int __user *)arg; struct hsmp_message msg = { 0 }; + const struct hsmp_plat_desc *plat_desc = hsmp_desc(); + const struct hsmp_msg_desc *desc = NULL; int ret; if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message))) return -EFAULT; /* - * Check msg_id is within the range of supported msg ids - * i.e within the array bounds of hsmp_msg_desc_table + * The client message set has no per-message descriptor table to + * bounds-check msg_id against or index into, and no per-message + * type to gate fd mode on, so none of that applies here. + * validate_message() (called via hsmp_send_message() below) still + * bounds num_args/response_sz generically, and firmware is the + * final arbiter of msg_id validity via its own response status. */ - if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) - return -ENOMSG; - - /* - * Sanitize the user-controlled msg_id against speculative - * execution. The bounds check above retires the out-of-range - * case with -ENOMSG, but a mispredicted branch can still let the - * CPU speculatively use msg_id as an index into - * hsmp_msg_desc_table[] (here and in validate_message() / - * is_get_msg() called downstream via hsmp_send_message()), and - * pull arbitrary kernel memory into the cache (Spectre v1, - * CVE-2017-5753). Clamp once into msg.msg_id so every downstream - * dereference sees the sanitized value. - */ - msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX); - - switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { - case FMODE_WRITE: - /* - * Device is opened in O_WRONLY mode - * Execute only set/configure commands - */ - if (is_get_msg(&msg)) - return -EPERM; - break; - case FMODE_READ: - /* - * Device is opened in O_RDONLY mode - * Execute only get/monitor commands - */ - if (!is_get_msg(&msg)) + if (plat_desc->msg_desc) { + /* Rejects out-of-range and reserved msg_ids */ + desc = get_msg_desc(msg.msg_id); + if (!desc) + return -ENOMSG; + + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { + case FMODE_WRITE: + /* + * Device is opened in O_WRONLY mode + * Execute only set/configure commands. + */ + if (is_get_msg(desc, &msg)) + return -EPERM; + break; + case FMODE_READ: + /* + * Device is opened in O_RDONLY mode + * Execute only get/monitor commands. + */ + if (!is_get_msg(desc, &msg)) + return -EPERM; + break; + case FMODE_READ | FMODE_WRITE: + /* + * Device is opened in O_RDWR mode + * Execute both get/monitor and set/configure commands + */ + break; + default: return -EPERM; - break; - case FMODE_READ | FMODE_WRITE: - /* - * Device is opened in O_RDWR mode - * Execute both get/monitor and set/configure commands - */ - break; - default: - return -EPERM; + } } ret = hsmp_send_message(&msg); if (ret) return ret; - if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) { + /* + * The client message set has no per-message response_sz to check + * here (desc is NULL); validate_message() already bounded + * msg.response_sz. + */ + if ((desc ? desc->response_sz : msg.response_sz) > 0) { /* Copy results back to user for get/monitor commands */ if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message))) return -EFAULT; @@ -687,9 +793,9 @@ int hsmp_cache_proto_ver(u16 sock_ind) struct hsmp_message msg = { 0 }; int ret; - msg.msg_id = HSMP_GET_PROTO_VER; + msg.msg_id = hsmp_desc()->proto_ver_msg; + msg.response_sz = hsmp_msg_response_sz(msg.msg_id); msg.sock_ind = sock_ind; - msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; ret = hsmp_send_message_locked(&msg); if (!ret) -- cgit v1.2.3 From 3a489373068353c13a8c12e4d162e66ded465b0e Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 12 Oct 2026 14:24:06 +0530 Subject: platform/x86/amd/hsmp: Route metric table through the client messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the client metric table and metric table DRAM address messages into the metric table read path for the Family 1Ah client platforms, and fetch the DRAM base for them during ACPI probe, lifting the defensive client exclusion placed on that call several commits ago. Add metric_tbl_msg and metric_dram_msg to struct hsmp_plat_desc, and have hsmp_metric_tbl_read_locked() and hsmp_get_tbl_dram_base() take the message ID from there instead of a shared constant, since the client set numbers these messages differently from the server set (05h/06h vs 24h/25h). Have hsmp_get_tbl_dram_base() take response_sz from hsmp_msg_response_sz() too, since the client set has no descriptor table to take it from directly. Add enum ryzen_master_proto_versions, and replace the single !is_client_platform() exclusion guarding the hsmp_get_tbl_dram_base() call in init_acpi() with a RYZEN_MASTER_PROTO_VER1 clause for client platforms alongside the existing HSMP_PROTO_VER6 clause for server. proto_ver holds the Ryzen Master interface version on client platforms, a separate numbering space from enum hsmp_proto_versions; gating the two platforms on their own version enum avoids relying on numeric coincidence between the two, and lets a client interface version bump be reasoned about independently of the server protocol version. Signed-off-by: Muralidhara M K Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20261012085407.1202295-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 11 +++++++++-- drivers/platform/x86/amd/hsmp/hsmp.c | 14 ++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 09dc8abd61a7..611352abea32 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -567,8 +567,15 @@ static int init_acpi(struct device *dev) return ret; } - /* Exclude client platforms from the metric table DRAM base lookup */ - if (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + /* + * proto_ver holds the Ryzen Master interface version on client + * platforms, a separate numbering space from enum + * hsmp_proto_versions; gate the two platforms on their own + * version enum rather than relying on numeric coincidence + * between the two. + */ + if ((is_client_platform() && hsmp_pdev->proto_ver >= RYZEN_MASTER_PROTO_VER1) || + (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) dev_info(dev, "Failed to init metric table\n"); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 08e00a7c329a..45d20beda946 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -57,6 +57,8 @@ struct hsmp_plat_desc { u32 num_msgs; u32 test_msg; u32 proto_ver_msg; + u32 metric_tbl_msg; + u32 metric_dram_msg; }; static const struct hsmp_plat_desc hsmp_desc_server = { @@ -64,6 +66,8 @@ static const struct hsmp_plat_desc hsmp_desc_server = { .num_msgs = HSMP_MSG_ID_MAX, .test_msg = HSMP_TEST, .proto_ver_msg = HSMP_GET_PROTO_VER, + .metric_tbl_msg = HSMP_GET_METRIC_TABLE, + .metric_dram_msg = HSMP_GET_METRIC_TABLE_DRAM_ADDR, }; /* @@ -77,6 +81,8 @@ static const struct hsmp_plat_desc hsmp_desc_client = { .num_msgs = HSMP_CLIENT_MSG_ID_MAX, .test_msg = HSMP_CLIENT_TEST, .proto_ver_msg = HSMP_CLIENT_GET_INTERFACE_VER, + .metric_tbl_msg = HSMP_CLIENT_GET_METRICS_TABLE, + .metric_dram_msg = HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR, }; static struct hsmp_plat_device hsmp_pdev; @@ -671,11 +677,11 @@ static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, return -EINVAL; } - msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.msg_id = hsmp_desc()->metric_tbl_msg; msg.sock_ind = sock->sock_ind; /* - * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared + * The metric table message makes firmware refill this socket's shared * metric DRAM region, which is then copied out below. Hold the * per-socket lock across the fill-and-copy so concurrent readers of the * same socket cannot return a torn snapshot. @@ -741,8 +747,8 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) int ret; msg.sock_ind = sock_ind; - msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; - msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; + msg.msg_id = hsmp_desc()->metric_dram_msg; + msg.response_sz = hsmp_msg_response_sz(msg.msg_id); ret = hsmp_send_message_locked(&msg); if (ret) -- cgit v1.2.3 From 6e01338e987886c03e58b3523adb1f1df176822e Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 19 Oct 2026 23:02:23 +0800 Subject: platform/wmi: Introduce wmidev_exists() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some WMI drivers need to be able to detect the existence of WMI devices matching a given GUID in order to determine the availability of optional features. Using wmi_has_guid() for this will not work for WMI devices that cannot be used with the legacy GUID-based API (aka have WMI_GUID_DUPLICATED set). Introduce wmidev_exists() that also checks WMI devices that are only available to the bus-based API. Also mark wmi_has_guid() as deprecated because only WMI drivers using the legacy GUID-based API will continue to have use for it. Signed-off-by: Armin Wolf Tested-by: Mark Pearson Reviewed-by: Mark Pearson Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-1-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/wmi/core.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c index 529825dcfbfe..a99da9b7039d 100644 --- a/drivers/platform/wmi/core.c +++ b/drivers/platform/wmi/core.c @@ -145,7 +145,7 @@ static inline void get_acpi_method_name(const struct wmi_block *wblock, buffer[4] = '\0'; } -static int wmidev_match_guid(struct device *dev, const void *data) +static int wmidev_match_guid_legacy(struct device *dev, const void *data) { struct wmi_block *wblock = dev_to_wblock(dev); const guid_t *guid = data; @@ -218,7 +218,7 @@ static struct wmi_device *wmi_find_device_by_guid(const char *guid_string) if (ret < 0) return ERR_PTR(ret); - dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid); + dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid_legacy); if (!dev) return ERR_PTR(-ENODEV); @@ -800,10 +800,10 @@ acpi_status wmi_remove_notify_handler(const char *guid) EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); /** - * wmi_has_guid - Check if a GUID is available + * wmi_has_guid - Check if a GUID is available (deprecated) * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * - * Check if a given GUID is defined by _WDG. + * Check if a given GUID is available and usable with the legacy GUID-based API. * * Return: True if GUID is available, false otherwise. */ @@ -821,6 +821,31 @@ bool wmi_has_guid(const char *guid_string) } EXPORT_SYMBOL_GPL(wmi_has_guid); +static int wmidev_match_guid(struct device *dev, void *data) +{ + struct wmi_block *wblock = dev_to_wblock(dev); + const guid_t *guid = data; + + if (guid_equal(guid, &wblock->gblock.guid)) + return 1; + + return 0; +} + +/** + * wmidev_exists - Check if a WMI device with a given GUID exists + * @guid: GUID of the WMI device to search for + * + * Check if a WMI device with a given GUID exists at the given moment. + * + * Return: True if a matching WMI device exists, false otherwise. + */ +bool wmidev_exists(const guid_t *guid) +{ + return bus_for_each_dev(&wmi_bus_type, NULL, (void *)guid, wmidev_match_guid) == 1; +} +EXPORT_SYMBOL_GPL(wmidev_exists); + /** * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated) * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba -- cgit v1.2.3 From 3b53e691e907086672b72d0544e4bf27efd4250d Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:24 +0800 Subject: platform/x86: lenovo-wmi-capdata: Do not stop the AC notifier chain on error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Returning NOTIFY_BAD to the AC notifier chain stops it by preventing later notifier blocks from being called, but our error is non-fatal for other AC notifier blocks, which should continue to consume the event. Return NOTIFY_DONE and add a dev_warn() on error, so that the AC notifier chain continues. This also aligns with hp-wmi. The function documentation was copied from elsewhere and makes no sense here, so correct it as well. Fixes: e1a5fe662b59 ("platform/x86: Add Lenovo Capability Data 01 WMI Driver") Cc: stable@vger.kernel.org Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-2-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index d5e961566136..58787da959b4 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -774,13 +774,11 @@ static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) /** * lwmi_cd01_notifier_call() - Call method for cd01 notifier. - * block call chain. - * @nb: The notifier_block registered to lenovo-wmi-events driver. + * @nb: The cd01 ACPI notifier_block. * @action: Unused. * @data: The ACPI event. * - * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile - * of a change. + * Refresh cached capdata on AC status change. * * Return: notifier_block status. */ @@ -799,8 +797,16 @@ static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long acti switch (event->type) { case ACPI_AC_NOTIFY_STATUS: ret = lwmi_cd_cache(priv); - if (ret) - return NOTIFY_BAD; + if (ret) { + dev_warn(&priv->wdev->dev, + "failed to refresh capdata on AC change: %d\n", ret); + + /* + * Our error is non-fatal for others, and we still want + * the other event consumers to process it. + */ + return NOTIFY_DONE; + } return NOTIFY_OK; default: -- cgit v1.2.3 From 6f8252df18828d19030af6538362c6bea59414d1 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:25 +0800 Subject: platform/x86: lenovo-wmi-capdata: Only allocate sub-master info when necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When capdata00 declares no fan test support, there is no need to allocate sub-master info as it's basically unused except for the priv->sub_master->sub_component_list == ERR_PTR(-ENODEV) placeholder. Therefore, only allocate priv->sub_master when necessary, otherwise its absence implies the absence of sub-component. With that, PTR_ERR(sub_component_list) == -ENODEV is repurposed for the following changes to represent stubbed sub-component. Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-3-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 34 +++++++++++++++++-------------- 1 file changed, 19 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 58787da959b4..fbe2ba627ff5 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -101,9 +101,9 @@ struct lwmi_cd_priv { struct lwmi_cd_sub_master_priv { struct device *master_dev; cd_list_cb_t master_cb; - struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */ + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV): stubbed sub-component. */ bool registered; /* Has the sub-master been registered? */ - } *sub_master; + } *sub_master; /* NULL: no sub-component. */ }; struct cd_list { @@ -258,6 +258,12 @@ static int lwmi_cd_component_bind(struct device *cd_dev, case LENOVO_CAPABILITY_DATA_00: binder->cd00_list = priv->list; + /* Missing sub-master info implies missing sub-component. */ + if (!priv->sub_master) { + binder->cd_fan_list_cb(om_dev, NULL); + break; + } + priv->sub_master->master_dev = om_dev; priv->sub_master->master_cb = binder->cd_fan_list_cb; lwmi_cd_call_master_cb(priv); @@ -288,13 +294,9 @@ static void lwmi_cd_component_unbind(struct device *cd_dev, { struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); - switch (priv->list->type) { - case LENOVO_CAPABILITY_DATA_00: + if (priv->sub_master) { priv->sub_master->master_dev = NULL; priv->sub_master->master_cb = NULL; - return; - default: - return; } } @@ -369,17 +371,19 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, struct component_match *master_match = NULL; int ret; + /* + * If the sub-master has no sub-component, there is no need to allocate + * or register the sub-master. The master callback will be called with + * NULL on master <-> sub-master bind to inform the master about the + * absence of sub-component. + */ + if (sub_component_type == CD_TYPE_NONE) + return 0; + priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL); if (!priv->sub_master) return -ENOMEM; - if (sub_component_type == CD_TYPE_NONE) { - /* The master callback will be called with NULL on bind. */ - priv->sub_master->sub_component_list = ERR_PTR(-ENODEV); - priv->sub_master->registered = false; - return 0; - } - /* * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack * data cannot be used here. Steal one from lwmi_cd_table. @@ -404,7 +408,7 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, */ static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) { - if (!priv->sub_master->registered) + if (!priv->sub_master || !priv->sub_master->registered) return; component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); -- cgit v1.2.3 From c8b350085917f8ed6412ae53f3f13cfca4d79ffa Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:26 +0800 Subject: platform/x86: lenovo-wmi-capdata: Store a pointer to component info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the following changes, priv->list will become optional (i.e., NULLable). IOW, the type of the component cannot be reliable retrieved from priv->list->type. Store a pointer to component info so that the component type is always available. In this manner, priv->list->type can be eliminated. Note that the component type check in lwmi_cd*_get_data() must be removed due to it being called without the availability of priv. Removing it should be fine as the capdata APIs are really internal methods and their caller, i.e., lenovo-wmi-other, always passes a matched list. This also eliminates the need to pass the component type along setup functions. Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-4-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 44 +++++++++++++------------------ 1 file changed, 19 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index fbe2ba627ff5..5748859f8101 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -87,6 +87,7 @@ static const struct lwmi_cd_info { struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ + const struct lwmi_cd_info *info; struct wmi_device *wdev; struct cd_list *list; struct dentry *debugfs_dir; @@ -108,7 +109,6 @@ struct lwmi_cd_priv { struct cd_list { struct mutex list_mutex; /* list R/W mutex */ - enum lwmi_cd_type type; u8 count; union { @@ -137,7 +137,7 @@ static int lwmi_cd_match(struct device *dev, void *type) return false; priv = dev_get_drvdata(dev); - return priv->list->type == *(enum lwmi_cd_type *)type; + return priv->info->type == *(enum lwmi_cd_type *)type; } /** @@ -254,7 +254,7 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); struct lwmi_cd_binder *binder = data; - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: binder->cd00_list = priv->list; @@ -452,14 +452,11 @@ static const struct component_ops lwmi_cd_sub_component_ops = { * * Return: 0 on success, or -EINVAL. */ -#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _output_t) \ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \ { \ u8 idx; \ \ - if (WARN_ON(list->type != _cd_type)) \ - return -EINVAL; \ - \ guard(mutex)(&list->list_mutex); \ for (idx = 0; idx < list->count; idx++) { \ if (list->_cdxx[idx].id != attribute_id) \ @@ -470,13 +467,13 @@ static const struct component_ops lwmi_cd_sub_component_ops = { return -EINVAL; \ } -DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); +DEF_LWMI_CDXX_GET_DATA(cd00, struct capdata00); EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA"); -DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); +DEF_LWMI_CDXX_GET_DATA(cd01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); -DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); +DEF_LWMI_CDXX_GET_DATA(cd_fan, struct capdata_fan); EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); /* ======== debugfs ======== */ @@ -550,15 +547,14 @@ static int lwmi_cd_debugfs_show(struct seq_file *s, void *data) guard(mutex)(&priv->list->list_mutex); - /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */ for (idx = 0; idx < priv->list->count; idx++) { - seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx); + seq_printf(s, "%s[%u]:\n", priv->info->name, idx); - if (priv->list->type == LENOVO_CAPABILITY_DATA_00) + if (priv->info->type == LENOVO_CAPABILITY_DATA_00) lwmi_cd00_show(s, &priv->list->cd00[idx]); - else if (priv->list->type == LENOVO_CAPABILITY_DATA_01) + else if (priv->info->type == LENOVO_CAPABILITY_DATA_01) lwmi_cd01_show(s, &priv->list->cd01[idx]); - else if (priv->list->type == LENOVO_FAN_TEST_DATA) + else if (priv->info->type == LENOVO_FAN_TEST_DATA) lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]); } @@ -603,7 +599,7 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) int idx; void *p; - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: p = &priv->list->cd00[0]; size = sizeof(priv->list->cd00[0]); @@ -706,14 +702,13 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. - * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { struct cd_list *list; size_t list_size; @@ -721,7 +716,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) count = wmidev_instance_count(priv->wdev); - switch (type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: list_size = struct_size(list, cd00, count); break; @@ -747,7 +742,6 @@ got_list: if (ret) return ret; - list->type = type; list->count = count; priv->list = list; @@ -757,7 +751,6 @@ got_list: /** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. - * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and @@ -765,11 +758,11 @@ got_list: * * Return: 0 on success, or an error code. */ -static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv) { int ret; - ret = lwmi_cd_alloc(priv, type); + ret = lwmi_cd_alloc(priv); if (ret) return ret; @@ -842,10 +835,11 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) if (!priv) return -ENOMEM; + priv->info = info; priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); - ret = lwmi_cd_setup(priv, info->type); + ret = lwmi_cd_setup(priv); if (ret) goto out; @@ -911,7 +905,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) lwmi_cd_debugfs_remove(priv); - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: lwmi_cd_sub_master_del(priv); fallthrough; -- cgit v1.2.3 From 9a4102e4185ad37d41b00d263ac05c370f5c9baf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:27 +0800 Subject: platform/x86: lenovo-wmi-capdata: Defer mutex initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the following changes, priv->list may be freed if the first call to lwmi_cd_cache() fails due to WMI/ACPI errors, so the list_mutex must be initialized after it in order not to break lockdep, as there is no devm_mutex_destroy(). Considering that the first call to lwmi_cd_cache() doesn't need serialization as there is no other reader or writer this early, the initialization of list_mutex can be deferred. Therefore, initialize list_mutex only after the first call to lwmi_cd_cache() succeeds, otherwise it remains uninitialized and can be devm_kfree()-ed. Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-5-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 86 +++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 5748859f8101..3ef8879d8587 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -48,6 +48,8 @@ #include #include +#include + #include "wmi-capdata.h" #include "wmi-helpers.h" @@ -91,6 +93,7 @@ struct lwmi_cd_priv { struct wmi_device *wdev; struct cd_list *list; struct dentry *debugfs_dir; + bool initialized; /* * A capdata device may be a component master of another capdata device. @@ -586,14 +589,14 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) /* ======== WMI interface ======== */ /** - * lwmi_cd_cache() - Cache all WMI data block information + * __lwmi_cd_cache() - Cache all WMI data block information locklessly * @priv: lenovo-wmi-capdata driver data. * - * Loop through each WMI data block and cache the data. + * Loop through each WMI data block and cache the data locklessly. * * Return: 0 on success, or an error. */ -static int lwmi_cd_cache(struct lwmi_cd_priv *priv) +static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) { size_t size; int idx; @@ -615,7 +618,6 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return -EINVAL; } - guard(mutex)(&priv->list->list_mutex); for (idx = 0; idx < priv->list->count; idx++, p += size) { union acpi_object *ret_obj __free(kfree) = NULL; @@ -633,14 +635,38 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return 0; } +/** + * lwmi_cd_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. + * + * Loop through each WMI data block and cache the data. + * + * Return: 0 on success, or an error. + */ +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) +{ + /* Synchronize mutex initialization. Paired with lwmi_cd_finalize(). */ + if (!smp_load_acquire(&priv->initialized)) + return __lwmi_cd_cache(priv); + + switch (priv->info->type) { + case LENOVO_CAPABILITY_DATA_01: + break; + default: + return -EINVAL; + } + + guard(mutex)(&priv->list->list_mutex); + return __lwmi_cd_cache(priv); +} + /** * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list * @priv: lenovo-wmi-capdata driver data. - * @listptr: Pointer to returned cd_list pointer. * - * Return: count of fans found, or an error. + * Return: 0 on success, or an error. */ -static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr) +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) { struct cd_list *list; size_t size; @@ -686,6 +712,9 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis if (!list) return -ENOMEM; + list->count = count; + priv->list = list; + for (idx = 0; idx < count; idx++) { /* Do not calculate array index using count, as it may be truncated. */ list->cd_fan[idx] = (struct capdata_fan) { @@ -695,8 +724,7 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_lis }; } - *listptr = list; - return count; + return 0; } /** @@ -712,7 +740,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { struct cd_list *list; size_t list_size; - int count, ret; + int count; count = wmidev_instance_count(priv->wdev); @@ -724,11 +752,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) list_size = struct_size(list, cd01, count); break; case LENOVO_FAN_TEST_DATA: - count = lwmi_cd_fan_list_alloc_cache(priv, &list); - if (count < 0) - return count; - - goto got_list; + return lwmi_cd_fan_list_alloc_cache(priv); default: return -EINVAL; } @@ -737,17 +761,33 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) if (!list) return -ENOMEM; -got_list: - ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); - if (ret) - return ret; - list->count = count; priv->list = list; return 0; } +/** + * lwmi_cd_finalize() - Finalize the capability data initialization + * @priv: lenovo-wmi-capdata driver data. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_finalize(struct lwmi_cd_priv *priv) +{ + int ret; + + if (priv->list) { + ret = devm_mutex_init(&priv->wdev->dev, &priv->list->list_mutex); + if (ret) + return ret; + } + + /* Synchronize mutex initialization. Paired with lwmi_cd_cache(). */ + smp_store_release(&priv->initialized, 1); + return 0; +} + /** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. @@ -766,7 +806,11 @@ static int lwmi_cd_setup(struct lwmi_cd_priv *priv) if (ret) return ret; - return lwmi_cd_cache(priv); + ret = lwmi_cd_cache(priv); + if (ret) + return ret; + + return lwmi_cd_finalize(priv); } /** -- cgit v1.2.3 From 50a11dcdbd073a26b152b46d214cc980ecfc8426 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:28 +0800 Subject: platform/x86: lenovo-wmi-{capdata,other}: Only allocate capdata list when necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When no capability data is available, there is no need to allocate capability data list as it's basically unused except for the priv->list->count == 0 placeholder. Therefore, only allocate priv->list when necessary, otherwise its absence implies the absence of capability data. In this manner, lenovo-wmi-other can skip registering unavailable functionalities accordingly. Meanwhile, skip creating the debugfs directory as it provides nothing when there is no capability data. Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-6-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 42 ++++++++++++++++++++++--------- drivers/platform/x86/lenovo/wmi-other.c | 24 +++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 3ef8879d8587..3c09bb9e3d46 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -313,8 +313,8 @@ static const struct component_ops lwmi_cd_component_ops = { * @dev: The sub-master capdata basic device. * * Call component_bind_all to bind the sub-component device to the sub-master - * device. On success, collect the pointer to the sub-component list and try - * to call the master callback. + * device. On success, collect the pointer (or ERR_PTR(-ENODEV) if it's stubbed) + * to the sub-component list and try to call the master callback. * * Return: 0 on success, or an error code. */ @@ -328,7 +328,7 @@ static int lwmi_cd_sub_master_bind(struct device *dev) if (ret) return ret; - priv->sub_master->sub_component_list = sub_component_list; + priv->sub_master->sub_component_list = sub_component_list ?: ERR_PTR(-ENODEV); lwmi_cd_call_master_cb(priv); return 0; @@ -460,6 +460,9 @@ static const struct component_ops lwmi_cd_sub_component_ops = { { \ u8 idx; \ \ + if (WARN_ON(!list)) \ + return -EINVAL; \ + \ guard(mutex)(&list->list_mutex); \ for (idx = 0; idx < list->count; idx++) { \ if (list->_cdxx[idx].id != attribute_id) \ @@ -571,6 +574,9 @@ DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs); */ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv) { + if (!priv->list) + return; + priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev); debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops); @@ -582,6 +588,7 @@ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv) */ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) { + /* Debugfs can handle NULL dir, no need to check. */ debugfs_remove_recursive(priv->debugfs_dir); priv->debugfs_dir = NULL; } @@ -645,6 +652,9 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) */ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { + if (!priv->list) + return 0; + /* Synchronize mutex initialization. Paired with lwmi_cd_finalize(). */ if (!smp_load_acquire(&priv->initialized)) return __lwmi_cd_cache(priv); @@ -708,6 +718,9 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) count = 0; } + if (!count) + return 0; + list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL); if (!list) return -ENOMEM; @@ -743,6 +756,8 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) int count; count = wmidev_instance_count(priv->wdev); + if (!count) + return 0; switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: @@ -892,7 +907,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA; struct capdata00 capdata00; - ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00); + ret = priv->list ? lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, + &capdata00) : -ENODATA; if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); sub_component_type = CD_TYPE_NONE; @@ -913,14 +929,16 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; - ret = register_acpi_notifier(&priv->acpi_nb); - if (ret) - goto out; + if (priv->list) { + ret = register_acpi_notifier(&priv->acpi_nb); + if (ret) + goto out; - ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, - &priv->acpi_nb); - if (ret) - goto out; + ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, + &priv->acpi_nb); + if (ret) + goto out; + } ret = component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; @@ -938,7 +956,7 @@ out: lwmi_cd_debugfs_add(priv); dev_dbg(&wdev->dev, "registered %s with %u items\n", - info->name, priv->list->count); + info->name, priv->list ? priv->list->count : 0); } return ret; } diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index fbb32bf404f2..147e182685a1 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -1619,11 +1619,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) * lenovo-wmi-other master driver, with a callback to collect fan info from * capdata_fan. On success, assign the capability data list pointers to the * driver data struct for later access. These pointers are only valid while the - * capdata interfaces exist. Finally, collect fan info from capdata00 and - * register all firmware attribute groups. Note that the HWMON device is - * registered only if all fan info is collected. Hence, it is not registered - * here. See lwmi_om_fan_info_collect_cd00() and - * lwmi_om_fan_info_collect_cd_fan(). + * capdata interfaces exist. + * + * If capdata00 is bound, collect fan info from capdata00 and register the power + * supply extension. Note that the HWMON device is registered only if all fan + * info is collected. Hence, it is not registered here. See + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan(). + * + * If capdata01 is bound, register all firmware attribute groups. * * Return: 0 on success, or an error code. */ @@ -1643,16 +1646,19 @@ static int lwmi_om_master_bind(struct device *dev) priv->cd00_list = binder.cd00_list; priv->cd01_list = binder.cd01_list; - if (!priv->cd00_list || !priv->cd01_list) { + if (!priv->cd00_list && !priv->cd01_list) { component_unbind_all(dev, NULL); return -ENODEV; } - lwmi_om_fan_info_collect_cd00(priv); - lwmi_om_psy_ext_init(priv); + if (priv->cd00_list) { + lwmi_om_fan_info_collect_cd00(priv); + lwmi_om_psy_ext_init(priv); + } - lwmi_om_fw_attr_add(priv); + if (priv->cd01_list) + lwmi_om_fw_attr_add(priv); return 0; } -- cgit v1.2.3 From f44eea1b94819cc3a790c24a9cc77d06738050f5 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:29 +0800 Subject: platform/x86: lenovo-wmi-capdata: Adopt new WMI API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new WMI API supports multiple ACPI types by converting them into a unified buffer that satisfies alignment and size requirements. Adopt it to make our life easier. Note that the new WMI API only accepts a few ACPI types to conform to the behavior of the Windows WMI-ACPI driver. By adopting the new API, we intentionally rejects improper ACPI types instead of silently ignoring them. Meanwhile, considering that `struct_size(block, data, count * 3)' may overflow when calculating `count * 3', ignore Fan Test Data with count > U8_MAX instead of capping `count'. Reviewed-by: Armin Wolf Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-7-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 76 ++++++++++++++----------------- 1 file changed, 34 insertions(+), 42 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 3c09bb9e3d46..0efe37644627 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -626,17 +626,19 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) } for (idx = 0; idx < priv->list->count; idx++, p += size) { - union acpi_object *ret_obj __free(kfree) = NULL; + struct wmi_buffer wbuf; + int ret; - ret_obj = wmidev_block_query(priv->wdev, idx); - if (!ret_obj) - return -ENODEV; - - if (ret_obj->type != ACPI_TYPE_BUFFER || - ret_obj->buffer.length < size) + ret = wmidev_query_block(priv->wdev, idx, &wbuf, size); + if (ret == -ENODATA) /* The block is too short, probably stubbed. */ continue; + if (ret) + return ret; + + /* Capdata 01 is an extension to capdata 00. */ + struct capdata00 *capdata __free(kfree) = wbuf.data; - memcpy(p, ret_obj->buffer.pointer, size); + memcpy(p, capdata, size); } return 0; @@ -670,6 +672,11 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return __lwmi_cd_cache(priv); } +struct lwmi_cd_fan_block { + u32 nr; + u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ +} __packed; + /** * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list * @priv: lenovo-wmi-capdata driver data. @@ -678,43 +685,29 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) */ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) { + struct wmi_buffer wbuf; struct cd_list *list; - size_t size; + int ret, idx; u32 count; - int idx; - /* Emit unaligned access to u8 buffer with __packed. */ - struct cd_fan_block { - u32 nr; - u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ - } __packed * block; + ret = wmidev_query_block(priv->wdev, 0, &wbuf, sizeof(struct lwmi_cd_fan_block)); + if (ret == -ENODATA) /* The block is too short, probably stubbed. */ + return 0; + if (ret) + return ret; - union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0); - if (!ret_obj) - return -ENODEV; + struct lwmi_cd_fan_block *block __free(kfree) = wbuf.data; - if (ret_obj->type == ACPI_TYPE_BUFFER) { - block = (struct cd_fan_block *)ret_obj->buffer.pointer; - size = ret_obj->buffer.length; + count = block->nr; - count = size >= sizeof(*block) ? block->nr : 0; - if (size < struct_size(block, data, count * 3)) { - dev_warn(&priv->wdev->dev, - "incomplete fan test data block: %zu < %zu, ignoring\n", - size, struct_size(block, data, count * 3)); - count = 0; - } else if (count > U8_MAX) { - dev_warn(&priv->wdev->dev, - "too many fans reported: %u > %u, truncating\n", - count, U8_MAX); - count = U8_MAX; - } - } else { - /* - * This is usually caused by a dummy ACPI method. Do not return an error - * as failing to probe this device will result in sub-master device being - * unbound. This behavior aligns with lwmi_cd_cache(). - */ + if (count > U8_MAX) { + dev_warn(&priv->wdev->dev, + "too many fans reported: %u > %u, ignoring\n", count, U8_MAX); + count = 0; + } else if (wbuf.length < struct_size(block, data, count * 3)) { + dev_warn(&priv->wdev->dev, + "incomplete fan test data block: %zu < %zu (%u fans), ignoring\n", + wbuf.length, struct_size(block, data, count * 3), count); count = 0; } @@ -729,11 +722,10 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) priv->list = list; for (idx = 0; idx < count; idx++) { - /* Do not calculate array index using count, as it may be truncated. */ list->cd_fan[idx] = (struct capdata_fan) { .id = block->data[idx], - .max_rpm = block->data[idx + block->nr], - .min_rpm = block->data[idx + (2 * block->nr)], + .max_rpm = block->data[idx + count], + .min_rpm = block->data[idx + (2 * count)], }; } -- cgit v1.2.3 From 952f7fcb161aefcbd6528e34a43932be1f64d839 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:30 +0800 Subject: platform/x86: lenovo-wmi-capdata: Register component even on WMI error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices do not support LENOVO_CAPABILITY_DATA_01 and define the query method as a stub that returns zero buffer. Unfortunately, some devices do not implement the stub properly, causing WMI errors (including ACPI errors). The current lenovo-wmi-* implementation enforces the binding between LENOVO_CAPABILITY_DATA_00+01 and LENOVO_OTHER_MODE because of a limitation of the device component framework. When the capdata device bailing out due to a WMI error, lenovo-wmi-other becomes unbound and unable to provide firmware-attributes or hwmon device for the other functional capdata device. Therefore, errors must be non-fatal in order not to break the assumptions made by the device component framework. Stub the capdata device by releasing the capability data list in this case. After that, NULL list will be passed to lenovo-wmi-other on bind. The latter will provide whatever is available, or unbind the components if nothing is available. A stubbed capdata device releases or skips allocating most resources, e.g., the capability data list and the debugfs directory. The device itself is only used to satisfy the component dependency of lenovo-wmi- other and coordinate with the latter about the absence of the capability data. Reported-by: Charles Closes: https://msgid.link/CAKtz0s8UYRQYW_0bh=0TMx47Axm-W-muEay-r3rqUBS1NHMPVw@mail.gmail.com/ Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-8-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 94 +++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 0efe37644627..7310b72e8c2f 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +47,7 @@ #include #include #include +#include #include #include @@ -447,7 +450,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = { /* * lwmi_cd*_get_data - Get the data of the specified attribute * @list: The lenovo-wmi-capdata pointer to its cd_list struct. - * @attribute_id: The capdata attribute ID to be found. + * @attribute_id: The capdata attribute ID (non-zero) to be found. * @output: Pointer to a capdata* struct to return the data. * * Retrieves the capability data struct pointer for the given @@ -460,7 +463,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = { { \ u8 idx; \ \ - if (WARN_ON(!list)) \ + if (WARN_ON(!list || !attribute_id)) \ return -EINVAL; \ \ guard(mutex)(&list->list_mutex); \ @@ -595,6 +598,89 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) /* ======== WMI interface ======== */ +/** + * lwmi_cd_stub() - Stub the device by not providing any capability data + * @priv: lenovo-wmi-capdata driver data. + * @err: The occurred error. + * + * The Other Mode driver binds to both Capability Data 00 and 01. If either 00 + * or 01 fails to probe, the Other Mode device will fail to provide fw-attr or + * hwmon/power_supply_ext devices for the other functional capdata device. + * + * Therefore, errors must be non-fatal in order not to break the assumptions + * made by the device component framework, so that the Other Mode device can + * provide whatever is functional. + * + * After stubbing the device, a NULL list will be passed to the Other Mode + * device on bind. + * + * Return: 0 if the @err is suppressed, otherwise its propagated as is. + */ +static int lwmi_cd_stub(struct lwmi_cd_priv *priv, int err) +{ + /* + * No barrier is required in any possible code paths: + * - called from lwmi_cd_cache(): a proper barrier has been employed + * - called from lwmi_cd_fan_list_alloc_cache(): early setup does not + * require synchronization, and priv->initialized must be false + */ + bool initialized = READ_ONCE(priv->initialized); + + dev_warn(&priv->wdev->dev, "%s %s (%u items) due to error: %d\n", + initialized ? "clearing" : "stubbing", priv->info->name, + priv->list ? priv->list->count : 0, err); + + /* Simply print the warning message. */ + if (!priv->list) + return initialized ? err : 0; + + /* + * Stub the device on initialization errors. + * + * Note that if a transient error occurs during initialization, there is + * nothing we can do to re-enable the functionalities of the Other Mode + * driver even if a successful runtime notifier call making the relevant + * capdata emerge, as registering functionalities without the protection + * of the component framework is unsafe. Therefore, registering the + * notifier for a stubbed device is meaningless, and we don't need to + * distinguish transient and pernament errors during initialization. + * + * For runtime errors (see below), the registered functionalities + * (fw-attr) become temporarily unavailable due to the unavailablilty of + * the relevant capdata, and will recover after a successful runtime + * notifier call. + */ + if (!initialized) { + devm_kfree(&priv->wdev->dev, priv->list); + priv->list = NULL; + + return 0; + } + + /* + * Runtime errors are transient. Simply clear all cached data and + * propagate the error. + * + * Since a valid attribute id is never 0 (the firmware also untilize the + * fact to stub some capabilities according to platform metadata), + * clearing the cached data effectively makes all attributes temporarily + * unavailable until the next notifier call. + */ + + lockdep_assert_held(&priv->list->list_mutex); + + switch (priv->info->type) { + case LENOVO_CAPABILITY_DATA_01: + memset(priv->list->cd01, 0, + flex_array_size(priv->list, cd01, priv->list->count)); + break; + default: + unreachable(); + } + + return err; +} + /** * __lwmi_cd_cache() - Cache all WMI data block information locklessly * @priv: lenovo-wmi-capdata driver data. @@ -633,7 +719,7 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) if (ret == -ENODATA) /* The block is too short, probably stubbed. */ continue; if (ret) - return ret; + return lwmi_cd_stub(priv, ret); /* Capdata 01 is an extension to capdata 00. */ struct capdata00 *capdata __free(kfree) = wbuf.data; @@ -694,7 +780,7 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) if (ret == -ENODATA) /* The block is too short, probably stubbed. */ return 0; if (ret) - return ret; + return lwmi_cd_stub(priv, ret); /* Print the warning message. */ struct lwmi_cd_fan_block *block __free(kfree) = wbuf.data; -- cgit v1.2.3 From 41a2ea4df8f036c9420616f29deb8ef5744dfd65 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:31 +0800 Subject: platform/x86: lenovo-wmi-capdata: Detect stubbed capdata device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices may stub the capdata device's WMI query method because it doesn't support the relevant interfaces at all. For example, most ThinkBook devices doesn't support GameZone or Other Mode thermal tuning, so capdata 01 is useless and stubbed. Keeping the capability data list with empty data is meaningless and causes lenovo-wmi-other to call lwmi_cd*_get_data() to retrieve nonexistent capdata in vain. Therefore, stub the device and release (or skip allocating) needless resources, e.g., the capability data list and the debugfs directory. Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-9-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 7310b72e8c2f..b86bc7a57a04 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -691,6 +691,7 @@ static int lwmi_cd_stub(struct lwmi_cd_priv *priv, int err) */ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) { + bool got_data = false; size_t size; int idx; void *p; @@ -725,8 +726,31 @@ static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) struct capdata00 *capdata __free(kfree) = wbuf.data; memcpy(p, capdata, size); + + /* + * A valid attribute always has a non-zero id. IOW, a zero id + * implies a stubbed item (or the query method being stubbed). + * + * The stubbed item is still copied to the cache slot anyway, in + * case the firmware dynamically stubs the capdata item after + * switching Game Zone mode. In this manner, the previously + * exposed capdata can be void. + */ + if (capdata->id) + got_data = true; } + /* + * Some devices may stub the query method because it doesn't support the + * relevant interfaces at all. For example, most ThinkBook devices + * doesn't support Game Zone or Other Mode thermal tuning, so capdata 01 + * is useless and stubbed. + * + * Stub the device and release needless resources. + */ + if (!got_data) + return lwmi_cd_stub(priv, -ENODATA); + return 0; } @@ -772,6 +796,7 @@ struct lwmi_cd_fan_block { static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) { struct wmi_buffer wbuf; + bool got_data = false; struct cd_list *list; int ret, idx; u32 count; @@ -808,13 +833,21 @@ static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) priv->list = list; for (idx = 0; idx < count; idx++) { + if (!block->data[idx]) /* Ignore stub. */ + continue; + list->cd_fan[idx] = (struct capdata_fan) { .id = block->data[idx], .max_rpm = block->data[idx + count], .min_rpm = block->data[idx + (2 * count)], }; + + got_data = true; } + if (!got_data) + return lwmi_cd_stub(priv, -ENODATA); + return 0; } -- cgit v1.2.3 From 1e99760fb07fbe568d1163795ad3a31825952d45 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:32 +0800 Subject: platform/x86: lenovo-wmi-capdata: Do not match missing components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices do not support LENOVO_CAPABILITY_DATA_01. They either do not have the corresponding WMI GUID, or do not implement the query method. In both cases, the WMI core does not create the corresponding WMI device, leaving the component master (i.e., lenovo-wmi-other) waits for the missing component forever. Identify missing components using the new wmidev_exists() interface, and skip adding them to the match list, so that all components in the list must present, fulfilling the binding requirement. Reported-by: Navon John Lukose Closes: https://msgid.link/20260928190901.1369497-1-navonjohnlukose@gmail.com Suggested-by: Mark Pearson Link: https://msgid.link/9789f452-d7eb-4f1e-8a13-7335332193a7@app.fastmail.com Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-10-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-capdata.c | 41 +++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index b86bc7a57a04..1d44ff83e157 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -75,14 +76,19 @@ enum lwmi_cd_type { CD_TYPE_NONE = -1, }; -#define LWMI_CD_TABLE_ITEM(_type) \ - [_type] = { \ - .name = #_type, \ - .type = _type, \ +static const struct wmi_device_id lwmi_cd_id_table[]; + +/* Save some .rodata space by reusing the GUID strings from the id table. */ +#define LWMI_CD_TABLE_ITEM(_type) \ + [_type] = { \ + .name = #_type, \ + .guid_string = lwmi_cd_id_table[_type].guid_string, \ + .type = _type, \ } static const struct lwmi_cd_info { const char *name; + const char *guid_string; enum lwmi_cd_type type; } lwmi_cd_table[] = { LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), @@ -153,24 +159,49 @@ static int lwmi_cd_match(struct device *dev, void *type) * * Adds all component matches to the list stored in @matchptr for the @master * device. @matchptr must be initialized to NULL. + * + * Missing components are not added to the match list, or else the master waits + * for them forever. For some components, their existence can be determined by + * querying other components, so they are handled as sub-components and thus not + * matched here. For components with no existence declaration, wmidev_exists() + * is used to determine their existence. + * + * If all components are missing, ERR_PTR(-ENXIO) is returned through @matchptr. */ void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr) { - int i; + bool matched = false; + int ret, i; if (WARN_ON(*matchptr)) return; for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + guid_t guid; + /* Skip sub-components. */ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA) continue; + ret = guid_parse(lwmi_cd_table[i].guid_string, &guid); + if (ret < 0) { + *matchptr = ERR_PTR(ret); + return; + } + + if (!wmidev_exists(&guid)) + continue; + component_match_add(master, matchptr, lwmi_cd_match, (void *)&lwmi_cd_table[i].type); if (IS_ERR(*matchptr)) return; + + matched = true; } + + if (!matched) + *matchptr = ERR_PTR(-ENXIO); } EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); -- cgit v1.2.3 From 87a7c09a9d4092782820d2c28c09df1d85d4cd93 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 19 Oct 2026 23:02:33 +0800 Subject: platform/x86: lenovo-wmi-helpers: Adopt new WMI API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new WMI API supports multiple ACPI types by converting them into a unified buffer that satisfies alignment and size requirements. Adopt it to make our life easier. Note that the Windows WMI-ACPI driver converts all ACPI objects into a common buffer format, so adopting the new API intentionally accepts more ACPI types as a followup of commit 465dc9da8ff6 ("platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32"). Suggested-by: Armin Wolf Link: https://lore.kernel.org/r/f1787927-b655-4321-b9d9-bc12353c72db@gmx.de/ Reviewed-by: Armin Wolf Tested-by: Navon John Lukose Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20261019-lwmi-wmi-new-api-v5-11-71557d28d058@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/wmi-helpers.c | 61 ++++++++++++------------------- 1 file changed, 23 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c index 8f5766c391eb..687caa226263 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -24,9 +24,10 @@ #include #include #include -#include #include +#include + #include "wmi-helpers.h" /* Thermal mode notifier chain. */ @@ -42,49 +43,33 @@ static BLOCKING_NOTIFIER_HEAD(tm_chain_head); * @size: Length of the buffer. * @retval: Pointer for the return value to be assigned. * - * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI - * integer. Validates the return value type and assigns the value to the - * retval pointer. + * Calls the specified WMI method for Lenovo WMI devices that return a WMI + * integer. Validates the return value and assigns it to @retval when it's not + * NULL, otherwise the return value is not validated. * * Return: 0 on success, or an error code. */ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, unsigned char *buf, size_t size, u32 *retval) { - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer input = { size, buf }; - acpi_status status; - - status = wmidev_evaluate_method(wdev, instance, method_id, &input, - &output); - if (ACPI_FAILURE(status)) - return -EIO; - - union acpi_object *ret_obj __free(kfree) = output.pointer; - - if (retval) { - if (!ret_obj) - return -ENODATA; - - switch (ret_obj->type) { - /* - * The ACPI method may simply return a buffer when a u32 - * is expected. This is valid on Windows as its WMI-ACPI - * driver converts everything to a common buffer. - */ - case ACPI_TYPE_BUFFER: - if (ret_obj->buffer.length < sizeof(u32)) - return -ENXIO; - - *retval = get_unaligned_le32(ret_obj->buffer.pointer); - return 0; - case ACPI_TYPE_INTEGER: - *retval = (u32)ret_obj->integer.value; - return 0; - default: - return -ENXIO; - } - } + struct wmi_buffer input = { + .length = size, + .data = buf, + }; + struct wmi_buffer output; + int ret; + + if (!retval) + return wmidev_invoke_procedure(wdev, instance, method_id, &input); + + ret = wmidev_invoke_method(wdev, instance, method_id, &input, + &output, sizeof(__le32)); + if (ret) + return ret; + + __le32 *valp __free(kfree) = output.data; + + *retval = le32_to_cpu(*valp); return 0; }; -- cgit v1.2.3 From db3a9dddab5f34fd3e6fd23271db37e56e75d27d Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 15 Oct 2026 10:55:30 +0200 Subject: platform: arm64: Add a driver for the EC found on ASUS Glymur machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Qualcomm Snapdragon X2 Elite (Extreme)-based ASUS laptops (at least the Zenbook A14 and A16) expose a custom, somewhat Qualcomm reference-derived EC interface. Introduce a driver to communicate with said EC, allowing for fan speed reporting, keyboard backlight setting and receiving of a number of miscellaneous sideband events. Assisted-by: LLM Signed-off-by: Konrad Dybcio [ij: add compiler.h and tweak initializer] Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20261015-topic-asus_ec-v4-2-af6305ffcb10@oss.qualcomm.com Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/Kconfig | 16 + drivers/platform/arm64/Makefile | 1 + drivers/platform/arm64/asus-glymur-ec.c | 617 ++++++++++++++++++++++++++++++++ 3 files changed, 634 insertions(+) create mode 100644 drivers/platform/arm64/asus-glymur-ec.c (limited to 'drivers') diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index de45c5e101a4..2a84775a13a9 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -33,6 +33,22 @@ config EC_ACER_ASPIRE1 laptop where this information is not properly exposed via the standard ACPI devices. +config EC_ASUS_GLYMUR + tristate "ASUS Zenbook A16 (Qualcomm) Embedded Controller driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on I2C + depends on HWMON + depends on INPUT + depends on PM + depends on THERMAL || THERMAL=n + select NEW_LEDS + select LEDS_CLASS + help + Say M or Y here to enable the EC driver for the Qualcomm Snapdragon- + based ASUS Zenbook A16 (UX3607OA) and similar machines. It provides + temperature sensors readout, fan monitoring, keyboard and EC event + reporting. + config EC_HUAWEI_GAOKUN tristate "Huawei Matebook E Go Embedded Controller driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile index 605cca4d42a9..3bc3d4669c27 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -6,6 +6,7 @@ # obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o +obj-$(CONFIG_EC_ASUS_GLYMUR) += asus-glymur-ec.o obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o diff --git a/drivers/platform/arm64/asus-glymur-ec.c b/drivers/platform/arm64/asus-glymur-ec.c new file mode 100644 index 000000000000..d75923fac9c4 --- /dev/null +++ b/drivers/platform/arm64/asus-glymur-ec.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The EC firmware on the ASUS Zenbook A16 looks to be derived from the + * 'Qualcomm Hamoa EC' reference base with many major modifications. Some of + * the original interface is reused, albeit many of its core invariants (such + * as the capabilities bitmap) are absent. + * + * It presents itself as two I2C devices: + * [0x5b] is the custom ASUS interface + * [0x76] is the Hamoa EC-derived interface + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Qualcomm-derived registers (main device @ 0x76) */ +#define ASUS_QCOM_EC_EVENT_CMD 0x05 + #define ASUS_QCOM_EC_EVENT_NONE 0x00 + #define ASUS_QCOM_EC_EVENT_INVALID 0xff + +#define ASUS_QCOM_EC_SUBCMD_FULL_VER 0x0f +#define ASUS_QCOM_EC_FULL_VER_LEN 7 + +/* This "read RAM" command isn't actually part of the Qualcomm standard */ +#define ASUS_QCOM_EC_RECM_OFFS 0x52 + +#define ASUS_QCOM_EC_RAM_FAN_CPU 0x0602 +#define ASUS_QCOM_EC_RAM_AC_STATUS 0x0609 +#define ASUS_QCOM_EC_RAM_FAN_GPU 0x0624 + +#define ASUS_QCOM_EC_MODERN_STANDBY_CMD 0x23 +#define ASUS_QCOM_EC_MODERN_STANDBY_ENTER 0x07 +#define ASUS_QCOM_EC_MODERN_STANDBY_EXIT 0x08 + +/* ASUS-specific registers (secondary device @ 0x5b) */ +#define ASUS_EC_SUBDEV_ADDR 0x5b + +#define ASUS_EC_REQ_REG 0x10 +#define ASUS_EC_RESP_REG 0x11 + +#define ASUS_EC_PAGE_MAILBOX 0xc4 + #define ASUS_EC_MBOX_REG_CMD 0x30 + #define ASUS_EC_MBOX_REG_SUBCMD 0x31 + #define ASUS_EC_MBOX_REG_DATA 0x32 + +#define ASUS_EC_MBOX_CMD_KBD 0x01 + #define ASUS_EC_MBOX_SUBCMD_KBD_EN 0x81 + #define ASUS_EC_MBOX_DATA_KBD_EN 0x04 + #define ASUS_EC_MBOX_SUBCMD_KBD_LVL 0x87 + +#define ASUS_EC_MBOX_CMD_MISC 0x02 + #define ASUS_EC_MBOX_SUBCMD_MISC_ENABLE 0x83 + +#define ASUS_EC_PAGE_TEMPERATURE 0xc6 + #define ASUS_EC_TEMP_REG_CPU 0xa6 + #define ASUS_EC_TEMP_REG_SOC 0x2a + #define ASUS_EC_TEMP_MAX 0x7f + +#define ASUS_EC_KBD_MAX_BRIGHTNESS 3 + +static const u8 asus_ec_kbd_levels[] = { + [0] = 0x00, + [1] = 0x55, + [2] = 0xaa, + [3] = 0xff, +}; + +enum asus_ec_event { + ASUS_EC_EVT_CHARGE_STATE = 0x1c, + ASUS_EC_EVT_AC_STATE = 0x1d, + ASUS_EC_EVT_CRIT_TRIP = 0x1e, + ASUS_EC_EVT_THERMISTOR = 0x4f, + ASUS_EC_EVT_HOTKEY = 0xda, + ASUS_EC_EVT_FAN_STATUS = 0xdb, + ASUS_EC_EVT_THERMAL_TRIP = 0xe0, + ASUS_EC_EVT_CHARGE_COMMIT = 0xe4, +}; + +struct asus_ec { + struct i2c_client *client; + struct i2c_client *subdev_client; + /* Serializes all EC access across both I2C devices */ + struct mutex lock; + struct device *hwmon_dev; + struct led_classdev kbd_led; +}; + +/* RAM addresses are sent MSB first, while returned data is LSB first. */ +struct asus_qcom_ec_ram_request { + __be16 addr; + u8 count; + u8 reserved[2]; +} __packed; + +struct asus_qcom_ec_ram_response { + __le16 data; + u8 unused[3]; +} __packed; + +static int asus_qcom_ec_write(struct asus_ec *ec, u8 offset, + const u8 *buf, u8 len) +{ + int ret; + + lockdep_assert_held(&ec->lock); + + ret = i2c_smbus_write_i2c_block_data(ec->client, offset, len, buf); + if (ret < 0) { + dev_dbg(&ec->client->dev, + "Main device write off %#x len %u failed: %d\n", + offset, len, ret); + } + + return ret; +} + +static int asus_qcom_ec_read(struct asus_ec *ec, u8 offset, u8 *buf, u8 len) +{ + int ret; + + lockdep_assert_held(&ec->lock); + + ret = i2c_smbus_read_i2c_block_data(ec->client, offset, len, buf); + if (ret < 0) + return ret; + if (ret < len) { + dev_err(&ec->client->dev, + "Short read off %#x: got %d of %u bytes\n", + offset, ret, len); + return -EIO; + } + + return 0; +} + +static int asus_qcom_ec_read_ram(struct asus_ec *ec, u16 addr, u8 count, u16 *out) +{ + struct asus_qcom_ec_ram_request req = { + .addr = cpu_to_be16(addr), + .count = count, + .reserved = {}, + }; + struct asus_qcom_ec_ram_response resp = { }; + int ret; + + lockdep_assert_held(&ec->lock); + + if (count != 1 && count != 2) + return -EINVAL; + + ret = asus_qcom_ec_write(ec, ASUS_QCOM_EC_RECM_OFFS, + (const u8 *)&req, sizeof(req)); + if (ret < 0) + return ret; + + ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_RECM_OFFS, + (u8 *)&resp, sizeof(resp)); + if (ret < 0) + return ret; + + if (count == 2) + *out = le16_to_cpu(resp.data); + else + *out = (u8)le16_to_cpu(resp.data); + + return 0; +} + +static int asus_ec_readb(struct asus_ec *ec, u8 page, u8 reg, u8 *out) +{ + u8 sel[2] = { page, reg }; + int ret; + + lockdep_assert_held(&ec->lock); + + ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG, + sizeof(sel), sel); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(ec->subdev_client, ASUS_EC_RESP_REG); + if (ret < 0) + return ret; + + *out = ret; + + return 0; +} + +static int asus_ec_writeb(struct asus_ec *ec, u8 page, u8 reg, u8 data) +{ + u8 sel[2] = { page, reg }; + int ret; + + lockdep_assert_held(&ec->lock); + + ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG, + sizeof(sel), sel); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(ec->subdev_client, + ASUS_EC_RESP_REG, data); +} + +static int asus_ec_wait_mailbox(struct asus_ec *ec) +{ + int ret, tries; + u8 status; + + lockdep_assert_held(&ec->lock); + + for (tries = 5; tries > 0; tries--) { + ret = asus_ec_readb(ec, ASUS_EC_PAGE_MAILBOX, + ASUS_EC_MBOX_REG_CMD, &status); + if (ret < 0) + return ret; + if (!status) + return 0; + usleep_range(5000, 6000); + } + + return -ETIMEDOUT; +} + +static int asus_ec_mailbox_cmd(struct asus_ec *ec, u8 cmd, u8 sub, u8 data) +{ + int ret; + + lockdep_assert_held(&ec->lock); + + ret = asus_ec_wait_mailbox(ec); + if (ret < 0) + return ret == -ETIMEDOUT ? -EBUSY : ret; + + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_SUBCMD, sub); + if (ret < 0) + return ret; + + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_DATA, data); + if (ret < 0) + return ret; + + ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_CMD, cmd); + if (ret < 0) + return ret; + + return asus_ec_wait_mailbox(ec); +} + +static int asus_ec_enable_writes(struct asus_ec *ec) +{ + guard(mutex)(&ec->lock); + + return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_MISC, + ASUS_EC_MBOX_SUBCMD_MISC_ENABLE, 1); +} + +static int asus_ec_read_version(struct asus_ec *ec, u8 *major, u8 *minor, u8 *patch) +{ + u8 resp[ASUS_QCOM_EC_FULL_VER_LEN + 1]; + int ret; + + guard(mutex)(&ec->lock); + + ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_SUBCMD_FULL_VER, + resp, sizeof(resp)); + if (ret < 0) + return ret; + if (resp[0] < ASUS_QCOM_EC_FULL_VER_LEN) + return -EIO; + + *major = resp[3]; + *minor = resp[2]; + *patch = resp[1]; + + return 0; +} + +static int asus_ec_get_temp(struct asus_ec *ec, u8 reg, long *millideg) +{ + u8 raw; + int ret; + + guard(mutex)(&ec->lock); + + ret = asus_ec_readb(ec, ASUS_EC_PAGE_TEMPERATURE, reg, &raw); + if (ret < 0) + return ret; + if (raw > ASUS_EC_TEMP_MAX) + return -ENODATA; + + *millideg = raw * MILLIDEGREE_PER_DEGREE; + + return 0; +} + +static int asus_ec_get_fan_rpm(struct asus_ec *ec, u16 base, long *rpm) +{ + u16 val; + int ret; + + guard(mutex)(&ec->lock); + + ret = asus_qcom_ec_read_ram(ec, base, 2, &val); + if (ret < 0) + return ret; + + *rpm = val; + + return 0; +} + +static irqreturn_t asus_ec_irq(int irq, void *data) +{ + struct asus_ec *ec = data; + struct device *dev = &ec->client->dev; + int code; + + scoped_guard(mutex, &ec->lock) + code = i2c_smbus_read_byte_data(ec->client, ASUS_QCOM_EC_EVENT_CMD); + + if (code < 0) { + dev_err_ratelimited(dev, "Failed to read EC event: %d\n", code); + return IRQ_HANDLED; + } + if (code == ASUS_QCOM_EC_EVENT_NONE || + code == ASUS_QCOM_EC_EVENT_INVALID) + return IRQ_HANDLED; + + switch (code) { + case ASUS_EC_EVT_HOTKEY: + /* Present in DSDT, don't know what triggers it */ + dev_dbg_ratelimited(dev, "EC hotkey event\n"); + break; + + case ASUS_EC_EVT_FAN_STATUS: + dev_dbg_ratelimited(dev, "Fan status changed\n"); + break; + + /* Maybe the temperature of the EC itself? */ + case ASUS_EC_EVT_THERMAL_TRIP: + case ASUS_EC_EVT_CRIT_TRIP: + /* ASUS_EC_TEMP_SOC climbing above 91 *C or falling below 92 *C */ + case ASUS_EC_EVT_THERMISTOR: + dev_dbg_ratelimited(dev, "Thermal event %#x\n", code); + break; + + case ASUS_EC_EVT_AC_STATE: { + u16 ac = 0; + int ret; + + /* TODO: ping SoCCP over the pmic_glink OEM-specific interface */ + scoped_guard(mutex, &ec->lock) + ret = asus_qcom_ec_read_ram(ec, ASUS_QCOM_EC_RAM_AC_STATUS, 1, &ac); + if (ret < 0) { + dev_warn_ratelimited(dev, + "Failed to read AC status: %d\n", + ret); + break; + } + dev_dbg_ratelimited(dev, "AC event: 0x%x\n", ac); + break; + } + case ASUS_EC_EVT_CHARGE_STATE: + case ASUS_EC_EVT_CHARGE_COMMIT: + break; + + default: + dev_info_ratelimited(dev, "unknown EC event: %#x\n", code); + break; + } + + return IRQ_HANDLED; +} + +static int asus_ec_kbd_led_set(struct led_classdev *led, + enum led_brightness brightness) +{ + struct asus_ec *ec = container_of(led, struct asus_ec, kbd_led); + int ret; + + if (brightness > ASUS_EC_KBD_MAX_BRIGHTNESS) + brightness = ASUS_EC_KBD_MAX_BRIGHTNESS; + + guard(mutex)(&ec->lock); + + ret = asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_EN, + ASUS_EC_MBOX_DATA_KBD_EN); + if (ret < 0) + return ret; + + return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_LVL, + asus_ec_kbd_levels[brightness]); +} + +enum asus_ec_temp_channel { + /* External sensor? */ + ASUS_EC_TEMP_CPU, + /* Likely SoC-internal temp data communicated over a side channel */ + ASUS_EC_TEMP_SOC, + ASUS_EC_TEMP_COUNT, +}; + +static umode_t asus_ec_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_fan: + case hwmon_temp: + return 0444; + default: + return 0; + } +} + +static int asus_ec_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct asus_ec *ec = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + switch (channel) { + case ASUS_EC_TEMP_CPU: + return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_CPU, val); + case ASUS_EC_TEMP_SOC: + return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_SOC, val); + default: + return -EINVAL; + } + case hwmon_fan: + switch (channel) { + case 0: + return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_CPU, val); + case 1: + return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_GPU, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int asus_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type != hwmon_temp || channel >= ASUS_EC_TEMP_COUNT) + return -EINVAL; + + switch (channel) { + case ASUS_EC_TEMP_CPU: + *str = "CPU"; + break; + case ASUS_EC_TEMP_SOC: + *str = "SoC"; + break; + } + + return 0; +} + +static const struct hwmon_channel_info * const asus_ec_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_ops asus_ec_hwmon_ops = { + .is_visible = asus_ec_hwmon_is_visible, + .read = asus_ec_hwmon_read, + .read_string = asus_ec_hwmon_read_string, +}; + +static const struct hwmon_chip_info asus_ec_hwmon_chip_info = { + .ops = &asus_ec_hwmon_ops, + .info = asus_ec_hwmon_info, +}; + +static int asus_glymur_ec_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 major, minor, patch; + struct asus_ec *ec; + int ret; + + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); + if (!ec) + return -ENOMEM; + + ec->client = client; + i2c_set_clientdata(client, ec); + + ret = devm_mutex_init(dev, &ec->lock); + if (ret) + return ret; + + ec->subdev_client = devm_i2c_new_dummy_device(dev, client->adapter, + ASUS_EC_SUBDEV_ADDR); + if (IS_ERR(ec->subdev_client)) { + return dev_err_probe(dev, PTR_ERR(ec->subdev_client), + "Failed to register 0x5b EC device\n"); + } + + ret = asus_ec_enable_writes(ec); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable EC direct access: %d\n", ret); + + ret = asus_ec_read_version(ec, &major, &minor, &patch); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read EC version: %d\n", ret); + + dev_dbg(dev, "ASUS Zenbook EC version %u.%u.%u\n", major, minor, patch); + + ec->kbd_led.name = "asus::kbd_backlight"; + ec->kbd_led.max_brightness = ASUS_EC_KBD_MAX_BRIGHTNESS; + ec->kbd_led.brightness_set_blocking = asus_ec_kbd_led_set; + ec->kbd_led.flags = LED_CORE_SUSPENDRESUME; + + ret = devm_led_classdev_register(dev, &ec->kbd_led); + if (ret) + return dev_err_probe(dev, ret, "Failed to register keyboard LED\n"); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, asus_ec_irq, + IRQF_ONESHOT, dev_name(dev), ec); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ\n"); + + ec->hwmon_dev = devm_hwmon_device_register_with_info(dev, + "asus_glymur_ec", + ec, + &asus_ec_hwmon_chip_info, + NULL); + if (IS_ERR(ec->hwmon_dev)) { + return dev_err_probe(dev, PTR_ERR(ec->hwmon_dev), + "Failed to register hwmon\n"); + } + + return 0; +} + +static int asus_glymur_ec_modern_standby(struct asus_ec *ec, u8 val) +{ + guard(mutex)(&ec->lock); + + return i2c_smbus_write_byte_data(ec->client, + ASUS_QCOM_EC_MODERN_STANDBY_CMD, val); +} + +static int asus_glymur_ec_suspend(struct device *dev) +{ + return asus_glymur_ec_modern_standby(dev_get_drvdata(dev), + ASUS_QCOM_EC_MODERN_STANDBY_ENTER); +} + +static int asus_glymur_ec_resume(struct device *dev) +{ + return asus_glymur_ec_modern_standby(dev_get_drvdata(dev), + ASUS_QCOM_EC_MODERN_STANDBY_EXIT); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(asus_glymur_ec_pm_ops, + asus_glymur_ec_suspend, + asus_glymur_ec_resume); + +static const struct of_device_id asus_glymur_ec_of_match[] = { + { .compatible = "asus,zenbook-a14-ux3407na-ec" }, + { .compatible = "asus,zenbook-a16-ux3607oa-ec" }, + {} +}; +MODULE_DEVICE_TABLE(of, asus_glymur_ec_of_match); + +static const struct i2c_device_id asus_glymur_ec_id[] = { + { "asus-glymur-ec" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, asus_glymur_ec_id); + +static struct i2c_driver asus_glymur_ec_driver = { + .driver = { + .name = "asus-glymur-ec", + .of_match_table = asus_glymur_ec_of_match, + .pm = pm_sleep_ptr(&asus_glymur_ec_pm_ops), + }, + .probe = asus_glymur_ec_probe, + .id_table = asus_glymur_ec_id, +}; +module_i2c_driver(asus_glymur_ec_driver); + +MODULE_DESCRIPTION("ASUS Zenbook A16 (Qualcomm) Embedded Controller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From c21bef518bc023dfac0f22c9a63a813706e67639 Mon Sep 17 00:00:00 2001 From: Patryk Pilichowski Date: Tue, 20 Oct 2026 16:14:26 +0200 Subject: platform/x86: msi-wmi: Sort scancodes numerically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scancode enum is grouped by model, so the values are not in order and are hard to follow. The grouping is not meaningful either, as the same event codes are used across MSI models. Sort the enum by scancode, give every entry an explicit value and use lowercase hex. No functional change. Suggested-by: Ilpo Järvinen Signed-off-by: Patryk Pilichowski Link: https://patch.msgid.link/20261020141427.1580282-2-pat.pilichowski@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 05c35133a1b3..5369ac28f4c5 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -33,25 +33,22 @@ MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID); MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); enum msi_scancodes { - /* Generic MSI keys (not present on MSI Wind) */ - MSI_KEY_BRIGHTNESSUP = 0xD0, - MSI_KEY_BRIGHTNESSDOWN, - MSI_KEY_VOLUMEUP, - MSI_KEY_VOLUMEDOWN, - MSI_KEY_MUTE, - /* MSI Wind keys */ WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */ - WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ - WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */ - WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ - WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ - WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ - /* MSI Claw keys */ CLAW_KEY_VOLUMEDOWN = 0x21, CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ CLAW_KEY_VOLUMEUP = 0x32, + WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ + WIND_KEY_CAMERA = 0x57, /* Fn+F6 webcam toggle */ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO = 0x60, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + MSI_KEY_BRIGHTNESSUP = 0xd0, + MSI_KEY_BRIGHTNESSDOWN = 0xd1, + MSI_KEY_VOLUMEUP = 0xd2, + MSI_KEY_VOLUMEDOWN = 0xd3, + MSI_KEY_MUTE = 0xd4, }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, -- cgit v1.2.3 From 34f5b0ef61f6366b2bdc9f6d111bc5b225be004f Mon Sep 17 00:00:00 2001 From: Patryk Pilichowski Date: Tue, 20 Oct 2026 16:14:27 +0200 Subject: platform/x86: msi-wmi: Ignore WMI events for keys also handled by atkbd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On MSI notebooks, several keys generate a WMI event through the Wind event GUID in addition to a regular AT keyboard event. Their event codes are not in the keymap, so every press logs "Unknown key pressed". These keys already work through the AT keyboard, so mark their WMI codes as KE_IGNORE, as is already done for the MSI Claw. On models where a code does not also produce a keyboard event, the event is already dropped, so the only change there is that the log message goes away. The Wind event GUID is not specific to the MSI Wind. It is present in 17 of 18 MSI notebooks in the linuxhw ACPI dump collection (2012 to 2024), with the same _WED structure. The codes are shared across models too: the volume keys on the tested notebook send the same codes as the MSI Claw (0x21, 0x32). Tested on an MSI Katana 15 B13VFK: Fn (0x0b), mic mute (0x19), settings (0x1d), mute (0x23) and Bluetooth (0x78) no longer log unknown key messages, and the keys keep working through atkbd. Link: https://github.com/linuxhw/ACPI/tree/d6d30c6ef6f4/Notebook/MSI Signed-off-by: Patryk Pilichowski Link: https://patch.msgid.link/20261020141427.1580282-3-pat.pilichowski@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 5369ac28f4c5..dc56e8da0cdb 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -34,7 +34,11 @@ MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); enum msi_scancodes { WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */ + WIND_KEY_FN_NB = 0x0b, + WIND_KEY_MICMUTE_NB = 0x19, + WIND_KEY_SETTINGS_NB = 0x1d, CLAW_KEY_VOLUMEDOWN = 0x21, + WIND_KEY_MUTE_NB = 0x23, CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ CLAW_KEY_VOLUMEUP = 0x32, @@ -44,6 +48,7 @@ enum msi_scancodes { WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ WIND_KEY_TURBO = 0x60, /* Fn+F10 turbo mode toggle */ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + WIND_KEY_BLUETOOTH_NB = 0x78, MSI_KEY_BRIGHTNESSUP = 0xd0, MSI_KEY_BRIGHTNESSDOWN = 0xd1, MSI_KEY_VOLUMEUP = 0xd2, @@ -63,6 +68,13 @@ static struct key_entry msi_wmi_keymap[] = { { KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} }, { KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} }, + /* These MSI Notebook keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, WIND_KEY_FN_NB }, + { KE_IGNORE, WIND_KEY_MICMUTE_NB }, + { KE_IGNORE, WIND_KEY_SETTINGS_NB }, + { KE_IGNORE, WIND_KEY_MUTE_NB }, + { KE_IGNORE, WIND_KEY_BLUETOOTH_NB }, + /* These are unknown WMI events found on MSI Wind */ { KE_IGNORE, 0x00 }, { KE_IGNORE, 0x62 }, -- cgit v1.2.3 From 148794d9f0d29e25283978302c41533b81b20b8c Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Sun, 18 Oct 2026 09:06:03 -0700 Subject: platform/x86: asus-armoury: Add XBOX ROG Ally X 20 Tunables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Tunable table for XBOX ROG Ally X 20. Signed-off-by: Derek J. Clark Link: https://patch.msgid.link/20261018160603.65449-1-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 5792210b75bd..8f88a979c3d6 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -2697,6 +2697,32 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC74XA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 13, + .ppt_pl2_sppt_max = 45, + .ppt_pl3_fppt_min = 19, + .ppt_pl3_fppt_max = 55, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 7, + .ppt_pl1_spl_def = 17, + .ppt_pl1_spl_max = 35, + .ppt_pl2_sppt_min = 13, + .ppt_pl2_sppt_def = 21, + .ppt_pl2_sppt_max = 45, + .ppt_pl3_fppt_min = 19, + .ppt_pl3_fppt_def = 26, + .ppt_pl3_fppt_max = 55, + }, + }, + }, {} }; -- cgit v1.2.3 From 9962770049c715fc30a7b3274d8f245167c0a410 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 17 Oct 2026 23:33:53 -0400 Subject: platform/x86: gpd pocket fan: Cancel work before releasing GPIOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fan worker changes the direction of both GPIOs and schedules its next run. Its managed cancellation action is registered before the GPIOs are acquired, so devres releases the GPIOs before cancelling the work. A running worker can therefore change a GPIO after the driver has released it. Register the work cancellation action after both GPIO acquisitions. This makes devres cancel and drain the worker before releasing either GPIO. No work has been queued yet if GPIO acquisition or action registration fails. This issue was identified as part of our ongoing research while reviewing kernel code. Fixes: b82a7b018b93 ("platform/x86: gpd pocket fan: Clean-up by using managed work init") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/179229442299.3956787.17526066623947466315.gpd-pocket-fan@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/gpd-pocket-fan.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c index c9236738f896..ced93795c5d4 100644 --- a/drivers/platform/x86/gpd-pocket-fan.c +++ b/drivers/platform/x86/gpd-pocket-fan.c @@ -153,10 +153,6 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev) return -ENOMEM; fan->dev = &pdev->dev; - ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work, - gpd_pocket_fan_worker); - if (ret) - return ret; /* Note this returns a "weak" reference which we don't need to free */ fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0"); @@ -175,6 +171,11 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev) if (IS_ERR(fan->gpio1)) return PTR_ERR(fan->gpio1); + ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work, + gpd_pocket_fan_worker); + if (ret) + return ret; + gpd_pocket_fan_force_update(fan); platform_set_drvdata(pdev, fan); -- cgit v1.2.3 From e931e54edb0fae350007c4054cfbf095cc316bd8 Mon Sep 17 00:00:00 2001 From: Krishna Chomal Date: Sat, 17 Oct 2026 18:54:21 +0530 Subject: platform/x86: hp-wmi: Disable hwmon attributes when no fans are usable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, hp_wmi_hwmon_is_visible() checks whether fan RPM queries succeed before exposing fan1_input and fan2_input, but pwm1_enable remains visible always, even when fan support is unavailable. On some devices, such as certain Omnibook models, the firmware does not provide functional WMI fan methods. Exposing pwm1_enable on these devices can mislead userspace and cause fan mode changes to fail. Additionally, some firmware implementations provide stub methods that return an all-zero buffer instead of an error. In this case, hp_wmi_get_active_fan_speed() and hp_wmi_get_fan_speed() can report 0 RPM even when the fan is physically spinning. Use the result of fan count query (fetched from firmware once in hp_wmi_setup_fan_settings(), and cached in hwmon private data) to determine whether fan related attributes should be exposed. The fan count query can have the following outcomes: * Negative: The WMI query failed. Other fan-related queries are also expected to fail, so disable fan support. * Zero (stub firmware): The methods return zero-filled responses without providing functional fan support, so disable fan support. * Zero (genuine firmware): No fans are exposed through WMI, so disable fan support. * Positive (1 or 2): Firmware reports one or more fans, so allow fan support. Per-channel RPM queries continue to determine which individual fan input attributes are exposed. When fan support is unavailable, hide the fan and PWM attributes and skip applying the initial fan settings. Tested on: HP OmniBook 5 Laptop 16-ag1xxx (board: 8DF4) Tested-by: Lorenzo Collevecchio Reported-by: Lorenzo Collevecchio Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222138 Signed-off-by: Krishna Chomal Link: https://patch.msgid.link/20261017132421.279522-1-krishna.chomal108@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 0eac5e1d9cdb..52077833ac89 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -651,6 +651,7 @@ enum pwm_modes { struct hp_wmi_hwmon_priv { struct mutex lock; /* protects mode, pwm */ + int fan_count; u8 min_rpm; u8 max_rpm; u8 mode; @@ -2950,12 +2951,18 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct hp_wmi_hwmon_priv *priv = data; + switch (type) { case hwmon_pwm: + if (priv->fan_count <= 0) + return 0; if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) return 0; return 0644; case hwmon_fan: + if (priv->fan_count <= 0) + return 0; if (hp_wmi_fan_control_supported()) { if (hp_wmi_get_active_fan_speed(channel) >= 0) return 0444; @@ -3124,6 +3131,7 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) /* Default behaviour on hwmon init is automatic mode */ priv->mode = PWM_MODE_AUTO; + priv->fan_count = hp_wmi_get_fan_count_userdefine_trigger(); /* Bypass devices without fan control support. */ if (!hp_wmi_fan_table_supported()) @@ -3208,6 +3216,11 @@ static int hp_wmi_hwmon_init(void) return PTR_ERR(hwmon); } + if (priv->fan_count <= 0) { + dev_info(dev, "No usable fans reported by WMI, disabling fan support\n"); + return 0; + } + ret = hp_wmi_apply_fan_settings(priv); if (ret) dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret); -- cgit v1.2.3 From d92df6048d22799d04c98616cc42f0601c0edcab Mon Sep 17 00:00:00 2001 From: Dennis Nilsson Date: Sat, 17 Oct 2026 10:33:58 +0200 Subject: platform/x86: acer-wmi: Cycle platform profile on mode key 0x4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some Nitro models using the Predator Sense v4 interface, such as the Nitro AN16-41, the mode key raises WMID_GAMING_TURBO_KEY_EVENT with key_num 0x4 instead of 0x5. The driver routes key_num 0x4 to acer_toggle_turbo(), which returns immediately on machines without ACER_CAP_TURBO_LED because WMID_gaming_get_u64() rejects the missing capability, so the key press is silently dropped. Only toggle turbo for key_num 0x4 when the turbo LED capability is present, and cycle the platform profile otherwise. Machines with the turbo quirk keep their current behaviour, and on every other machine key_num 0x4 was previously a no-op. Tested on an Acer Nitro AN16-41 (BIOS V1.03), where the mode key sends the event payload 07 04 01 00 and now cycles the platform profile. Assisted-by: LLM Signed-off-by: Dennis Nilsson Link: https://patch.msgid.link/20261017-acer-nitro-an16-41-v1-1-c2e7a0ee7303@dennisnilsson.se Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 9e3758c570f3..323179c4340a 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -2564,8 +2564,13 @@ static void acer_wmi_notify(union acpi_object *obj, void *context) acer_kbd_dock_event(&return_value); break; case WMID_GAMING_TURBO_KEY_EVENT: - if (return_value.key_num == 0x4) - acer_toggle_turbo(); + if (return_value.key_num == 0x4) { + /* Models without a turbo button report their mode key as 0x4 */ + if (has_cap(ACER_CAP_TURBO_LED)) + acer_toggle_turbo(); + else if (has_cap(ACER_CAP_PLATFORM_PROFILE)) + acer_thermal_profile_change(); + } if (return_value.key_num == 0x5 && has_cap(ACER_CAP_PLATFORM_PROFILE)) acer_thermal_profile_change(); break; -- cgit v1.2.3 From 164de1f81266bcac60ccc1580b02241447047621 Mon Sep 17 00:00:00 2001 From: Dennis Nilsson Date: Sat, 17 Oct 2026 10:33:59 +0200 Subject: platform/x86: acer-wmi: Add support for Acer Nitro AN16-41 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Acer Nitro AN16-41 supports the Predator Sense v4 platform profile, hwmon sensors and fan control, but currently needs the predator_v4 module parameter to expose them. Add a DMI quirk for it, reusing the Nitro AN515-58 quirk entry since the feature set is the same. Tested on BIOS V1.03: all five platform profiles are offered by the firmware and switching them changes the fan curve, both fan speeds and the temperature sensors read correctly, and pwm1/pwm2 work in custom, maximum and automatic mode. Assisted-by: LLM Signed-off-by: Dennis Nilsson Link: https://patch.msgid.link/20261017-acer-nitro-an16-41-v1-2-c2e7a0ee7303@dennisnilsson.se Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 323179c4340a..3a018fdf5138 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -681,6 +681,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, .driver_data = &quirk_acer_travelmate_2490, }, + { + .callback = dmi_matched, + .ident = "Acer Nitro AN16-41", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN16-41"), + }, + .driver_data = &quirk_acer_nitro_an515_58, + }, { .callback = dmi_matched, .ident = "Acer Nitro AN515-58", -- cgit v1.2.3 From f1d81af2521ad504a20481fef12c358d26e6c3a6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Oct 2026 08:39:55 +0200 Subject: platform/x86: ibm_rtl: Fix potential OOB read at signature search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loop for signature search fetches 22 bytes of struct ibm_rtl_table at each check, while the loop count is calculated in 4 bytes unit, which means that, at the last entry, it fetches over the limit. Rewrite the loop to use the byte offset and correct the boundary condition check for avoiding the potential OOB access. Fixes: 15dd100349b8 ("platform/x86: ibm_rtl: fix EBDA signature search pointer arithmetic") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261021064012.457870-1-tiwai@suse.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/ibm_rtl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c index 139956168cf9..82fda8d9178b 100644 --- a/drivers/platform/x86/ibm_rtl.c +++ b/drivers/platform/x86/ibm_rtl.c @@ -234,9 +234,9 @@ static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = { }; static int __init ibm_rtl_init(void) { - unsigned long ebda_addr, ebda_size; + unsigned long ebda_addr, ebda_size, offset; unsigned int ebda_kb; - int ret = -ENODEV, i; + int ret = -ENODEV; if (force) pr_warn("module loaded by force\n"); @@ -271,9 +271,9 @@ static int __init ibm_rtl_init(void) { return -ENOMEM; /* search for the _RTL_ signature at the start of the table */ - for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { + for (offset = 0; offset + sizeof(*rtl_table) <= ebda_size; offset += 4) { struct ibm_rtl_table __iomem * tmp; - tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int)); + tmp = (struct ibm_rtl_table __iomem *) (ebda_map + offset); if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { phys_addr_t addr; unsigned int plen; -- cgit v1.2.3 From 1d1efe639126366408c36adb72e063559bda9daa Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:13 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for G713QR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS G713QR laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-2-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 8f88a979c3d6..f2dde15c0ba0 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1130,6 +1130,19 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G713QR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 15, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA401IHR"), -- cgit v1.2.3 From 43e909d10d5d5704efcd51d2b5c4659655efe8ab Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:14 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for FA608PM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS FA608PM laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-3-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index f2dde15c0ba0..5c502f16b6e9 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -787,6 +787,37 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA608PM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 15, + .nv_dynamic_boost_min = 10, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 100, + .nv_tgp_min = 55, + .ppt_pl1_spl_max = 100, + .ppt_pl1_spl_min = 45, + .ppt_pl2_sppt_max = 115, + .ppt_pl2_sppt_min = 60, + .ppt_pl3_fppt_max = 135, + .ppt_pl3_fppt_min = 80, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_max = 68, + .ppt_pl1_spl_min = 35, + .ppt_pl2_sppt_max = 71, + .ppt_pl2_sppt_min = 36, + .ppt_pl3_fppt_max = 74, + .ppt_pl3_fppt_min = 70, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA608UM"), -- cgit v1.2.3 From 8b2403c1a187fdf7170467822ac5d1deb8499ffa Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:15 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for FA608UH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS FA608UH laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-4-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 5c502f16b6e9..7b49838bbda0 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -818,6 +818,39 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA608UH"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 15, + .nv_dynamic_boost_min = 10, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 100, + .nv_tgp_min = 55, + .ppt_pl1_spl_max = 90, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 90, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 90, + .ppt_pl3_fppt_min = 35, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 35, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA608UM"), -- cgit v1.2.3 From 38146854b074a5f3f9b2bec6f6381f0c10b59840 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:16 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for FA608UP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS FA608UP laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-5-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 7b49838bbda0..3c91c130b070 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -887,6 +887,39 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA608UP"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 15, + .nv_dynamic_boost_min = 10, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 100, + .nv_tgp_min = 55, + .ppt_pl1_spl_max = 90, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 90, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 90, + .ppt_pl3_fppt_min = 35, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 65, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_def = 54, + .ppt_pl2_sppt_max = 65, + .ppt_pl2_sppt_min = 35, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 35, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA608WI"), -- cgit v1.2.3 From a2cc819aac6877258a8222ae40a3b349d844e2a9 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:17 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for GA402NV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS GA402NV laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-6-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 3c91c130b070..9c67915d5ce1 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1300,6 +1300,37 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GA402NV"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 25, + .nv_dynamic_boost_min = 5, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_def = 35, + .ppt_pl1_spl_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_def = 65, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 25, + .ppt_pl3_fppt_max = 80, + .ppt_pl3_fppt_min = 35, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_max = 35, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 35, + .ppt_pl2_sppt_min = 25, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 35, + }, + }, + }, { .matches = { // This model is full AMD. No Nvidia dGPU. -- cgit v1.2.3 From eb588e8506d0a8736d9176cccdf6b42af3a12c99 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:18 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for G533QS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS G533QS laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-7-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 9c67915d5ce1..ca8acd936285 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1227,6 +1227,19 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G533QS"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 15, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "G713QR"), -- cgit v1.2.3 From 61be8edd585af9390b3f967ac0d3e07b7dbbbfe1 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 21 Oct 2026 09:08:19 +0200 Subject: platform/x86: asus-armoury: add power limits quirk for GU605CM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a power limits quirk entry for the ASUS GU605CM laptop. The limits are extracted from the device's ThrottleGear XML configuration. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20261021070948.19916-8-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index ca8acd936285..331c3bddf013 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1729,6 +1729,37 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU605CM"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .nv_dynamic_boost_max = 15, + .nv_dynamic_boost_min = 5, + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .nv_tgp_max = 95, + .nv_tgp_min = 55, + .ppt_pl1_spl_def = 85, + .ppt_pl1_spl_max = 75, + .ppt_pl1_spl_min = 45, + .ppt_pl2_sppt_def = 110, + .ppt_pl2_sppt_max = 95, + .ppt_pl2_sppt_min = 56, + }, + .dc_data = &(struct power_limits) { + .nv_temp_target_max = 87, + .nv_temp_target_min = 75, + .ppt_pl1_spl_def = 85, + .ppt_pl1_spl_max = 75, + .ppt_pl1_spl_min = 25, + .ppt_pl2_sppt_def = 110, + .ppt_pl2_sppt_max = 95, + .ppt_pl2_sppt_min = 32, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605CP"), -- cgit v1.2.3 From 39b6fe1f4b98bc1cc935d38f4168611716e26086 Mon Sep 17 00:00:00 2001 From: Bartu Alev Date: Sat, 10 Oct 2026 13:15:57 +0300 Subject: platform/x86: asus-wmi: rename TUF RGB state DEVID to _CMD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-existing TUF RGB state DEVID (0x00100057) is a write-only command used with the DEVS method. Rename it to make clear it is a command rather than a readable state, avoiding confusion with the upcoming readback support. Suggested-by: Denis Benato Signed-off-by: Bartu Alev Link: https://patch.msgid.link/20261010101601.888709-2-bartualev@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index fb1d4a085089..d1341adc1b3b 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1151,7 +1151,7 @@ static ssize_t kbd_rgb_state_store(struct device *dev, /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, - ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL); + ASUS_WMI_DEVID_TUF_RGB_CMD, 0xbd | cmd << 8 | (flags << 16), 0, NULL); if (err) return err; @@ -5196,7 +5196,7 @@ static int asus_wmi_add(struct platform_device *pdev) asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU); asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU); - asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE); + asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_CMD); if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE)) asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; -- cgit v1.2.3 From 3d9a175707eeb15a2d577af1e669b98e0236719b Mon Sep 17 00:00:00 2001 From: Bartu Alev Date: Sat, 10 Oct 2026 13:15:58 +0300 Subject: platform/x86: asus-wmi: fix TUF keyboard power state shutdown naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fourth state flag of kbd_rgb_state_index is named "keyboard" and has long been treated as having no effect. In hardware the flag, BIT(7) of the payload sent to the TUF RGB state device, gates whether the keyboard backlight stays lit while the machine powers off: with the bit set the keyboard lights up white during the ACPI S5 sequence, with the bit clear the machine powers off dark. Verified by A/B testing both values on a TUF Gaming A15 (FA507NV). Rename the fourth field and its index entry from "keyboard" to "shutdown". The write format is unchanged, the value is still positional, and the diff is two variable names and one string, so it backports cleanly. Fixes: 61f64515299e ("platform/x86: asus-wmi: Implement TUF laptop keyboard power states") Cc: stable@vger.kernel.org Signed-off-by: Bartu Alev Link: https://patch.msgid.link/20261010101601.888709-3-bartualev@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index d1341adc1b3b..3348ce162ad3 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1130,10 +1130,10 @@ static ssize_t kbd_rgb_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - u32 flags, cmd, boot, awake, sleep, keyboard; + u32 flags, cmd, boot, awake, sleep, shutdown; int err; - if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5) + if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &shutdown) != 5) return -EINVAL; if (cmd) @@ -1146,7 +1146,7 @@ static ssize_t kbd_rgb_state_store(struct device *dev, flags |= BIT(3); if (sleep) flags |= BIT(5); - if (keyboard) + if (shutdown) flags |= BIT(7); /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */ @@ -1160,7 +1160,7 @@ static ssize_t kbd_rgb_state_store(struct device *dev, static DEVICE_ATTR_WO(kbd_rgb_state); static DEVICE_STRING_ATTR_RO(kbd_rgb_state_index, 0444, - "cmd boot awake sleep keyboard"); + "cmd boot awake sleep shutdown"); static struct attribute *kbd_rgb_state_attrs[] = { &dev_attr_kbd_rgb_state.attr, -- cgit v1.2.3 From 9d3475065167197742b0882aff21cf8e78393277 Mon Sep 17 00:00:00 2001 From: Bartu Alev Date: Sat, 10 Oct 2026 13:15:59 +0300 Subject: platform/x86: asus-wmi: name the TUF keyboard RGB state and speed codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TUF keyboard RGB attributes encode four power-state flags and three animation speeds as bare BIT() and hex literals inside their store handlers. The speed codes in particular read as arbitrary hex until one knows they are EC firmware conventions: 0xe1, 0xeb and 0xf5 select animation step thresholds of 30, 20 and 10 ticks per phase, which the EC derives from the stored byte through a lookup table. Give the seven values names and use them in kbd_rgb_mode_store() and kbd_rgb_state_store(), so the handlers state what they mean without a decoding comment. No behavior change. Suggested-by: Ilpo Järvinen Signed-off-by: Bartu Alev Link: https://patch.msgid.link/20261010101601.888709-4-bartualev@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3348ce162ad3..b33ebdb21aa2 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1058,6 +1058,10 @@ static DEVICE_ATTR_RW(gpu_mux_mode); #endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */ /* TUF Laptop Keyboard RGB Modes **********************************************/ + +#define TUF_KBD_RGB_SPEED_SLOW 0xe1 +#define TUF_KBD_RGB_SPEED_NORMAL 0xeb +#define TUF_KBD_RGB_SPEED_FAST 0xf5 static ssize_t kbd_rgb_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1091,16 +1095,16 @@ static ssize_t kbd_rgb_mode_store(struct device *dev, switch (speed) { case 0: - speed = 0xe1; + speed = TUF_KBD_RGB_SPEED_SLOW; break; case 1: - speed = 0xeb; + speed = TUF_KBD_RGB_SPEED_NORMAL; break; case 2: - speed = 0xf5; + speed = TUF_KBD_RGB_SPEED_FAST; break; default: - speed = 0xeb; + speed = TUF_KBD_RGB_SPEED_NORMAL; } err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev, @@ -1126,6 +1130,11 @@ static const struct attribute_group kbd_rgb_mode_group = { }; /* TUF Laptop Keyboard RGB State **********************************************/ + +#define TUF_KBD_RGB_STATE_BOOT BIT(1) +#define TUF_KBD_RGB_STATE_AWAKE BIT(3) +#define TUF_KBD_RGB_STATE_SLEEP BIT(5) +#define TUF_KBD_RGB_STATE_SHUTDOWN BIT(7) static ssize_t kbd_rgb_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1141,13 +1150,13 @@ static ssize_t kbd_rgb_state_store(struct device *dev, flags = 0; if (boot) - flags |= BIT(1); + flags |= TUF_KBD_RGB_STATE_BOOT; if (awake) - flags |= BIT(3); + flags |= TUF_KBD_RGB_STATE_AWAKE; if (sleep) - flags |= BIT(5); + flags |= TUF_KBD_RGB_STATE_SLEEP; if (shutdown) - flags |= BIT(7); + flags |= TUF_KBD_RGB_STATE_SHUTDOWN; /* 0xbd is the required default arg0 for the method. Nothing happens otherwise */ err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, -- cgit v1.2.3