From 7bc99dff4d975e850ecb3393811c4264aee24335 Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Tue, 19 May 2026 18:12:13 +0000 Subject: [PATCH 001/152] platform/x86: asus-armoury: gate PPT writes behind active fan curve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On models flagged with requires_fan_curve in the DMI power_data table (30 entries), the BIOS ACPI method SPLX only writes PPT values to the EC when the fan mode is set to Manual (FANM=4). FANM is set to 4 by the DEFC method when a custom fan curve is written. Without an active custom fan curve, the WMI DEVS call returns success but the firmware silently ignores the PPT value, so userspace observes no effect from its write. Gate writes to ASUS_WMI_DEVID_PPT_{PL1_SPL,PL2_SPPT,PL3_FPPT,APU_SPPT, PLAT_SPPT} on a check of asus_wmi_custom_fan_curve_is_enabled(), and return -EBUSY with a pr_warn_once() when no fan curve is active on an affected model. Export the helper from asus-wmi so asus-armoury can call it across module boundaries. Signed-off-by: Ahmed Yaseen Reviewed-by: Denis Benato Link: https://patch.msgid.link/20260519181155.46044-2-yaseen@ghoul.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 20 ++++++++++++++++++ drivers/platform/x86/asus-wmi.c | 24 ++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 5 +++++ 3 files changed, 49 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 495dc1e31d40..f2a880eb0cdf 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -93,6 +93,8 @@ struct asus_armoury_priv { u32 mini_led_dev_id; u32 gpu_mux_dev_id; + + bool requires_fan_curve; }; static struct asus_armoury_priv asus_armoury = { @@ -216,6 +218,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, u32 result; int err; + /* On some models, PPT changes require an active fan curve */ + if (asus_armoury.requires_fan_curve) { + switch (dev_id) { + case ASUS_WMI_DEVID_PPT_PL1_SPL: + case ASUS_WMI_DEVID_PPT_PL2_SPPT: + case ASUS_WMI_DEVID_PPT_PL3_FPPT: + case ASUS_WMI_DEVID_PPT_APU_SPPT: + case ASUS_WMI_DEVID_PPT_PLAT_SPPT: + if (!asus_wmi_custom_fan_curve_is_enabled()) { + pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); + return -EBUSY; + } + break; + } + } + /* * Prevent developers from bricking devices or issuing dangerous * commands that can be difficult or impossible to recover from. @@ -1010,6 +1028,8 @@ static void init_rog_tunables(void) return; } + asus_armoury.requires_fan_curve = power_data->requires_fan_curve; + /* Initialize AC power tunables */ ac_limits = power_data->ac_data; if (ac_limits) { diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3c9ef826551d..c7c8fcfc1d72 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4053,6 +4053,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) return 0; } +/* + * Returns true if at least one custom fan curve is active + * + * Used by asus-armoury to check if PPT writes will be accepted by the BIOS + * on models that require an active fan curve for TDP changes. + */ +bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + struct fan_curve_data *curves; + struct asus_wmi *asus; + + guard(spinlock_irqsave)(&asus_ref.lock); + asus = asus_ref.asus; + if (!asus) + return false; + + curves = asus->custom_fan_curves; + + return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || + (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || + (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); +} +EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); + /* Throttle thermal policy ****************************************************/ static int throttle_thermal_policy_write(struct asus_wmi *asus) { diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index c29962d5baac..b5ed8c83ace1 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); int asus_hid_register_listener(struct asus_hid_listener *cdev); void asus_hid_unregister_listener(struct asus_hid_listener *cdev); int asus_hid_event(enum asus_hid_event event); +bool asus_wmi_custom_fan_curve_is_enabled(void); #else static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) { @@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) { return -ENODEV; } +static inline bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + return false; +} #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ From 27ef0cdcdc208fbb0d06812d2cc0564d82c7fcd9 Mon Sep 17 00:00:00 2001 From: David Glushkov Date: Thu, 28 May 2026 20:33:58 +0200 Subject: [PATCH 002/152] platform/x86: msi-ec: Add MSI Raider A18 HX A9WJG EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for EC firmware 182LIMS1.111, found on the MSI Raider A18 HX A9WJG. The out-of-tree msi-ec driver probes successfully on this machine and exports the msi-ec platform device. Without this entry, the in-tree driver rejects the EC firmware as unsupported. Tested on MSI Raider A18 HX A9WJG with BIOS E182LAMS.31A. Signed-off-by: David Glushkov Link: https://patch.msgid.link/20260528183358.552782-1-david.glushkov@sntiq.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 81 +++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index dfe4532ebe56..667df9509798 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1130,6 +1130,86 @@ static struct msi_ec_conf CONF13 __initdata = { }, }; +static const char * const ALLOWED_FW_14[] __initconst = { + "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 + "182KIMS1.113", // Raider A18 HX A7VIG + NULL +}; + +static struct msi_ec_conf CONF14 __initdata = { + .allowed_fw = ALLOWED_FW_14, + .charge_control = { + .address = 0xd7, + .offset_start = 0x8a, + .offset_end = 0x80, + .range_min = 0x8a, + .range_max = 0xe4, + }, + .webcam = { + .address = 0x2e, + .block_address = 0x2f, + .bit = 1, + }, + .fn_win_swap = { + .address = 0xe8, + .bit = 4, + }, + .cooler_boost = { + .address = 0x98, + .bit = 7, + }, + .shift_mode = { + .address = 0xd2, + .modes = { + { SM_ECO_NAME, 0xc2 }, + { SM_COMFORT_NAME, 0xc1 }, + { SM_TURBO_NAME, 0xc4 }, + MSI_EC_MODE_NULL + }, + }, + .super_battery = { + .address = 0xeb, + .mask = 0x0f, + }, + .fan_mode = { + .address = 0xd4, + .modes = { + { FM_AUTO_NAME, 0x0d }, + { FM_SILENT_NAME, 0x1d }, + { FM_ADVANCED_NAME, 0x8d }, + MSI_EC_MODE_NULL + }, + }, + .cpu = { + .rt_temp_address = 0x68, + .rt_fan_speed_address = 0x71, + .rt_fan_speed_base_min = 0x00, + .rt_fan_speed_base_max = 0x96, + .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, + .bs_fan_speed_base_min = 0x00, + .bs_fan_speed_base_max = 0x0f, + }, + .gpu = { + .rt_temp_address = 0x80, + .rt_fan_speed_address = 0x89, + }, + .leds = { + .micmute_led_address = 0x2c, + .mute_led_address = 0x2d, + .bit = 1, + }, + .kbd_bl = { + .bl_mode_address = MSI_EC_ADDR_UNSUPP, + .bl_modes = { 0x00, 0x08 }, + .max_mode = 1, + .bl_state_address = MSI_EC_ADDR_UNSUPP, + .state_base_value = 0x80, + .max_state = 3, + }, +}; + static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF0, &CONF1, @@ -1145,6 +1225,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF11, &CONF12, &CONF13, + &CONF14, NULL }; From 63af040b0b4067d75e59df9fa093f3f5dbc5899e Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:07 +0200 Subject: [PATCH 003/152] platform/x86: uniwill-laptop: Add keyboard backlight support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many Uniwill-based devices support either a white-only or fully features RGB keyboard backlight. Add support for this feature and handle the associated WMI events. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../admin-guide/laptops/uniwill-laptop.rst | 13 + drivers/platform/x86/uniwill/uniwill-acpi.c | 392 +++++++++++++++++- 2 files changed, 397 insertions(+), 8 deletions(-) diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index 24b41dbab886..2b1e5da703a5 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -77,6 +77,19 @@ LED class device. The default name of this LED class device is ``uniwill:multico See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various animation modes of the lightbar. +Keyboard Backlight +------------------ + +The ``uniwill-laptop`` driver supports controlling the keyboard backlight using the standard +LED class interface. The default name of this LED class device is ``uniwill:white:kbd_backlight`` +when the keyboard backlight supports only a single color, or ``uniwill:multicolor:kbd_backlight`` +when the keyboard backlight supports RGB colors. The maximum intensity for each color channel +in RGB mode is 50. + +Keep in mind that due to hardware design choices, the driver does not support the RGB value +``0x000000`` (black), instead it will fall back to ``0x010101`` (faint white). In order to +disable the keyboard backlight, the standard LED brightness setting has to be used instead. + Configurable TGP ---------------- diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index ab063ead45b9..fd040197b189 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -110,8 +110,31 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 #define EC_ADDR_PROJECT_ID 0x0740 +#define PROJECT_ID_NONE 0x00 +#define PROJECT_ID_GI 0x01 +#define PROJECT_ID_GJ 0x02 +#define PROJECT_ID_GK 0x03 +#define PROJECT_ID_GICN 0x04 +#define PROJECT_ID_GJCN 0x05 +#define PROJECT_ID_GK5CN_X 0x06 +#define PROJECT_ID_GK7CN_S 0x07 +#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 +#define PROJECT_ID_PF 0x09 +#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A +#define PROJECT_ID_IDP 0x0B +#define PROJECT_ID_IDY_6Y 0x0C +#define PROJECT_ID_IDY_7Y 0x0D +#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E +#define PROJECT_ID_CML_GAMING 0x0F +#define PROJECT_ID_GK7NXXR 0x10 +#define PROJECT_ID_GM5MU1Y 0x11 #define PROJECT_ID_PH4TRX1 0x12 +#define PROJECT_ID_PH4TUX1 0x13 +#define PROJECT_ID_PH4TQX1 0x14 #define PROJECT_ID_PH6TRX1 0x15 +#define PROJECT_ID_PH6TQXX 0x16 +#define PROJECT_ID_PHXAXXX 0x17 +#define PROJECT_ID_PHXPXXX 0x18 #define EC_ADDR_AP_OEM 0x0741 #define ENABLE_MANUAL_CTRL BIT(0) @@ -214,6 +237,7 @@ #define FAN_TABLE_OFFICE_MODE BIT(2) #define FAN_V3 BIT(3) #define DEFAULT_MODE BIT(4) +#define ENABLE_CHINA_MODE BIT(6) #define EC_ADDR_PL1_SETTING 0x0783 @@ -225,11 +249,11 @@ #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C -#define KBD_WHITE_ONLY BIT(0) // ~single color -#define KBD_SINGLE_COLOR_OFF BIT(1) +#define KBD_WHITE_ONLY BIT(0) +#define KBD_POWER_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) #define KBD_APPLY BIT(4) -#define KBD_BRIGHTNESS GENMASK(7, 5) +#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) #define EC_ADDR_FAN_CTRL 0x078E #define FAN3P5 BIT(1) @@ -320,6 +344,9 @@ #define LED_CHANNELS 3 #define LED_MAX_BRIGHTNESS 200 +#define KBD_LED_CHANNELS 3 +#define KBD_LED_MAX_INTENSITY 50 + #define UNIWILL_FEATURE_FN_LOCK BIT(0) #define UNIWILL_FEATURE_SUPER_KEY BIT(1) #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) @@ -333,6 +360,7 @@ #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) +#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -344,6 +372,7 @@ struct uniwill_data { acpi_handle handle; struct regmap *regmap; unsigned int features; + u8 project_id; struct acpi_battery_hook hook; struct mutex battery_lock; /* Protects the list of currently registered batteries */ union { @@ -362,6 +391,18 @@ struct uniwill_data { struct mutex led_lock; /* Protects writes to the lightbar registers */ struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; + bool single_color_kbd; + u8 kbd_led_max_brightness; + unsigned int last_kbd_status; + union { + struct { + /* Protects writes to the RGB keyboard backlight registers */ + struct mutex kbd_rgb_led_lock; + struct led_classdev_mc kbd_led_mc_cdev; + struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; + }; + struct led_classdev kbd_led_cdev; + }; struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; struct notifier_block nb; @@ -376,6 +417,7 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; + u8 kbd_led_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -427,6 +469,9 @@ static const struct key_entry uniwill_keymap[] = { { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + /* Reported when the EC changed the keyboard backlight brightness */ + { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the microphone mute status */ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, @@ -435,11 +480,6 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, /* FIXME: find out the exact meaning of those events */ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, @@ -547,6 +587,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_TRIGGER: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -583,8 +628,14 @@ 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_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -616,8 +667,10 @@ 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_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_USB_C_POWER_PRIORITY: @@ -1441,6 +1494,246 @@ static int uniwill_led_init(struct uniwill_data *data) &init_data); } +static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) +{ + struct led_classdev *led_cdev; + int ret; + + if (data->single_color_kbd) + led_cdev = &data->kbd_led_cdev; + else + led_cdev = &data->kbd_led_mc_cdev.led_cdev; + + guard(mutex)(&led_cdev->led_access); + + /* Sync the LED brightness with the actual hardware state */ + ret = led_update_brightness(led_cdev); + if (ret < 0) + return ret; + + led_classdev_notify_brightness_hw_changed(led_cdev, brightness); + + return 0; +} + +#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) + +static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) +{ + /* KBD_POWER_OFF is always implicitly cleared */ + unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; + + /* We must ensure that the "apply" bit is always written */ + return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); +} + +static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); +} + +static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { + EC_ADDR_RGB_RED, + EC_ADDR_RGB_GREEN, + EC_ADDR_RGB_BLUE, +}; + +static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + unsigned int min_intensity = 0; + unsigned int regval; + int ret; + + guard(mutex)(&data->kbd_rgb_led_lock); + + /* + * The EC interprets a RGB value of 0x000000 as a command to restore + * the device-specfic 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 && + data->kbd_led_mc_subled_info[1].intensity == 0 && + data->kbd_led_mc_subled_info[2].intensity == 0) + min_intensity = 1; + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + } + + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); + if (ret < 0) + return ret; + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static int uniwill_kbd_led_init(struct uniwill_data *data) +{ + unsigned int color_indices[KBD_LED_CHANNELS] = { + LED_COLOR_ID_RED, + LED_COLOR_ID_GREEN, + LED_COLOR_ID_BLUE, + }; + struct led_init_data init_data = { + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + bool intensity_all_zeros = true; + bool needs_trigger = false; + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); + if (ret < 0) + return ret; + + if (!(regval & CHINA_MODE)) { + ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + regval |= KBD_APPLY; + regval &= ~KBD_POWER_OFF; + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); + if (ret < 0) + return ret; + + switch (data->project_id) { + case PROJECT_ID_PF: + case PROJECT_ID_PF4MU_PF4MN_PF5MU: + case PROJECT_ID_PH4TRX1: + case PROJECT_ID_PH4TUX1: + case PROJECT_ID_PH4TQX1: + case PROJECT_ID_PH6TRX1: + case PROJECT_ID_PH6TQXX: + case PROJECT_ID_PHXAXXX: + case PROJECT_ID_PHXPXXX: + data->single_color_kbd = true; + break; + default: + data->single_color_kbd = regval & KBD_WHITE_ONLY; + break; + } + + if (data->single_color_kbd) { + init_data.default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT; + data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; + data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; + data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; + + return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); + } + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; + + ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); + if (ret < 0) + return ret; + + /* + * Make sure that the initial intensity value is not greater than + * the maximum intensity. + */ + if (regval > KBD_LED_MAX_INTENSITY) { + regval = KBD_LED_MAX_INTENSITY; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + + needs_trigger = true; + } + + if (regval) + intensity_all_zeros = false; + + data->kbd_led_mc_subled_info[i].intensity = regval; + data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; + data->kbd_led_mc_subled_info[i].channel = i; + } + + /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ + if (intensity_all_zeros) { + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].intensity = 1; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); + if (ret < 0) + return ret; + } + + needs_trigger = true; + } + + if (needs_trigger) { + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, + RGB_APPLY_COLOR); + if (ret < 0) + return ret; + } + + ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); + if (ret < 0) + return ret; + + init_data.default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT; + 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.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; + data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; + data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; + + return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, + &init_data); +} + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) { /* 0 means "charging threshold not active" */ @@ -1789,6 +2082,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); return NOTIFY_OK; + case UNIWILL_OSD_KB_LED_LEVEL0: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); + case UNIWILL_OSD_KB_LED_LEVEL1: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); + case UNIWILL_OSD_KB_LED_LEVEL2: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); + case UNIWILL_OSD_KB_LED_LEVEL3: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); + case UNIWILL_OSD_KB_LED_LEVEL4: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); default: mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, action, 1, true); @@ -1842,6 +2160,7 @@ static int uniwill_ec_init(struct uniwill_data *data) if (ret < 0) return ret; + data->project_id = value; dev_dbg(data->dev, "Project ID: %u\n", value); ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); @@ -1885,6 +2204,7 @@ static int uniwill_probe(struct platform_device *pdev) return ret; data->features = device_descriptor.features; + data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -1905,6 +2225,10 @@ static int uniwill_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = uniwill_kbd_led_init(data); + if (ret < 0) + return ret; + ret = uniwill_hwmon_init(data); if (ret < 0) return ret; @@ -1976,6 +2300,31 @@ static int uniwill_suspend_battery(struct uniwill_data *data) return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); } +static int uniwill_suspend_kbd_led(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + /* + * Save the current keyboard backlight settings in order to restore them + * during resume. We cannot use the regmap code for that since this register + * needs to be declared as volatile because the brightness can be changed + * by the EC. + */ + data->last_kbd_status = regval; + FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); + regval |= KBD_APPLY | KBD_POWER_OFF; + + return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2006,6 +2355,10 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_kbd_led(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2052,6 +2405,23 @@ static int uniwill_resume_battery(struct uniwill_data *data) return 0; } +static int uniwill_resume_kbd_led(struct uniwill_data *data) +{ + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); + if (ret < 0) + return ret; + + if (data->single_color_kbd) + return 0; + + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); +} + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2096,6 +2466,10 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_kbd_led(data); + if (ret < 0) + return ret; + ret = uniwill_resume_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2745,6 +3119,8 @@ static int __init uniwill_init(void) if (force) { /* Assume that the device supports all features except the charge limit */ device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; + /* Some models only support 3 brightness levels */ + device_descriptor.kbd_led_max_brightness = 4; pr_warn("Enabling potentially unsupported features\n"); } From 69be0016c161eb08d3f2ce3dc5c1d31ecf990f18 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:08 +0200 Subject: [PATCH 004/152] platform/x86: uniwill-laptop: Handle screen-related events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EC will report event 0xCC on some devices when the screen has been enabled/disabled during resume/suspend. Ignore this event because it is currently unused by the driver. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 6 ++++++ drivers/platform/x86/uniwill/uniwill-wmi.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index fd040197b189..b3be2f2dbdd8 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -95,6 +95,9 @@ #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 +#define EC_ADDR_SCREEN_STATUS 0x0466 +#define SCREEN_SUSPENDED BIT(6) + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C #define EC_ADDR_SECOND_FAN_RPM_2 0x046D @@ -488,6 +491,9 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the benchmark mode status */ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the screen is enabled/disabled during resume/suspend */ + { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index fb1910c0f741..b25b2f31211c 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -113,6 +113,8 @@ #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 +#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 From c393505bab5b1fa25aa702c04a25b3bae0a570ee Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:09 +0200 Subject: [PATCH 005/152] platform/x86: uniwill-laptop: Add AC auto boot support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices support a "AC auto boot" feature where the system will automatically boot when being connected to a power source. Add support for this feature. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/testing/sysfs-driver-uniwill-laptop | 11 ++++ .../admin-guide/laptops/uniwill-laptop.rst | 7 +++ drivers/platform/x86/uniwill/uniwill-acpi.c | 51 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 2397c65c969a..57272f906184 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -78,3 +78,14 @@ Description: Reading this file returns the profile names with the currently active one in brackets. + +What: /sys/bus/platform/devices/INOU0000:XX/ac_auto_boot +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf +Description: + Allows userspace applications to configure if the device should boot automatically + when being connected to a power source. Writing "1"/"0" into this file + enables/disables this functionality. + + Reading this file returns the current status of the AC auto boot functionality. diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index 2b1e5da703a5..b6213fb1d3e0 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -98,6 +98,13 @@ allow it. See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. +AC Auto Boot +------------ + +The ``uniwill-laptop`` driver allows the user to configure if the system should automatically +boot when being connected to a power source, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + References ========== diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index b3be2f2dbdd8..897c6163de53 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -112,6 +112,9 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 +#define EC_ADDR_OEM_9 0x0726 +#define AC_AUTO_BOOT_ENABLE BIT(3) + #define EC_ADDR_PROJECT_ID 0x0740 #define PROJECT_ID_NONE 0x00 #define PROJECT_ID_GI 0x01 @@ -364,6 +367,7 @@ #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) #define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) +#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -586,6 +590,7 @@ static const struct regmap_bus uniwill_ec_bus = { static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { + case EC_ADDR_OEM_9: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: @@ -625,6 +630,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: + case EC_ADDR_OEM_9: case EC_ADDR_PROJECT_ID: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: @@ -1136,6 +1142,45 @@ static int usb_c_power_priority_init(struct uniwill_data *data) return 0; } +static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + if (enable) + regval = AC_AUTO_BOOT_ENABLE; + else + regval = 0; + + ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); +} + +static DEVICE_ATTR_RW(ac_auto_boot); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -1147,6 +1192,7 @@ static struct attribute *uniwill_attrs[] = { /* Power-management-related */ &dev_attr_ctgp_offset.attr, &dev_attr_usb_c_power_priority.attr, + &dev_attr_ac_auto_boot.attr, NULL }; @@ -1186,6 +1232,11 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_ac_auto_boot.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) + return attr->mode; + } + return 0; } From e02c8d7a197d8a94e30fdcdee2a626d8a9ae1549 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:10 +0200 Subject: [PATCH 006/152] platform/x86: uniwill-laptop: Add support for USB powershare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices support a "USB powershare" feature where the system will continue to provide power via the USB ports when hibernating or powered off. Add support for this feaure. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/testing/sysfs-driver-uniwill-laptop | 16 ++- .../admin-guide/laptops/uniwill-laptop.rst | 7 ++ drivers/platform/x86/uniwill/uniwill-acpi.c | 100 ++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 57272f906184..943f92c6b561 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -86,6 +86,20 @@ Contact: Armin Wolf Description: Allows userspace applications to configure if the device should boot automatically when being connected to a power source. Writing "1"/"0" into this file - enables/disables this functionality. + enables/disables this functionality. Enabling both AC auto boot and USB powershare + at the same time is not supported. Reading this file returns the current status of the AC auto boot functionality. + +What: /sys/bus/platform/devices/INOU0000:XX/usb_powershare_high +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf +Description: + Allows userspace applications to configure if the device should continue to provide + power via the USB ports when hibernating or powered off. Might also increase the + power budget available to USB ports on some devices. Writing "1"/"0" into this + file enables/disables this functionality. Enabling both USB powershare and AC auto + boot at the same time is not supported. + + Reading this file returns the current status of the USB powershare functionality. diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index b6213fb1d3e0..be50b45b82ef 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -105,6 +105,13 @@ The ``uniwill-laptop`` driver allows the user to configure if the system should boot when being connected to a power source, see Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. +USB Powershare +-------------- + +The ``uniwill-laptop`` driver allows the user to configure if the system should continue to +provide power via the USB ports when hibernating or powered off, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + References ========== diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 897c6163de53..00140c0a67a0 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -368,6 +368,7 @@ #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) #define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) #define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) +#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -393,6 +394,7 @@ struct uniwill_data { bool last_fn_lock_state; bool last_super_key_enable_state; bool last_touchpad_toggle_enable_state; + bool last_usb_powershare_high_state; 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 */ @@ -1181,6 +1183,70 @@ static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *at static DEVICE_ATTR_RW(ac_auto_boot); +static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = TRIGGER_USB_CHARGING; + else + value = 0; + + /* + * Normaly this RMW-sequence could also trigger the super key toggle, + * but the EC seems to take care that those bits are always read as 0. + */ + return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); +} + +static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + ret = uniwill_write_usb_powershare_high(data, enable); + if (ret < 0) + return ret; + + return count; +} + +static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); + if (ret < 0) + return ret; + + *status = !!(value & TRIGGER_USB_CHARGING); + + return 0; +} + +static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool status; + int ret; + + ret = uniwill_read_usb_powershare_high(data, &status); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", status); +} + +static DEVICE_ATTR_RW(usb_powershare_high); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -1193,6 +1259,7 @@ static struct attribute *uniwill_attrs[] = { &dev_attr_ctgp_offset.attr, &dev_attr_usb_c_power_priority.attr, &dev_attr_ac_auto_boot.attr, + &dev_attr_usb_powershare_high.attr, NULL }; @@ -1237,6 +1304,11 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_usb_powershare_high.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return attr->mode; + } + return 0; } @@ -2382,6 +2454,18 @@ static int uniwill_suspend_kbd_led(struct uniwill_data *data) return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); } +static int uniwill_suspend_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + /* + * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it + * ourselves. + */ + return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2416,6 +2500,10 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2479,6 +2567,14 @@ static int uniwill_resume_kbd_led(struct uniwill_data *data) return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); } +static int uniwill_resume_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); +} + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2527,6 +2623,10 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_resume_nvidia_ctgp(data); if (ret < 0) return ret; From e76799915b86f0604ae7b944830e6166faa68f0f Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:11 +0200 Subject: [PATCH 007/152] platform/x86: uniwill-laptop: Add support for the MACHENIKE L16 Pro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user has reported that the driver works on the MACHENIKE L16 Pro. Add the necessary device descriptor and DMI entry to allow the driver to automatically load on this device. Reported-by: zatrit Closes: https://github.com/Wer-Wolf/uniwill-laptop/pull/11 Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-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(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 00140c0a67a0..0011c553c2cb 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2657,6 +2657,20 @@ static struct platform_driver uniwill_driver = { .shutdown = uniwill_shutdown, }; +static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 4, +}; + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { .features = UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | @@ -2809,6 +2823,14 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { }; static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + { + .ident = "MACHENIKE L16 Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), + }, + .driver_data = &machenike_l16p_descriptor, + }, { .ident = "XMG FUSION 15 (L19)", .matches = { From efd0636aaedb0a42793d85c1eae3832f12fd45d2 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:12 +0200 Subject: [PATCH 008/152] platform/x86: uniwill-laptop: Add support for the AiStone X4SP4NAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user has reported that the driver works on the AiStone X4SP4NAL. Add the necessary device descriptor and DMI entry to allow the driver to automatically load on this device. Reported-by: Michael Seifert Closes: https://github.com/Wer-Wolf/uniwill-laptop/pull/10 Tested-by: Michael Seifert Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-7-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 0011c553c2cb..d688ffca3b5e 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2822,7 +2822,28 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { UNIWILL_FEATURE_PRIMARY_FAN, }; +static struct uniwill_device_descriptor x4sp4nal_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_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 2, +}; + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + { + .ident = "AiStone X4SP4NAL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), + }, + .driver_data = &x4sp4nal_descriptor, + }, { .ident = "MACHENIKE L16 Pro", .matches = { From 32763779b5762d2f6887ddcd0892231b7027fea2 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Thu, 9 Jul 2026 16:42:03 +0300 Subject: [PATCH 009/152] platform/x86: uniwill-laptop: Add lightbar support for LAPQC71A/B MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LAPQC71A and LAPQC71B both feature a RGB lightbar with 36 brightness levels per color component. Extend the device descriptor to supply the maximum brightness of the lightbar and whitelist both models for UNIWILL_FEATURE_LIGHTBAR. Signed-off-by: Werner Sembach Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-8-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index d688ffca3b5e..f55b239bd4d1 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -348,7 +348,6 @@ #define FAN_TABLE_LENGTH 16 #define LED_CHANNELS 3 -#define LED_MAX_BRIGHTNESS 200 #define KBD_LED_CHANNELS 3 #define KBD_LED_MAX_INTENSITY 50 @@ -398,6 +397,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; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; bool single_color_kbd; @@ -427,6 +427,7 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; u8 kbd_led_max_brightness; + u8 lightbar_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -1514,7 +1515,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br for (int i = 0; i < LED_CHANNELS; i++) { /* Prevent the brightness values from overflowing */ - value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); + 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); if (ret < 0) return ret; @@ -1583,14 +1584,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 = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; 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 = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1603,7 +1604,7 @@ static int uniwill_led_init(struct uniwill_data *data) * Make sure that the initial intensity value is not greater than * the maximum brightness. */ - value = min(LED_MAX_BRIGHTNESS, value); + value = min(data->lightbar_max_brightness, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -2334,6 +2335,7 @@ static int uniwill_probe(struct platform_device *pdev) data->features = device_descriptor.features; data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; + data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -2673,11 +2675,13 @@ static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { .features = UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 36, }; static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { @@ -2701,6 +2705,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, }; /* @@ -3321,6 +3326,8 @@ static int __init uniwill_init(void) device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; /* 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; pr_warn("Enabling potentially unsupported features\n"); } From 08ecf6d131f38595a1e7f5441c8e1d29302cc718 Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:32 +0530 Subject: [PATCH 010/152] platform/x86: hp-wmi: Introduce board-specific feature data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hp_wmi DMI table is about to carry more than thermal-profile data. Replace the direct thermal_profile_params .driver_data pointers with hp_wmi_board_params and rename the table/setup helper accordingly. No functional changes intended. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-2-radheykalra901@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 127 +++++++++++++++++++------------ 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 8ba286ed8721..ee8375d28672 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -133,11 +133,35 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, }; -/* - * A generic pointer for the currently-active board's thermal profile - * parameters. - */ -static struct thermal_profile_params *active_thermal_profile_params; +struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; +}; + +static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, +}; + +static const struct hp_wmi_board_params *active_board_params; + +static const struct thermal_profile_params *hp_wmi_thermal_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->thermal_profile; +} /* DMI board names of devices that should use the omen specific path for * thermal profiles. @@ -187,87 +211,87 @@ static const char * const victus_thermal_profile_boards[] = { "8A25", }; -/* DMI Board names of Victus 16-r and Victus 16-s laptops */ -static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { +/* DMI board-specific feature data for Omen and Victus laptops. */ +static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, {}, }; @@ -1874,7 +1898,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil u8 current_dstate, current_gpu_slowdown_temp, tp; const struct thermal_profile_params *params; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { *profile = active_platform_profile; @@ -1886,10 +1913,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil return ret; /* - * We cannot use active_thermal_profile_params here, because boards - * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically - * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen - * Gaming Hub on windows. Hence accept both of these values. + * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, + * but logically it should have tp == 0x30 || tp == 0x31, as + * corrected by the Omen Gaming Hub on windows. Hence accept both + * of these values. */ if (tp == victus_s_thermal_params.performance || tp == omen_v1_thermal_params.performance) { @@ -1924,12 +1951,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) { - struct thermal_profile_params *params; + const struct thermal_profile_params *params; bool gpu_ctgp_enable, gpu_ppab_enable; u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ int err, tp; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); if (!params) return -ENODEV; @@ -2195,6 +2222,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { static int thermal_profile_setup(struct platform_device *device) { const struct platform_profile_ops *ops; + const struct thermal_profile_params *params; int err, tp; if (is_omen_thermal_profile()) { @@ -2226,13 +2254,17 @@ static int thermal_profile_setup(struct platform_device *device) ops = &platform_profile_victus_ops; } else if (is_victus_s_thermal_profile()) { + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + /* * For an unknown EC layout board, platform_profile_victus_s_get_ec(), * behaves like a wrapper around active_platform_profile, to avoid using * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. */ - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || - active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { active_platform_profile = PLATFORM_PROFILE_BALANCED; } else { err = platform_profile_victus_s_get_ec(&active_platform_profile); @@ -2693,24 +2725,25 @@ static int hp_wmi_hwmon_init(void) return 0; } -static void __init setup_active_thermal_profile_params(void) +static void __init setup_active_board_params(void) { const struct dmi_system_id *id; + const struct thermal_profile_params *params; - /* - * Currently only victus_s devices use the - * active_thermal_profile_params - */ - id = dmi_first_match(victus_s_thermal_profile_boards); + id = dmi_first_match(hp_wmi_feature_boards); if (id) { + active_board_params = id->driver_data; + params = hp_wmi_thermal_profile(); + if (!params) + return; + /* * Marking this boolean is required to ensure that * is_victus_s_thermal_profile() behaves like a valid * wrapper. */ is_victus_s_board = true; - active_thermal_profile_params = id->driver_data; - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", dmi_get_system_info(DMI_BOARD_NAME)); } @@ -2745,10 +2778,10 @@ static int __init hp_wmi_init(void) } /* - * Setup active board's thermal profile parameters before - * starting platform driver probe. + * Setup active board feature data before starting platform + * driver probe. */ - setup_active_thermal_profile_params(); + setup_active_board_params(); err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); if (err) goto err_unregister_device; From 0687673b1aa02fe10d2f0c627564843728f0b7e6 Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:33 +0530 Subject: [PATCH 011/152] platform/x86: hp-wmi: Drive fan control from board data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the board-specific .driver_data to describe fan-control support and fan-speed read callbacks. Existing boards keep the same Victus fan-control path, but the hwmon code no longer hardcodes that decision through is_victus_s_thermal_profile(). No functional changes intended. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-3-radheykalra901@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 79 ++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index ee8375d28672..11de5c70ce4d 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -133,24 +133,41 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, }; +struct hp_wmi_fan_profile_params { + int (*get_fan_speed)(int fan); + bool fan_table; +}; + struct hp_wmi_board_params { const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + +static int hp_wmi_get_fan_speed_victus_s(int fan); + +static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { + .get_fan_speed = hp_wmi_get_fan_speed_victus_s, + .fan_table = true, }; static const struct hp_wmi_board_params victus_s_board_params = { .thermal_profile = &victus_s_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_board_params = { .thermal_profile = &omen_v1_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_legacy_board_params = { .thermal_profile = &omen_v1_legacy_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { .thermal_profile = &omen_v1_no_ec_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params *active_board_params; @@ -1813,6 +1830,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } +static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->fan_profile; +} + +static bool hp_wmi_fan_control_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->get_fan_speed; +} + +static bool hp_wmi_fan_table_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->fan_table; +} + +static int hp_wmi_get_active_fan_speed(int fan) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + if (!params || !params->get_fan_speed) + return -EOPNOTSUPP; + + return params->get_fan_speed(fan); +} + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, bool *ppab_enable, u8 *dstate, @@ -2432,7 +2481,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2444,7 +2493,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); if (ret < 0) @@ -2453,7 +2502,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_AUTO: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2477,12 +2526,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: - if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) return 0; return 0644; case hwmon_fan: - if (is_victus_s_thermal_profile()) { - if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) + if (hp_wmi_fan_control_supported()) { + if (hp_wmi_get_active_fan_speed(channel) >= 0) return 0444; } else { if (hp_wmi_get_fan_speed(channel) >= 0) @@ -2506,8 +2555,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: - if (is_victus_s_thermal_profile()) - ret = hp_wmi_get_fan_speed_victus_s(channel); + if (hp_wmi_fan_control_supported()) + ret = hp_wmi_get_active_fan_speed(channel); else ret = hp_wmi_get_fan_speed(channel); if (ret < 0) @@ -2516,10 +2565,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; *val = rpm_to_pwm(rpm / 100, priv); @@ -2553,7 +2602,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, switch (type) { case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* PWM input is invalid when not in manual mode */ if (priv->mode != PWM_MODE_MANUAL) @@ -2570,13 +2619,13 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* * When switching to manual mode, set fan speed to * current RPM values to ensure a smooth transition. */ - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; priv->pwm = rpm_to_pwm(rpm / 100, priv); @@ -2642,8 +2691,8 @@ 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; - /* Bypass all non-Victus S devices */ - if (!is_victus_s_thermal_profile()) + /* Bypass devices without fan control support. */ + if (!hp_wmi_fan_table_supported()) return 0; ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, From dcabd63cd12c119dc6bbc5c22bfa411021fd9a5b Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:34 +0530 Subject: [PATCH 012/152] platform/x86: hp-wmi: Add Victus 15-fb0xxx support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Victus 15-fb0xxx board 8A3D exposes the Victus fan table and accepts the existing Victus fan-speed WMI control path. Add a DMI match using the Victus S thermal-profile and fan-control data. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-4-radheykalra901@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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 11de5c70ce4d..0dcf2901259e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -234,6 +234,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, + .driver_data = (void *)&victus_s_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, .driver_data = (void *)&omen_v1_legacy_board_params, From ca9338dbc64759b30741b12017c050b33c94dfa2 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:48 +0200 Subject: [PATCH 013/152] platform/x86: dell-privacy: Fix race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accessing priv->features_present needs to happen with the list mutex being held, otherwise priv can be freed at any moment. Fixes: 8af9fa37b8a3 ("platform/x86: dell-privacy: Add support for Dell hardware privacy") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-privacy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c index f9d275b2f900..366e5b8dc868 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.c +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) { struct privacy_wmi_data *priv; - mutex_lock(&list_mutex); + guard(mutex)(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, struct privacy_wmi_data, list); - mutex_unlock(&list_mutex); return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); } From 072841e02cf9c00a7e8a9c567a14239e02ca47ad Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:49 +0200 Subject: [PATCH 014/152] platform/x86: dell-wmi-base: Fix resource leak on module load failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to properly clean up the SMBIOS request and the privacy driver when the module load fails. Fixes: 8af9fa37b8a3 ("platform/x86: dell-privacy: Add support for Dell hardware privacy") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-base.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index 997383ba1846..fd6a508cd902 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) err = dell_privacy_register_driver(); if (err) - return err; + goto out_smbios; - return wmi_driver_register(&dell_wmi_driver); + err = wmi_driver_register(&dell_wmi_driver); + if (err) + goto out_privacy; + + return 0; + +out_privacy: + dell_privacy_unregister_driver(); + +out_smbios: + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + return err; } late_initcall(dell_wmi_init); From adfd6846bea13667ff28f8aaf00c32fbd69825ab Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:50 +0200 Subject: [PATCH 015/152] platform/x86: dell-wmi-base: Fix handling of ultra performance key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit message of commit 5fbd827eb9c2 ("platform/x86: dell-wmi: Recognise or support new switches") states that the ultra performance key contains additional data after the type and code fields. The event data passed to dell_wmi_process_key() is already parsed, so "buffer" already starts after those two fields. Use the correct index for accessing the first data field to avoid a potential buffer overread. Fixes: 5fbd827eb9c2 ("platform/x86: dell-wmi: Recognise or support new switches") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index fd6a508cd902..38a6b3ae2f75 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l key++; used = 1; } else if (type == 0x0012 && code == 0x000d && remaining > 0) { - value = (le16_to_cpu(buffer[2]) == 2); + value = (le16_to_cpu(buffer[0]) == 2); used = 1; } From b8f0a45fed75cd045a898bb625f063b9ab91e86b Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:51 +0200 Subject: [PATCH 016/152] platform/x86: dell-ddv: Use no_free_ptr() to simplify error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use no_free_ptr() inside dell_wmi_ddv_query_buffer() in order to be able to use __free() with the result of the WMI call. Suggested-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-ddv.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 736d9b1fdcfb..f8903ced461b 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, struct dell_wmi_buffer **result) { - struct dell_wmi_buffer *buffer; struct wmi_buffer output; size_t buffer_size; int ret; - ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); if (ret < 0) return ret; - buffer = output.data; - if (!le32_to_cpu(buffer->raw_size)) { - ret = -ENODATA; + struct dell_wmi_buffer *buffer __free(kfree) = output.data; - goto err_free; - } + if (!le32_to_cpu(buffer->raw_size)) + return -ENODATA; buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); if (buffer_size > output.length) { dev_warn(&wdev->dev, FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", buffer_size, output.length); - ret = -EMSGSIZE; - - goto err_free; + return -EMSGSIZE; } - *result = buffer; + *result = no_free_ptr(buffer); return 0; - -err_free: - kfree(output.data); - - return ret; } static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, From dee82409a883474e4aa38b783c59e0ea776c6868 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 12 Jun 2026 19:16:53 -0700 Subject: [PATCH 017/152] platform/x86: msi-wmi: Reformat msi_wmi_notify() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reformats msi_wmi_notify() to use a switch statement that reduces nesting and prepares the function to support additional ACPI types. Signed-off-by: Derek J. Clark Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260613021654.933618-2-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 71 +++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 4a7ac85c4db4..d00ced756581 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -172,44 +172,51 @@ static const struct backlight_ops msi_backlight_ops = { static void msi_wmi_notify(union acpi_object *obj, void *context) { - struct key_entry *key; + struct key_entry *key = NULL; + int eventcode = 0; - if (obj && obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; + if (!obj) + return; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); - key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, - eventcode); - if (!key) { - pr_info("Unknown key pressed - %x\n", eventcode); + break; + default: + pr_info("Unknown event received\n"); + return; + } + + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); + + if (!key) { + pr_info("Unknown key pressed - 0x%x\n", eventcode); + return; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms + * timeframe -> Key press may result in 10-20 GPEs + */ + if (ktime_to_us(diff) < 1000 * 50) { + pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); return; } + last_pressed = cur; + } - if (event_wmi->quirk_last_pressed) { - ktime_t cur = ktime_get_real(); - ktime_t diff = ktime_sub(cur, last_pressed); - /* Ignore event if any event happened in a 50 ms - timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(diff) < 1000 * 50) { - pr_debug("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", - key->code, ktime_to_us(diff)); - return; - } - last_pressed = cur; - } + /* Brightness is served via acpi video driver */ + if (key->type == KE_KEY && + (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN))) + return; - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (backlight || - (key->code != MSI_KEY_BRIGHTNESSUP && - key->code != MSI_KEY_BRIGHTNESSDOWN))) { - pr_debug("Send key: 0x%X - Input layer keycode: %d\n", - key->code, key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, - true); - } - } else - pr_info("Unknown event received\n"); + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); } static int __init msi_wmi_backlight_setup(void) From 739c7e7d82f2064915773c9e80f5d5b68155288a Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 12 Jun 2026 19:16:54 -0700 Subject: [PATCH 018/152] platform/x86: msi-wmi: Add MSI Claw M-Center keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MSI Claw devices produce WMI events through the MSI WMI hotkeys GUID for some of their buttons. When pressed, these cause spam in the kernel. For the majority of devices these events can be safely ignored as they are duplicated by the AT Translated Set 2 Keyboard device exposed as an evdev. For the MSI Claw A8 BZ2EM model's M-Center Menu button (left of the screen) there is no associated keyboard event, so this event must be exposed. Map this button to the same scancode produced by the AT Keyboard device on other models. This does cause double F15 events on the A1M, 7 AI+ A2VM, and 8 AI+ A2VM, but it appears to be harmless in my testing. Signed-off-by: Derek J. Clark Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260613021654.933618-3-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index d00ced756581..c9db750fe5ae 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -46,6 +46,12 @@ enum msi_scancodes { 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, + CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + /* These are MSI Claw keys, used for MSI M-Center in Windows */ + { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, + + /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, + { KE_IGNORE, CLAW_KEY_QUICK_LONG }, + { KE_IGNORE, CLAW_KEY_VOLUMEUP }, + { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, + { KE_END, 0 } }; @@ -183,6 +198,17 @@ static void msi_wmi_notify(union acpi_object *obj, void *context) eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); break; + case ACPI_TYPE_BUFFER: + /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ + if (obj->buffer.length < 2) + return; + + /* pointer[0] is key ID, pointer[1] is active state. We don't get release + * events, so ignore the active state and treat as autorelease. + */ + eventcode = obj->buffer.pointer[0]; + pr_debug("Eventcode: 0x%x\n", eventcode); + break; default: pr_info("Unknown event received\n"); return; From 83c80495e45eddf64c6525fb582d8db68f256b71 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 14 Jun 2026 13:53:53 +0900 Subject: [PATCH 019/152] platform/x86: dell-wmi-sysman: Don't hex dump attribute security buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_attribute() populates the security area of the BIOS attribute request buffer with the current admin password via populate_security_buffer(), then dumps the whole request buffer with print_hex_dump_bytes(). This can expose the plaintext admin password in the kernel log. The same issue was fixed for the password attribute path by commit d1a196e0a6dc ("platform/x86: dell-wmi-sysman: Don't hex dump plaintext password data"). Remove the remaining dump from the BIOS attribute path. Fixes: e8a60aa7404b ("platform/x86: Introduce support for Systems Management Driver over WMI for Dell Systems") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260614045353.143500-1-sammiee5311@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c index db278ff4cc4d..154e115af4b4 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value) if (ret < 0) goto out; - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, SETATTRIBUTE_METHOD_ID); From bbc497b5231829d32c3a53b6e66be1add76c0064 Mon Sep 17 00:00:00 2001 From: Julian Haarmann Date: Sun, 14 Jun 2026 22:30:26 +0200 Subject: [PATCH 020/152] platform/x86: lenovo/ymc: Only match lower byte in WMI lid switch query response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On newer Lenovo Yoga devices like the "Yoga 9 2-in-1 14IPH11 - Type 83SE", the hinge switch WMI query returns extra data in the upper bits (e.g. 0x50001 laptop mode, 0x50002 tablet mode, ect.). The driver previously checked for exact matches (0x01 laptop, 0x02 tablet, ect.) causing newer switches to not work. Mask the WMI query result to only match the lower byte and ignore upper bits. Signed-off-by: Julian Haarmann Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260614203235.235724-1-julian.haarmann@student.kit.edu Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ymc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c index 1b73a55f1b89..015e046b0fce 100644 --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -8,6 +8,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include +#include #include #include #include @@ -20,6 +22,8 @@ #define LENOVO_YMC_QUERY_INSTANCE 0 #define LENOVO_YMC_QUERY_METHOD 0x01 +#define LENOVO_YMC_STATE_MASK GENMASK(7, 0) + static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); @@ -85,7 +89,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data) "WMI event data is not an integer\n"); goto free_obj; } - code = obj->integer.value; + + /* strip upper bits (e.g. 0x50000) on newer devices */ + code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value); if (!sparse_keymap_report_event(priv->input_dev, code, 1, true)) dev_warn(&wdev->dev, "Unknown key %d pressed\n", code); From 613550ffe30d7f3197c1f900e92c88c3705520f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 15 Jun 2026 13:28:59 +0200 Subject: [PATCH 021/152] platform/surface: aggregator: Consistently define ssam_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the the two struct ssam_device_id arrays were initialized by list expressions. This isn't easily readable if you don't work with the Surface System Aggregator core regularily. Using named initializers is more explicit and thus easier to parse and also more robust to changes of the struct definition. This robustness is relevant for a planned change to struct ssam_device_id replacing .driver_data by an anonymous union. This change doesn't introduce changes to the compiled ssam_device_id arrays. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Maximilian Luz Link: https://patch.msgid.link/4421c8c959452d8a717ebc7cc905ad9c2912680c.1781522576.git.u.kleine-koenig@baylibre.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_hub.c | 9 +++++++-- drivers/platform/surface/surface_aggregator_tabletsw.c | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 8b8b80228c14..541e9180f976 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_hub_match[] = { - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), + .driver_data = (unsigned long)&kip_hub, + }, { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), + .driver_data = (unsigned long)&base_hub, + }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index ffa36ed92897..13031c329553 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_tablet_sw_match[] = { - { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, - { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { + SSAM_SDEV(KIP, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_kip_sw_desc, + }, { + SSAM_SDEV(POS, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_pos_sw_desc, + }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); From 4340038998255dda56755209be9ed02dd91025d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 15 Jun 2026 14:51:37 +0200 Subject: [PATCH 022/152] power: supply: surface_{battery,charger}: Consistently define ssam_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the the two struct ssam_device_id arrays were initialized by list expressions. This isn't easily readable if you don't work with the Surface System Aggregator core regularily. Using named initializers is more explicit and thus easier to parse and also more robust to changes of the struct definition. This robustness is relevant for a planned change to struct ssam_device_id replacing .driver_data by an anonymous union. While touching these arrays, also drop the comma after the list terminators. This change doesn't introduce changes to the compiled ssam_device_id arrays. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Maximilian Luz Link: https://patch.msgid.link/bc8eff03b2f36c82af5a75fc7114c277228921db.1781526433.git.u.kleine-koenig@baylibre.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/power/supply/surface_battery.c | 11 ++++++++--- drivers/power/supply/surface_charger.c | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index c759add4df49..1273b6082311 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { }; static const struct ssam_device_id surface_battery_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, - { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat1, + }, { + SSAM_SDEV(BAT, KIP, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_battery_match); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 90b823848c99..d4bba6b41794 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { }; static const struct ssam_device_id surface_ac_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x01), + .driver_data = (unsigned long)&spwr_psy_props_adp1, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_ac_match); From 71eb3db934d779c3a2e397025068088994afb94e Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 17 Jun 2026 10:50:15 +0200 Subject: [PATCH 023/152] platform/x86: toshiba_bluetooth: Use more common error handling code in toshiba_bt_rfkill_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use an existing label once more so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Link: https://patch.msgid.link/fa7a5865-6dda-4305-ab48-e0c9310520c8@web.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_bluetooth.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index e00abba58c7c..bf199fba4999 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bt_dev); result = toshiba_bluetooth_sync_status(bt_dev); - if (result) { - kfree(bt_dev); - return result; - } + if (result) + goto err_free_bt_dev; bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", &pdev->dev, From 4c7d1b4cd75a775326e6802608440f7642284ea6 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:31 +0530 Subject: [PATCH 024/152] platform/x86/amd/hsmp: Validate ACPI UID before parsing socket index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_get_uid() passed the device UID directly to kstrtou16(uid + 2) without checking it. A NULL UID or one shorter than three characters would dereference a NULL pointer or read past the end of the string. Reject such UIDs with -EINVAL before stripping the "ID" prefix. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-2-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 97ed71593bdf..4a1ce4cb25e7 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } From 0d7a2664494534d6152be9bd9bf1cb1d089c67d3 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:32 +0530 Subject: [PATCH 025/152] platform/x86/amd/hsmp: Validate _DSD mailbox sub-package element count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_dsd() dereferenced elements[0] and elements[1] of each mailbox sub-package before confirming the package actually held two elements, allowing an out-of-bounds read on a malformed _DSD. Verify package.count >= 2 first, then fetch the string and integer objects. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-3-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 4a1ce4cb25e7..8c3185ae6395 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -151,12 +151,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; From b558cbed39af2f7dc6cd86c00916566fc6f170d3 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:33 +0530 Subject: [PATCH 026/152] platform/x86/amd/hsmp: Pass struct device explicitly to ACPI mailbox parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_crs() and hsmp_read_acpi_dsd() read the ACPI handle and emit error messages via sock->dev. Pass the struct device explicitly to both helpers instead of reading it back from sock->dev. This is a pure refactor with no functional change; it prepares for publishing sock->dev as the data-plane readiness gate only after the socket has been fully initialized, so the parsers must not depend on sock->dev already being set. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 8c3185ae6395..1fb09251d826 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -107,7 +107,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -116,10 +116,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -142,7 +142,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -192,14 +192,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -207,10 +207,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -232,12 +232,12 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + return hsmp_read_acpi_dsd(dev, sock); } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, From e5e511f5d9e05e075d114318e707e191ada6e145 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:34 +0530 Subject: [PATCH 027/152] platform/x86/amd/hsmp: Gate the data plane on a fully initialized socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_parse_acpi_table() published sock->dev before hsmp_read_acpi_crs() had mapped virt_base_addr. sock->dev is the readiness gate for the lock-free data plane, so on a multi-socket system - where socket 0 exposes /dev/hsmp before later sockets finish probing - an ioctl aimed at a socket still in bring-up could pass the gate and dereference a NULL virt_base_addr. Publish sock->dev last with smp_store_release() once virt_base_addr, the mailbox offsets and the semaphore are initialized, and read it with smp_load_acquire() in hsmp_send_message() so a non-NULL dev guarantees the rest of the socket state is visible. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-5-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 18 ++++++++++++++++-- drivers/platform/x86/amd/hsmp/hsmp.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 1fb09251d826..72f68cef1297 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -224,7 +224,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -237,7 +236,22 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(dev, sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 6a26937fc2b5..1a87931136fd 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -223,6 +223,19 @@ int hsmp_send_message(struct hsmp_message *msg) sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); sock = &hsmp_pdev.sock[sock_ind]; + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and + * the semaphore are visible. + * + * Pairs with smp_store_release(&sock->dev) in hsmp_parse_acpi_table(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; + ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) return ret; From f82c6c9ac993003da3171e60f708f4be8b27041b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:19:59 +0200 Subject: [PATCH 028/152] platform/x86: asus-laptop: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() which is never read afterward, so make it stop doing that and drop the symbol defined specifically for this purpose. Likewise, acpi_device_class() set by the driver is only used for generating ACPI netlink events, but the "hotkey" string literal may as well be used directly for that, so make the driver do so and stop setting acpi_device_class(), and drop the symbol defined specifically for this purpose. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/5106055.GXAFRqVoOG@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-laptop.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 140ac8a10537..449addd1ac7a 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -42,8 +42,6 @@ #define ASUS_LAPTOP_VERSION "0.42" #define ASUS_LAPTOP_NAME "Asus Laptop Support" -#define ASUS_LAPTOP_CLASS "hotkey" -#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" #define ASUS_LAPTOP_FILE KBUILD_MODNAME #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." @@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) /* TODO Find a better way to handle events count. */ count = asus->event_count[event % 128]++; - acpi_bus_generate_netlink_event(asus->device->pnp.device_class, - dev_name(&asus->device->dev), event, - count); + acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), + event, count); if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) event = ATKD_BRNUP; @@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev) if (!asus) return -ENOMEM; asus->handle = device->handle; - strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); - strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); asus->device = device; asus_dmi_check(); From 073bab8ee7881a212a227a94a86d90b2d3dc7b4f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:21:09 +0200 Subject: [PATCH 029/152] platform/x86: eeepc-laptop: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() which is never read afterward, so make it stop doing that and drop the symbol defined specifically for this purpose. Likewise, acpi_device_class() set by the driver is only used for generating ACPI netlink events, but the "hotkey" string literal may as well be used directly for that, so make the driver do so and stop setting acpi_device_class(), and drop the symbol defined specifically for this purpose. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2291726.irdbgypaU6@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/eeepc-laptop.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index d18a80907611..8a640c25830a 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -34,8 +34,6 @@ #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" #define EEEPC_LAPTOP_FILE "eeepc" -#define EEEPC_ACPI_CLASS "hotkey" -#define EEEPC_ACPI_DEVICE_NAME "Hotkey" #define EEEPC_ACPI_HID "ASUS010" MODULE_AUTHOR("Corentin Chary, Eric Cooper"); @@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) if (event > ACPI_MAX_SYS_NOTIFY) return; count = eeepc->event_count[event % 128]++; - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, + acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, count); /* Brightness events are special */ @@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev) if (!eeepc) return -ENOMEM; eeepc->handle = device->handle; - strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); - strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); eeepc->device = device; platform_set_drvdata(pdev, eeepc); From e235507af5618323696923a8e73090563f4d8a6b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:23:31 +0200 Subject: [PATCH 030/152] platform/x86: fujitsu-laptop: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() that are only used for printing messages and initializing input class device names. Since ACPI_FUJITSU_BL_DEVICE_NAME and ACPI_FUJITSU_LAPTOP_DEVICE_NAME can be used directly in all of those cases, make the driver do so and stop setting acpi_device_name() in it. Likewise, acpi_device_class() set by the driver are never read afterward, so make the driver stop setting it and drop the symbol defined specifically for this purpose. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Acked-by: Jonathan Woithe Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3441919.aeNJFYEL58@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-laptop.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 54d0b9cec4d3..ea543deef68f 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -56,7 +56,6 @@ #define FUJITSU_LCD_N_LEVELS 8 -#define ACPI_FUJITSU_CLASS "fujitsu" #define ACPI_FUJITSU_BL_HID "FUJ02B1" #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" @@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev) snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", acpi_device_hid(device)); - priv->input->name = acpi_device_name(device); + priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; priv->input->phys = priv->phys; priv->input->id.bustype = BUS_HOST; priv->input->id.product = 0x06; @@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev) return -ENOMEM; fujitsu_bl = priv; - strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); platform_set_drvdata(pdev, priv); - pr_info("ACPI: %s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, + acpi_device_bid(device)); if (get_max_brightness(&pdev->dev) <= 0) priv->max_brightness = FUJITSU_LCD_N_LEVELS; @@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev) snprintf(priv->phys, sizeof(priv->phys), "%s/input0", acpi_device_hid(device)); - priv->input->name = acpi_device_name(device); + priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; priv->input->phys = priv->phys; priv->input->id.bustype = BUS_HOST; @@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); fext = &pdev->dev; - strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); - platform_set_drvdata(pdev, priv); /* kfifo */ @@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) if (ret) return ret; - pr_info("ACPI: %s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, + acpi_device_bid(device)); while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && i++ < MAX_HOTKEY_RINGBUFFER_SIZE) From 37b35c06542a0449b407715d9115e1a3382710bd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:28:41 +0200 Subject: [PATCH 031/152] platform/x86: fujitsu-tablet: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_class() which is never read afterward, so make it stop doing that and drop the symbol defined specifically for this purpose. For consistency, also make it stop setting acpi_device_name() and add a name field to the "fujitsu" structure for storing the name string used during input class device initialization. While at it, use scnprintf() instead of snprintf() for populating the name and phys fields of the "fujitsu" structure. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2378407.iZASKD2KPV@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-tablet.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index 2f8c1b89cbca..e1f5dc86dc4d 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -22,8 +22,6 @@ #define MODULENAME "fujitsu-tablet" -#define ACPI_FUJITSU_CLASS "fujitsu" - #define INVERT_TABLET_MODE_BIT 0x01 #define INVERT_DOCK_STATE_BIT 0x02 #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 @@ -160,6 +158,7 @@ static struct { struct fujitsu_config config; unsigned long prev_keymask; + char name[17]; char phys[21]; int irq; @@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev) if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) return -ENODEV; - sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); - sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); + scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); + scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); - snprintf(fujitsu.phys, sizeof(fujitsu.phys), - "%s/input0", acpi_device_hid(adev)); - - error = input_fujitsu_setup(&pdev->dev, - acpi_device_name(adev), fujitsu.phys); + error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); if (error) return error; From ddbbe8b75a2716f700af71ee487cb1f4eefc4891 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:30:00 +0200 Subject: [PATCH 032/152] platform/x86: thinkpad_acpi: Stop setting acpi_device_class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To facilitate the removal of device_class from struct acpi_device_pnp in the future, make the driver store the device class string used for generating ACPI netlink events in a new device_class field in struct ibm_struct. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Mark Pearson Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/23182921.EfDdHjke4D@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 445e1403308e..f614b2701d48 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -309,6 +309,7 @@ struct tp_acpi_drv_struct { struct ibm_struct { char *name; + acpi_device_class device_class; int (*read) (struct seq_file *); int (*write) (char *); @@ -838,9 +839,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) } ibm->acpi->device->driver_data = ibm; - scnprintf(acpi_device_class(ibm->acpi->device), - sizeof(acpi_device_class(ibm->acpi->device)), - "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); + scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", + TPACPI_ACPI_EVENT_PREFIX, ibm->name); status = acpi_install_notify_handler(*ibm->acpi->handle, ibm->acpi->type, dispatch_acpi_notify, ibm); @@ -3869,7 +3869,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) pr_err("unknown HKEY notification event %d\n", event); /* forward it to userspace, maybe it knows how to handle it */ acpi_bus_generate_netlink_event( - ibm->acpi->device->pnp.device_class, + ibm->device_class, dev_name(&ibm->acpi->device->dev), event, 0); return; @@ -3949,7 +3949,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) /* netlink events */ if (send_acpi_ev) { acpi_bus_generate_netlink_event( - ibm->acpi->device->pnp.device_class, + ibm->device_class, dev_name(&ibm->acpi->device->dev), event, hkey); } From 125b27a6aeb52331122162a097f632b5878799aa Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:30:56 +0200 Subject: [PATCH 033/152] platform/x86: panasonic-laptop: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() and acpi_device_class() which are never read afterward, so make it stop doing that and drop the symbols defined specifically for this purpose. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3979672.kQq0lBPeGt@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/panasonic-laptop.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 719add753cb3..3effb11c4153 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); #define ECO_MODE_ON 0x80 #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" -#define ACPI_PCC_DEVICE_NAME "Hotkey" -#define ACPI_PCC_CLASS "pcc" #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" @@ -1017,8 +1015,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev) pcc->device = device; pcc->handle = device->handle; device->driver_data = pcc; - strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_PCC_CLASS); result = acpi_pcc_init_input(pcc); if (result) { From 38e88f19fd8bdb06bf4eb1a274b8d34853157495 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:32:31 +0200 Subject: [PATCH 034/152] platform/x86: sony-laptop: Stop setting acpi_device_class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since acpi_device_class() set by the driver is only used for generating ACPI netlink events and the "sony/hotkey" string literal may as well be used directly for this purpose, make the driver do so and stop setting acpi_device_class() in it. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/9664323.CDJkKcVGEf@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/sony-laptop.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 67370967df6f..0bf4d3054e37 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data) ev_type = HOTKEY; sony_laptop_report_input_event(real_ev); } - acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, + acpi_bus_generate_netlink_event("sony/hotkey", dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); } @@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev) return -ENODEV; sony_nc_acpi_device = device; - strscpy(acpi_device_class(device), "sony/hotkey"); - sony_nc_acpi_handle = device->handle; /* read device status */ @@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev) return -ENODEV; spic_dev.acpi_dev = device; - strscpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); mutex_init(&spic_dev.lock); From c08e14511a959edd6101853fbd3adb633bce3550 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:36:05 +0200 Subject: [PATCH 035/152] platform/x86: toshiba_acpi: Do not use uninitialized device_class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pnp.device_class string in the ACPI companion of the platform device used for binding the driver is never initialized, so passing it to acpi_bus_generate_netlink_event() is effectively equivalent to passing an empty string literal to that function. Accordingly, make the driver do the latter instead of doing the former. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3642854.QJadu78ljV@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_acpi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 7cecb3a70b9c..a0b8060836d0 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) LED_FULL : LED_OFF); /* Emulate the keyboard backlight event */ - acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, - dev_name(&toshiba_acpi->acpi_dev->dev), + acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), 0x92, 0); } @@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) break; } - acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, - dev_name(&acpi_dev->dev), + acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), event, (event == 0x80) ? dev->last_key_event : 0); } From 552d3203419791bf9a4af47617c504c13de8fe14 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:36:23 +0200 Subject: [PATCH 036/152] platform/x86: toshiba_haps: Do not use uninitialized device_class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pnp.device_class string in the ACPI companion of the platform device used for binding the driver is never initialized, so passing it to acpi_bus_generate_netlink_event() is effectively equivalent to passing an empty string literal to that function. Accordingly, make the driver do the latter instead of doing the former. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/1987644.CQOukoFCf9@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_haps.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index 8d12241924df..c6633b74029f 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) pr_debug("Received event: 0x%x\n", event); - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), - event, 0); + acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); } static void toshiba_haps_remove(struct platform_device *pdev) From eb0d94ad1e606af20f8b27141f7227405f764f16 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 13:37:57 +0200 Subject: [PATCH 037/152] platform/x86: xo15-ebook: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_class() which is never read afterward, so make it stop doing that and drop the two symbols defined specifically for this purpose. Likewise, acpi_device_name() set by the driver is only used for input class device name initialization and the "EBook Switch" string literal may as well be used directly for that, so make the driver do so and stop setting acpi_device_name(), which allows the symbol defined specifically for this purpose to be dropped. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2731795.Lt9SDvczpP@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/xo15-ebook.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c index e40e385c52bd..4c3185e2ceec 100644 --- a/drivers/platform/x86/xo15-ebook.c +++ b/drivers/platform/x86/xo15-ebook.c @@ -19,13 +19,10 @@ #define MODULE_NAME "xo15-ebook" -#define XO15_EBOOK_CLASS MODULE_NAME #define XO15_EBOOK_TYPE_UNKNOWN 0x00 #define XO15_EBOOK_NOTIFY_STATUS 0x80 -#define XO15_EBOOK_SUBCLASS "ebook" #define XO15_EBOOK_HID "XO15EBK" -#define XO15_EBOOK_DEVICE_NAME "EBook Switch" MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); MODULE_LICENSE("GPL"); @@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev) if (!id) return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); - strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); - strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); - snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); - input->name = acpi_device_name(device); + input->name = "EBook Switch"; input->phys = button->phys; input->id.bustype = BUS_HOST; From 1b257f94de20efcc1f5de6208104d4e1f2651823 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 3 Jul 2026 14:41:51 +0200 Subject: [PATCH 038/152] platform/x86: topstar-laptop: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() and acpi_device_class() which are never read afterward, so make it stop doing that. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/12951216.O9o76ZdvQC@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/topstar-laptop.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index e09d7f8ce45f..0e842c55dcc5 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, topstar); - strscpy(acpi_device_name(device), "Topstar TPSACPI"); - strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); topstar->device = device; err = topstar_acpi_init(topstar); From e8e52a2b543758399f5a874535c01807436a6f4c Mon Sep 17 00:00:00 2001 From: Shaposhnikov Daniil <2minesweeper2@gmail.com> Date: Mon, 25 May 2026 18:00:57 +0500 Subject: [PATCH 039/152] platform/x86: huawei-wmi: add ACPI fallback for Fn-lock on newer models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer Huawei laptops (e.g. FLMH-XX / MateBook 14 2024) no longer support the legacy WMI interface for Fn-lock control. Instead, they expose direct ACPI methods \GFRS and \SFRS (Get/Set Fn key Reversal Status) which communicate with the EC via registers 0x6B (read) and 0x6C (write). Add huawei_acpi_fn_lock_get() and huawei_acpi_fn_lock_set() helpers that use acpi_evaluate_object() to call these methods. Both huawei_wmi_fn_lock_get() and huawei_wmi_fn_lock_set() now probe for \GFRS/\SFRS via acpi_has_method() first and fall back to the legacy WMI path if not present. Tested on: HUAWEI FLMH-XX (MateBook 14 2024), CachyOS (kernel 7.0.9-1-cachyos). Signed-off-by: Shaposhnikov Daniil <2minesweeper2@gmail.com> Link: https://patch.msgid.link/20260525130058.7408-2-2minesweeper2@gmail.com [ij: removed a few blank lines and "legacy"s from comments] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/huawei-wmi.c | 99 +++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 93cca17fdf58..d6aaf14d66a5 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) /* Fn lock */ +/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ +#define FN_LOCK_ACPI_OFF 1 +#define FN_LOCK_ACPI_ON 2 +#define FN_LOCK_ACPI_STAT_OK 0 + +/* + * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct + * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control + * Fn-lock via EC registers 0x6B (read) and 0x6C (write). + * + * GFRS response buffer layout: + * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) + * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) + * + * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): + * Value is read from byte[2] of the integer argument, so it must be + * passed as (value << 16): + * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) + * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) + */ + +static int huawei_acpi_fn_lock_get(int *on) +{ + union acpi_object acpi_arg; + struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + acpi_arg.type = ACPI_TYPE_INTEGER; + acpi_arg.integer.value = 0; + + status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + union acpi_object *obj __free(kfree) = output.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) + return -ENODATA; + + /* byte[0] = STAT, byte[1] = fn-lock state */ + if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) + return -EIO; + + switch (obj->buffer.pointer[1]) { + case FN_LOCK_ACPI_OFF: + if (on) + *on = 0; + break; + case FN_LOCK_ACPI_ON: + if (on) + *on = 1; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int huawei_acpi_fn_lock_set(int on) +{ + union acpi_object acpi_arg; + struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + /* + * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). + * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) + * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) + */ + acpi_arg.type = ACPI_TYPE_INTEGER; + acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; + + status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + union acpi_object *obj __free(kfree) = output.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) + return -ENODATA; + + if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) + return -EIO; + + return 0; +} + static int huawei_wmi_fn_lock_get(int *on) { u8 ret[0x100] = { 0 }; int err, i; + /* Newer models: use direct ACPI \GFRS method */ + if (acpi_has_method(NULL, "\\GFRS")) + return huawei_acpi_fn_lock_get(on); + + /* WMI fallback */ err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); if (err) return err; @@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) { union hwmi_arg arg; + /* Newer models: use direct ACPI \SFRS method */ + if (acpi_has_method(NULL, "\\SFRS")) + return huawei_acpi_fn_lock_set(on); + + /* WMI fallback */ arg.cmd = FN_LOCK_SET; arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. From a7320d6eb9c4240c948cd9c64582b3bd04cbaf4b Mon Sep 17 00:00:00 2001 From: Krishna Chomal Date: Tue, 23 Jun 2026 19:43:14 +0530 Subject: [PATCH 040/152] platform/x86: hp-wmi: Add support for OMEN MAX 16-ak0xxx (8DD6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN MAX 16-ak0xxx (board ID: 8DD6) has the same WMI interface as other Victus S boards, but requires quirks for correctly switching thermal profile. After testing we know that (similar to another HP Omen Max 16 device, board ID 8D87), the embedded controller on this board does not expose thermal profile which means we have to intentionally disable EC readback. Add the DMI board name to victus_s_thermal_profile_boards[] table and map it to omen_v1_no_ec_thermal_params. Testing on board 8DD6 confirmed that platform profile is registered successfully and fan RPMs are readable and controllable. Tested-by: Yahia Ahmed Cc: stable@vger.kernel.org # v6.18+ Signed-off-by: Krishna Chomal Link: https://patch.msgid.link/20260623141314.33947-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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 0dcf2901259e..34c9b941bdd8 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -310,6 +310,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, .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, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, .driver_data = (void *)&omen_v1_legacy_board_params, From c036e9e5643d2b09eee51cdd8d1605f7b41d7b10 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 21 Jun 2026 14:28:59 +0200 Subject: [PATCH 041/152] platform/x86: asus-armoury: Add power limits quirk for FA401KM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add power limits quirk entry for ASUS ROG FA401KM laptop. The limits are extracted from the device's ThrottleGear XML configuration file for the 'Ryzen' profile. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260621123055.5465-2-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(+) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 65166b50a2c3..81bcd2f109ca 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), + }, + .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_max = 80, + .ppt_pl1_spl_min = 15, + .ppt_pl2_sppt_max = 80, + .ppt_pl2_sppt_min = 35, + .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 = 25, + .ppt_pl2_sppt_max = 44, + .ppt_pl2_sppt_min = 31, + .ppt_pl3_fppt_max = 65, + .ppt_pl3_fppt_min = 45, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), From c18eb1244741ac37b66ac526969132181608ed0c Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 21 Jun 2026 16:02:04 +0300 Subject: [PATCH 042/152] platform/x86: asus-armoury: Add power limits for ROG Strix SCAR 16 (G635LX) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the AC and DC power limit definitions for the ASUS ROG Strix SCAR 16 2025 (G635LX), enabling the nv_tgp, nv_dynamic_boost and ppt_pl*_* tuning attributes on this model. Without an entry in the power_limits table the attributes are not exposed (has_valid_limit() returns false) and the firmware power knobs cannot be controlled. The G635LX ships an NVIDIA RTX 5090 Laptop GPU (150 W base TGP + 25 W Dynamic Boost = 175 W max, per the ASUS specification) and an Intel Core Ultra 9 275HX, matching the ROG Strix SCAR 18 (G835LW). The limits are therefore taken from the G835LW sibling entry and the ASUS specification rather than dumped from this board's firmware. Testing on a G635LX confirmed the attributes populate with this entry and that writing nv_dynamic_boost changes the measured GPU power draw (~151 W at minimum, ~169 W at maximum during inference), i.e. the limits reach firmware. Signed-off-by: Avi Fenesh Link: https://patch.msgid.link/20260621130204.103566-1-aviarchi1994@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 81bcd2f109ca..53f9e2349816 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -2111,6 +2111,35 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G635LX"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 140, + .ppt_pl1_spl_max = 175, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 175, + .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 = 150, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 55, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 70, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "G713PV"), From 32ca220f74fec5f92ac831f867aee74d0215c0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Barroso=20Neves?= Date: Sun, 5 Jul 2026 16:51:26 -0300 Subject: [PATCH 043/152] platform/x86: asus-armoury: add support for HN7306EA and HN7306EAC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TDP data for laptop models HN7306EA and HN7306EAC. Signed-off-by: Flávio Barroso Neves Link: https://patch.msgid.link/20260705195126.17120-1-flaviobn@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 53f9e2349816..09a3eaf2e9f9 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -2383,6 +2383,35 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 35, + .ppt_pl1_spl_def = 60, + .ppt_pl1_spl_max = 85, + .ppt_pl2_sppt_min = 40, + .ppt_pl2_sppt_def = 70, + .ppt_pl2_sppt_max = 95, + .ppt_pl3_fppt_min = 50, + .ppt_pl3_fppt_def = 85, + .ppt_pl3_fppt_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 35, + .ppt_pl1_spl_def = 45, + .ppt_pl1_spl_max = 60, + .ppt_pl2_sppt_min = 40, + .ppt_pl2_sppt_def = 55, + .ppt_pl2_sppt_max = 70, + .ppt_pl3_fppt_min = 50, + .ppt_pl3_fppt_def = 65, + .ppt_pl3_fppt_max = 85, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71"), From 9e917ab411eab0308ad129a9c5b398f0a9ddfd58 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 1 Jul 2026 18:39:35 +0200 Subject: [PATCH 044/152] platform/x86: asus-armoury: Add power limits quirk for FA608WV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add power limits quirk entry for ASUS ROG FA608WV laptop. The limits are extracted from the device's ThrottleGear XML configuration file for the 'Ryzen' profile. Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260701164003.4271-1-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 09a3eaf2e9f9..bc899bec7943 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -786,6 +786,40 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), + }, + .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, + .nv_tgp_max = 115, + .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, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), From 5223acf679d1d59ec80db929bb320d02aa9a353d Mon Sep 17 00:00:00 2001 From: Vishnu Sankar Date: Thu, 9 Jul 2026 11:15:04 +0900 Subject: [PATCH 045/152] platform/x86: thinkpad_acpi: Add USB-C Security (USCS) support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer ThinkPad systems expose a USB-C Security (Restricted Mode) feature. When active, USB-C data connections are disabled while power delivery is preserved. This is useful for kiosk and physically-secured deployments. Hardware interface: The HKEY device exposes a read-only ACPI method USCS(): Return value bit layout: Bit 16 : Capability flag (1 = feature present on this SKU) Bit 0 : Current state (0 = security OFF, 1 = security ON) The sysfs attribute is read-only. The Fn+U followed by Fn+S hotkey chord is the only way to toggle the hardware state. Hotkey: Fn+U followed by Fn+S generates HKEY event 0x131e. sysfs interface: /sys/devices/platform/thinkpad_acpi/usb_c_security (read-only) "enabled\n" -- data connections are currently blocked "disabled\n" -- data connections are currently allowed The attribute is hidden on SKUs where the USCS capability bit (bit 16) is not set, so there is no ABI impact on unsupported hardware. Suggested-by: Mark Pearson Signed-off-by: Vishnu Sankar Link: https://patch.msgid.link/20260709021504.465792-1-vishnuocv@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../admin-guide/laptops/thinkpad-acpi.rst | 24 ++++ drivers/platform/x86/lenovo/thinkpad_acpi.c | 117 ++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst index f874db31801d..db4588af0278 100644 --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst @@ -1543,6 +1543,30 @@ Values: This setting can also be toggled via the Fn+doubletap hotkey. +USB-C Security +-------------- + +sysfs: usb_c_security + +Reports the current state of the USB-C Security (Restricted Mode) feature +on supported ThinkPad systems. When enabled, USB-C data connections are +disabled while power delivery is preserved. + +The available command is:: + + cat /sys/devices/platform/thinkpad_acpi/usb_c_security + +Values: + + * ``enabled`` - USB-C data connections are currently blocked + * ``disabled`` - USB-C data connections are currently allowed + +The attribute is read-only. The USB-C Security state can only be toggled +via the Fn+U followed by Fn+S hotkey chord. + +The sysfs attribute is not created on platforms that do not support this +feature. + Auxmac ------ diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index f614b2701d48..6dd7c28fc0db 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t { TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ + TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ @@ -375,6 +378,8 @@ static struct { u32 has_adaptive_kbd:1; u32 kbd_lang:1; u32 trackpoint_doubletap_enable:1; + u32 usbc_security_supported:1; + bool usbc_security_enabled; struct quirk_entry *quirks; } tp_features; @@ -11285,6 +11290,111 @@ static struct ibm_struct hwdd_driver_data = { .name = "hwdd", }; +/************************************************************************* + * USB-C Security subdriver + * + * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. + * It always returns: + * bit 16 - USB-C security capability present on this SKU or not + * bit 0 - USB-C Security state (enable or disable) + * + * Hotkey + * ------ + * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. + * The driver reads back the new state and notifies the sysfs attribute. + */ + +/* USCS() return word bit layout */ +#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ +#define USCS_STATUS_BIT BIT(0) /* current security state */ + +/* Protects USCS() ACPI method calls in usbc_security_query() */ +static DEFINE_MUTEX(usbc_security_mutex); + +/** + * usbc_security_query - read current USB-C security state via USCS() + * @enabled: out - true when security is ON (data connections blocked) + * + * Returns: + * 0 success, @enabled contains the current state + * -EIO ACPI evaluation failed + * -ENODEV capability bit absent; feature not present on this SKU* + */ +static int usbc_security_query(bool *enabled) +{ + int status; + + guard(mutex)(&usbc_security_mutex); + if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) + return -EIO; + + if (!(status & USCS_CAP_BIT)) { + pr_debug("USCS cap bit absent (raw=0x%x)\n", status); + return -ENODEV; + } + + *enabled = status & USCS_STATUS_BIT; + return 0; +} + +/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ +static ssize_t usb_c_security_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", + str_enabled_disabled(tp_features.usbc_security_enabled)); +} + +static DEVICE_ATTR_RO(usb_c_security); + +static struct attribute *usbc_security_attributes[] = { + &dev_attr_usb_c_security.attr, + NULL, +}; + +static umode_t usbc_security_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + return tp_features.usbc_security_supported ? attr->mode : 0; +} + +static const struct attribute_group usbc_security_attr_group = { + .is_visible = usbc_security_attr_is_visible, + .attrs = usbc_security_attributes, +}; + +static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) +{ + int err; + + err = usbc_security_query(&tp_features.usbc_security_enabled); + if (err == -ENODEV) + return 0; + if (err) + return err; + + tp_features.usbc_security_supported = true; + return 0; +} + +/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ +static bool tpacpi_usbc_security_hotkey(void) +{ + if (!tp_features.usbc_security_supported) + return false; + + if (usbc_security_query(&tp_features.usbc_security_enabled)) + return false; + + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); + return true; +} + +static struct ibm_struct usbc_security_driver_data = { + .name = "usbc_security", +}; + /* --------------------------------------------------------------------- */ static struct attribute *tpacpi_driver_attributes[] = { @@ -11345,6 +11455,7 @@ static const struct attribute_group *tpacpi_groups[] = { &dprc_attr_group, &auxmac_attr_group, &hwdd_attr_group, + &usbc_security_attr_group, NULL, }; @@ -11499,6 +11610,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) case TP_HKEY_EV_PROFILE_TOGGLE2: platform_profile_cycle(); return true; + case TP_HKEY_EV_USB_C_SECURITY: + return tpacpi_usbc_security_hotkey(); } return false; @@ -11964,6 +12077,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_hwdd_init, .data = &hwdd_driver_data, }, + { + .init = tpacpi_usbc_security_init, + .data = &usbc_security_driver_data, + }, }; static int __init set_ibm_param(const char *val, const struct kernel_param *kp) From 3e91964aa74ab261aa15d9d96318eded2fd9d22a Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Jul 2026 21:55:49 +0200 Subject: [PATCH 046/152] platform/x86: lg-laptop: Fix LED resource handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The event notification callback might access kbd_backlight even when it was not successfully registered with the LED subsystem. The same happens inside acpi_remove(), where the LED devices are unregistered unconditionally. Fix this by tracking the availability of the kbd_backlight LED device and use devm_led_classdev_register() to let devres take care of unregistering the LED devices during removal. For this the parent device of the LED devices is changed to the native platform device. Fixes: ae26278829a8 ("platform/x86: lg-laptop: Use correct event for keyboard backlight FN-key") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260708195553.7762-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lg-laptop.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index a8f2f465ef3f..a2db9657027e 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -100,6 +100,7 @@ static u32 inited; #define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; +static bool kbd_backlight_available; static struct led_classdev kbd_backlight; static enum led_brightness get_kbd_backlight_level(struct device *dev); @@ -214,6 +215,7 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u static void wmi_notify(union acpi_object *obj, void *context) { long data = (long)context; + unsigned int brightness; pr_debug("event guid %li\n", data); if (!obj) @@ -224,8 +226,11 @@ static void wmi_notify(union acpi_object *obj, void *context) struct key_entry *key; if (eventcode == 0x10000000) { - led_classdev_notify_brightness_hw_changed( - &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); + if (kbd_backlight_available) { + brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); + led_classdev_notify_brightness_hw_changed(&kbd_backlight, + brightness); + } } else { key = sparse_keymap_entry_from_scancode( wmi_input_dev, eventcode); @@ -865,8 +870,13 @@ static int acpi_probe(struct platform_device *pdev) goto out_platform_device; /* LEDs are optional */ - led_classdev_register(&pf_device->dev, &kbd_backlight); - led_classdev_register(&pf_device->dev, &tpad_led); + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) + kbd_backlight_available = false; + else + kbd_backlight_available = true; + + devm_led_classdev_register(&pdev->dev, &tpad_led); wmi_input_setup(); battery_hook_register(&battery_hook); @@ -884,9 +894,6 @@ static void acpi_remove(struct platform_device *pdev) { sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); - led_classdev_unregister(&tpad_led); - led_classdev_unregister(&kbd_backlight); - battery_hook_unregister(&battery_hook); wmi_input_destroy(); platform_device_unregister(pf_device); From ad873d6340957c1ed5a271eb7ebc8b873c42ba9d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Jul 2026 21:55:50 +0200 Subject: [PATCH 047/152] platform/x86: lg-laptop: Add support for native ACPI events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LG devices support two interfaces for receiving events: - the WMI-based interface currently being used by the driver - a ACPI-based interface similar to the WMI-based interface Older devices use the WMI-based interface by default and need to be manually switched into ACPI mode. Newer devices however only support the ACPI-based interface, preventing the current driver from receiving events on them. Fix this by always using the native ACPI-based interface for receiving events. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260708195553.7762-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/Kconfig | 1 - drivers/platform/x86/lg-laptop.c | 308 ++++++++++++++++++++----------- 2 files changed, 203 insertions(+), 106 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index b54b5212b204..957034f39e4e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -802,7 +802,6 @@ config LG_LAPTOP tristate "LG Laptop Extras" depends on ACPI depends on ACPI_BATTERY - depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP select NEW_LEDS diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index a2db9657027e..9ba1e872b6fe 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -8,6 +8,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -57,13 +59,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8 #define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8 -#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" -#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" -#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" -#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" -#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" -#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" -#define WMI_EVENT_GUID WMI_EVENT_GUID0 +#define LG_NOTIFY_TABLET_MODE_OFF 0x50 +#define LG_NOTIFY_TABLET_MODE_ON 0x51 +#define LG_NOTIFY_HOTKEY 0x80 +#define LG_NOTIFY_THERMAL 0x81 +#define LG_NOTIFY_MISC 0x82 + +#define LG_OREP_READ_EC 0 +#define LG_OREP_WRITE_EC 1 +#define LG_OREP_DEBUG 2 +#define LG_OREP_UPDATE_SYSTEM_STATE 3 +#define LG_OREP_INTERCEPT_WMI_EVENTS 4 +#define LG_OREP_WAKE_ON_LAN 6 #define SB_GGOV_METHOD "\\_SB.GGOV" #define GOV_TLED 0x2020008 @@ -83,21 +90,7 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define PLATFORM_NAME "lg-laptop" -MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); -MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); -MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); -MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); - static struct platform_device *pf_device; -static struct input_dev *wmi_input_dev; - -static u32 inited; -#define INIT_INPUT_WMI_0 0x01 -#define INIT_INPUT_WMI_2 0x02 -#define INIT_INPUT_ACPI 0x04 -#define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; static bool kbd_backlight_available; @@ -115,6 +108,36 @@ static const struct key_entry wmi_keymap[] = { {KE_END, 0} }; +static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, + unsigned long long *result) +{ + union acpi_object objs[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = command, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = value, + }, + } + }; + struct acpi_object_list args = { + .count = ARRAY_SIZE(objs), + .pointer = objs, + }; + acpi_status status; + + status = acpi_evaluate_integer(handle, "OREP", &args, result); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + static int ggov(u32 arg0) { union acpi_object args[1]; @@ -212,70 +235,6 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u return (union acpi_object *)buffer.pointer; } -static void wmi_notify(union acpi_object *obj, void *context) -{ - long data = (long)context; - unsigned int brightness; - - pr_debug("event guid %li\n", data); - if (!obj) - return; - - if (obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; - struct key_entry *key; - - if (eventcode == 0x10000000) { - if (kbd_backlight_available) { - brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); - led_classdev_notify_brightness_hw_changed(&kbd_backlight, - brightness); - } - } else { - key = sparse_keymap_entry_from_scancode( - wmi_input_dev, eventcode); - if (key && key->type == KE_KEY) - sparse_keymap_report_entry(wmi_input_dev, - key, 1, true); - } - } - - pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, - obj->integer.value); -} - -static void wmi_input_setup(void) -{ - acpi_status status; - - wmi_input_dev = input_allocate_device(); - if (wmi_input_dev) { - wmi_input_dev->name = "LG WMI hotkeys"; - wmi_input_dev->phys = "wmi/input0"; - wmi_input_dev->id.bustype = BUS_HOST; - - if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || - input_register_device(wmi_input_dev)) { - pr_info("Cannot initialize input device"); - input_free_device(wmi_input_dev); - return; - } - - inited |= INIT_SPARSE_KEYMAP; - status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, - (void *)0); - if (ACPI_SUCCESS(status)) - inited |= INIT_INPUT_WMI_0; - - status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, - (void *)2); - if (ACPI_SUCCESS(status)) - inited |= INIT_INPUT_WMI_2; - } else { - pr_info("Cannot allocate input device"); - } -} - static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, const char *buffer, size_t count) @@ -634,26 +593,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); -static void wmi_input_destroy(void) -{ - if (inited & INIT_INPUT_WMI_2) - wmi_remove_notify_handler(WMI_EVENT_GUID2); - - if (inited & INIT_INPUT_WMI_0) - wmi_remove_notify_handler(WMI_EVENT_GUID0); - - if (inited & INIT_SPARSE_KEYMAP) - input_unregister_device(wmi_input_dev); - - inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); -} - static struct platform_driver pf_driver = { .driver = { .name = PLATFORM_NAME, } }; +static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) +{ + union acpi_object objs[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = value, + }, + } + }; + struct acpi_object_list args = { + .count = ARRAY_SIZE(objs), + .pointer = objs, + }; + unsigned long long result; + acpi_status status; + + status = acpi_evaluate_integer(handle, "_WED", &args, &result); + if (ACPI_FAILURE(status)) + return -EIO; + + if (result > U32_MAX) + return -EPROTO; + + *data = result; + + return 0; +} + +static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) +{ + unsigned int kbd_brightness; + + switch (value) { + case LG_NOTIFY_HOTKEY: + sparse_keymap_report_event(input_dev, data, 1, true); + break; + case LG_NOTIFY_THERMAL: + /* Currently not supported */ + break; + case LG_NOTIFY_MISC: + switch (data) { + case 0x10000000: + if (!kbd_backlight_available) + break; + + kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); + led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); + break; + default: + sparse_keymap_report_event(input_dev, data, 1, true); + } + break; + default: + break; + } +} + +static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) +{ + struct input_dev *input_dev = context; + u32 data; + int ret; + + switch (value) { + case LG_NOTIFY_TABLET_MODE_OFF: + case LG_NOTIFY_TABLET_MODE_ON: + /* Already handled by intel-hid */ + return; + case LG_NOTIFY_HOTKEY: + case LG_NOTIFY_THERMAL: + case LG_NOTIFY_MISC: + ret = lg_laptop_get_event_data(handle, value, &data); + if (ret < 0) { + dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); + return; + } + + dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); + + lg_laptop_handle_input_event(input_dev, value, data); + return; + default: + dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); + } +}; + +static void lg_laptop_remove_notify_handler(void *context) +{ + acpi_handle handle = context; + + acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); +} + +static void lg_laptop_reenable_wmi_events(void *context) +{ + acpi_handle handle = context; + unsigned long long dummy; + + lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); +} + +static int lg_laptop_input_init(struct device *dev, acpi_handle handle) +{ + struct input_dev *input_dev; + unsigned long long result; + acpi_status status; + int ret; + + if (!acpi_has_method(handle, "_WED")) + return 0; + + input_dev = devm_input_allocate_device(dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "LG WMI hotkeys"; + input_dev->phys = "wmi/input0"; + input_dev->id.bustype = BUS_HOST; + ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); + if (ret < 0) + return ret; + + ret = input_register_device(input_dev); + if (ret < 0) + return ret; + + status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, + input_dev); + if (ACPI_FAILURE(status)) + return -EIO; + + ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); + if (ret < 0) + return ret; + + if (acpi_has_method(handle, "OREP")) { + ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); + if (ret < 0) + return ret; + if (result) + return -EIO; + + ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); + if (ret < 0) + return ret; + } + + return 0; +} + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, size_t size, u64 value) { @@ -865,10 +961,6 @@ static int acpi_probe(struct platform_device *pdev) if (year >= 2019) battery_limit_use_wmbb = 1; - ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); - if (ret) - goto out_platform_device; - /* LEDs are optional */ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); if (ret < 0) @@ -878,7 +970,14 @@ static int acpi_probe(struct platform_device *pdev) devm_led_classdev_register(&pdev->dev, &tpad_led); - wmi_input_setup(); + ret = lg_laptop_input_init(&pdev->dev, device->handle); + if (ret < 0) + goto out_platform_device; + + ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); + if (ret) + goto out_platform_device; + battery_hook_register(&battery_hook); return 0; @@ -895,7 +994,6 @@ static void acpi_remove(struct platform_device *pdev) sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); battery_hook_unregister(&battery_hook); - wmi_input_destroy(); platform_device_unregister(pf_device); pf_device = NULL; platform_driver_unregister(&pf_driver); From e114aeb30ee583790792b12e485405f211351fb5 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Jul 2026 21:55:51 +0200 Subject: [PATCH 048/152] platform/x86: lg-laptop: Add support for additional events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer devices support a number of additional events: - 0x0: Placeholder event - 0x78: Mute audio - 0xf070002: Open settings - 0x30010000/0x30010001: Hotkey combination pressed/released - 0x40020000: Disable camera - 0x40020001: Mute microphone - 0x40030001: Fn-lock Map those events onto appropriate key codes. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260708195553.7762-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lg-laptop.c | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 9ba1e872b6fe..9350418c50af 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -98,13 +98,32 @@ static struct led_classdev kbd_backlight; static enum led_brightness get_kbd_backlight_level(struct device *dev); static const struct key_entry wmi_keymap[] = { - {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ - {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ - {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ - {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing - * this key both sends an event and - * changes backlight level. - */ + /* Placeholder value send by multiple hotkeys handled by ACPI */ + { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, + /* LG control panel */ + { KE_KEY, 0x70, { KEY_F15 }}, + /* Touchpad toggle */ + { KE_KEY, 0x74, { KEY_F21 }}, + /* Mute Audio, already handled by ACPI */ + { KE_IGNORE, 0x78, { KEY_MUTE }}, + /* Read mode */ + { KE_KEY, 0xf020000, { KEY_F14 }}, + /* Open settings */ + { KE_KEY, 0xf070002, { KEY_CONFIG }}, + /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ + { KE_KEY, 0x10000000, { KEY_F16 }}, + /* Hotkey combination pressed */ + { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, + /* Hotkey combination released */ + { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, + /* Change fan mode */ + { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, + /* Disable camera */ + { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, + /* Mute microphone */ + { KE_KEY, 0x40020001, { KEY_MICMUTE }}, + /* Fn-Lock */ + { KE_KEY, 0x40030001, { KEY_FN_ESC }}, {KE_END, 0} }; From 9f57244922175306f1d139e4936d5daec792373b Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Jul 2026 21:55:52 +0200 Subject: [PATCH 049/152] platform/x86: lg-laptop: Improve WMAB control method support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WMAB ACPI control method can return two kinds of results: 1. A ACPI integer containing the result. 2. A ACPI buffer containing the result and an error code. Devices implement one of those mechanisms, but not both. Extend lg_wmab() to abstract away the differences between both mechanisms to fix keyboard backlight support on newer devices. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260708195553.7762-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lg-laptop.c | 267 +++++++++++++++++-------------- 1 file changed, 149 insertions(+), 118 deletions(-) diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 9350418c50af..164012c1c409 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include @@ -90,6 +93,11 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define PLATFORM_NAME "lg-laptop" +struct lg_wmab_buffer_result { + __le32 value; + __le32 status; +} __packed; + static struct platform_device *pf_device; static int battery_limit_use_wmbb; @@ -197,30 +205,97 @@ static int ggov(u32 arg0) return res; } -static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) +static int lg_wmab_get(struct device *dev, u32 method, u32 *result) { - union acpi_object args[3]; - acpi_status status; - struct acpi_object_list arg; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object in[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = method, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = WM_GET, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = 0, + }, + }, + }; + struct lg_wmab_buffer_result *buffer_result; + struct acpi_object_list input = { + .count = ARRAY_SIZE(in), + .pointer = in, + }; + acpi_status status; - args[0].type = ACPI_TYPE_INTEGER; - args[0].integer.value = method; - args[1].type = ACPI_TYPE_INTEGER; - args[1].integer.value = arg1; - args[2].type = ACPI_TYPE_INTEGER; - args[2].integer.value = arg2; + status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); + if (ACPI_FAILURE(status)) + return -EIO; - arg.count = 3; - arg.pointer = args; + union acpi_object *obj __free(kfree) = buffer.pointer; - status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); - if (ACPI_FAILURE(status)) { - dev_err(dev, "WMAB: call failed.\n"); - return NULL; + if (!obj) + return -ENODATA; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *result = obj->integer.value; + return 0; + case ACPI_TYPE_BUFFER: + if (obj->buffer.length != sizeof(*buffer_result)) + return -EPROTO; + + buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; + if (get_unaligned_le32(&buffer_result->status)) + return -EIO; + + *result = get_unaligned_le32(&buffer_result->value); + return 0; + default: + return -EPROTO; } +} - return buffer.pointer; +static int lg_wmab_set(struct device *dev, u32 method, u32 arg) +{ + union acpi_object in[] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = method, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = WM_SET, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = arg, + }, + }, + }; + struct acpi_object_list input = { + .count = ARRAY_SIZE(in), + .pointer = in, + }; + acpi_status status; + + status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; } static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) @@ -259,7 +334,6 @@ static ssize_t fan_mode_store(struct device *dev, const char *buffer, size_t count) { unsigned long value; - union acpi_object *r; int ret; ret = kstrtoul(buffer, 10, &value); @@ -268,10 +342,10 @@ static ssize_t fan_mode_store(struct device *dev, if (value >= 3) return -EINVAL; - r = lg_wmab(dev, WM_FAN_MODE, WM_SET, - FIELD_PREP(FAN_MODE_LOWER, value) | - FIELD_PREP(FAN_MODE_UPPER, value)); - kfree(r); + ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | + FIELD_PREP(FAN_MODE_UPPER, value)); + if (ret < 0) + return ret; return count; } @@ -279,22 +353,14 @@ static ssize_t fan_mode_store(struct device *dev, static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, char *buffer) { - unsigned int mode; - union acpi_object *r; + u32 mode; + int ret; - r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); - if (!r) - return -EIO; + ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); + if (ret < 0) + return ret; - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } - - mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); - kfree(r); - - return sysfs_emit(buffer, "%d\n", mode); + return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); } static ssize_t usb_charge_store(struct device *dev, @@ -344,41 +410,30 @@ static ssize_t reader_mode_store(struct device *dev, const char *buffer, size_t count) { bool value; - union acpi_object *r; int ret; ret = kstrtobool(buffer, &value); if (ret) return ret; - r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); - if (!r) - return -EIO; + ret = lg_wmab_set(dev, WM_READER_MODE, value); + if (ret < 0) + return ret; - kfree(r); return count; } static ssize_t reader_mode_show(struct device *dev, struct device_attribute *attr, char *buffer) { - unsigned int status; - union acpi_object *r; + u32 status; + int ret; - r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); - if (!r) - return -EIO; + ret = lg_wmab_get(dev, WM_READER_MODE, &status); + if (ret < 0) + return ret; - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } - - status = !!r->integer.value; - - kfree(r); - - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buffer, "%d\n", !!status); } static ssize_t fn_lock_store(struct device *dev, @@ -386,40 +441,30 @@ static ssize_t fn_lock_store(struct device *dev, const char *buffer, size_t count) { bool value; - union acpi_object *r; int ret; ret = kstrtobool(buffer, &value); if (ret) return ret; - r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); - if (!r) - return -EIO; + ret = lg_wmab_set(dev, WM_FN_LOCK, value); + if (ret < 0) + return ret; - kfree(r); return count; } static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buffer) { - unsigned int status; - union acpi_object *r; + u32 status; + int ret; - r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); - if (!r) - return -EIO; + ret = lg_wmab_get(dev, WM_FN_LOCK, &status); + if (ret < 0) + return ret; - if (r->type != ACPI_TYPE_BUFFER) { - kfree(r); - return -EIO; - } - - status = !!r->buffer.pointer[0]; - kfree(r); - - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buffer, "%d\n", !!status); } static ssize_t charge_control_end_threshold_store(struct device *dev, @@ -436,14 +481,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, if (value == 100 || value == 80) { union acpi_object *r; - if (battery_limit_use_wmbb) + if (battery_limit_use_wmbb) { r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); - else - r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); - if (!r) - return -EIO; + if (!r) + return -EIO; + + kfree(r); + } else { + ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); + if (ret < 0) + return ret; + } - kfree(r); return count; } @@ -454,8 +503,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { - unsigned int status; union acpi_object *r; + u32 status; + int ret; if (battery_limit_use_wmbb) { r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); @@ -468,19 +518,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, } status = r->buffer.pointer[0x10]; + kfree(r); } else { - r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); - if (!r) - return -EIO; - - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } - - status = r->integer.value; + ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); + if (ret < 0) + return ret; } - kfree(r); + if (status != 80 && status != 100) status = 0; @@ -546,10 +590,7 @@ static const struct attribute_group dev_attribute_group = { static void tpad_led_set(struct led_classdev *cdev, enum led_brightness brightness) { - union acpi_object *r; - - r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); - kfree(r); + lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); } static enum led_brightness tpad_led_get(struct led_classdev *cdev) @@ -563,46 +604,36 @@ static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) { u32 val; - union acpi_object *r; val = 0x22; if (brightness <= LED_OFF) val = 0; if (brightness >= LED_FULL) val = 0x24; - r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); - kfree(r); + + lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, val); } static enum led_brightness get_kbd_backlight_level(struct device *dev) { - union acpi_object *r; - int val; + u32 value; + int ret; - r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); - - if (!r) + ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); + if (ret < 0) return LED_OFF; - if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { - kfree(r); + if ((value & 0xFF00) != 0x0500) return LED_OFF; - } - switch (r->buffer.pointer[0] & 0x27) { + switch (value & 0x27) { case 0x24: - val = LED_FULL; - break; + return LED_FULL; case 0x22: - val = LED_HALF; - break; + return LED_HALF; default: - val = LED_OFF; + return LED_OFF; } - - kfree(r); - - return val; } static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) From 543efc52befae5e8ba9af85f26fd257b3c4fa459 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 8 Jul 2026 21:55:53 +0200 Subject: [PATCH 050/152] platform/x86: lg-laptop: Fix keyboard backlight support on LG Gram 16T90SP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the LG Gram 16T90SP, the ACPI firmware will only accept the values 0x80, 0xA2 and 0xA4 when setting the keyboard backlight. After a bit of research using ACPI tables from older models it seems that bit 8 is supposed to be always written as 1. Fix the keyboard backlight support on this model by always setting bit 8 inside the argument passed to the WMAB ACPI control method. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260708195553.7762-6-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lg-laptop.c | 47 ++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 164012c1c409..36ad1e269443 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -88,6 +88,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define WMBB_USB_CHARGE 0x10B #define WMBB_BATT_LIMIT 0x10C +#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) +#define KBD_LED_BRIGHTNESS_OFF 0x0 +#define KBD_LED_BRIGHTNESS_HALF 0x2 +#define KBD_LED_BRIGHTNESS_FULL 0x4 +#define KBD_LED_MODE_MASK GENMASK(6, 5) +#define KBD_LED_MODE_OFF 0x0 +#define KBD_LED_MODE_ON 0x1 +#define KBD_LED_STATUS BIT(7) +#define KBD_LED_MAGIC_MASK GENMASK(15, 8) +/* Exact purpose is unknown, maybe some sort of brightness limit? */ +#define KBD_LED_MAGIC 0x05 + #define FAN_MODE_LOWER GENMASK(1, 0) #define FAN_MODE_UPPER GENMASK(5, 4) @@ -603,15 +615,25 @@ static LED_DEVICE(tpad_led, 1, 0); static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) { - u32 val; + /* Must always be written */ + u32 value = KBD_LED_STATUS; + u32 mode, bright; - val = 0x22; - if (brightness <= LED_OFF) - val = 0; - if (brightness >= LED_FULL) - val = 0x24; + if (brightness <= LED_OFF) { + mode = KBD_LED_MODE_OFF; + bright = KBD_LED_BRIGHTNESS_OFF; + } else { + mode = KBD_LED_MODE_ON; + if (brightness >= LED_FULL) + bright = KBD_LED_BRIGHTNESS_FULL; + else + bright = KBD_LED_BRIGHTNESS_HALF; + } - lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, val); + value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); + value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); + + lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); } static enum led_brightness get_kbd_backlight_level(struct device *dev) @@ -623,13 +645,16 @@ static enum led_brightness get_kbd_backlight_level(struct device *dev) if (ret < 0) return LED_OFF; - if ((value & 0xFF00) != 0x0500) + if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) return LED_OFF; - switch (value & 0x27) { - case 0x24: + if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) + return LED_OFF; + + switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { + case KBD_LED_BRIGHTNESS_FULL: return LED_FULL; - case 0x22: + case KBD_LED_BRIGHTNESS_HALF: return LED_HALF; default: return LED_OFF; From 76708233b4acd6255fc54d5e1b15e779e91132e7 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 7 Jul 2026 23:36:11 +0200 Subject: [PATCH 051/152] platform/x86: asus-armoury: fix Use-After-Free and memory leak in driver init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In init_rog_tunables(), if dc_limits are defined and allocating dc_rog_tunables fails, the already allocated ac_rog_tunables gets freed but the pointer stored in asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] is not cleared. Since init_rog_tunables() returns void, the driver initialization continues, which can lead to a Use-After-Free (UAF) when asus_fw_attr_add() accesses the freed AC tunables pointer. Additionally, if init_rog_tunables() succeeds but asus_fw_attr_add() fails, the allocated tunables are not freed, resulting in a memory leak. Fix these issues by making init_rog_tunables() return an error code and propagating it in asus_fw_init(). Defer setting the global pointers in asus_armoury.rog_tunables until both tunables have been successfully allocated. If asus_fw_attr_add() fails, release the allocated resources using a standard goto rollback block in asus_fw_init(). Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260707213741.6515-2-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index f2a880eb0cdf..fa7d0485c712 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -1007,7 +1007,7 @@ static int asus_fw_attr_add(void) /* Init / exit ****************************************************************/ /* Set up the min/max and defaults for ROG tunables */ -static void init_rog_tunables(void) +static int init_rog_tunables(void) { const struct power_limits *ac_limits, *dc_limits; struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; @@ -1018,14 +1018,14 @@ static void init_rog_tunables(void) dmi_id = dmi_first_match(power_limits); if (!dmi_id) { pr_warn("No matching power limits found for this system\n"); - return; + return 0; } /* Get the power data for this system */ power_data = dmi_id->driver_data; if (!power_data) { pr_info("No power data available for this system\n"); - return; + return 0; } asus_armoury.requires_fan_curve = power_data->requires_fan_curve; @@ -1033,11 +1033,10 @@ static void init_rog_tunables(void) /* Initialize AC power tunables */ ac_limits = power_data->ac_data; if (ac_limits) { - ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); + ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); if (!ac_rog_tunables) - goto err_nomem; + return -ENOMEM; - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; ac_rog_tunables->power_limits = ac_limits; /* Set initial AC values */ @@ -1080,13 +1079,12 @@ static void init_rog_tunables(void) /* Initialize DC power tunables */ dc_limits = power_data->dc_data; if (dc_limits) { - dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); + dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); if (!dc_rog_tunables) { kfree(ac_rog_tunables); - goto err_nomem; + return -ENOMEM; } - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; dc_rog_tunables->power_limits = dc_limits; /* Set initial DC values */ @@ -1126,15 +1124,16 @@ static void init_rog_tunables(void) pr_debug("No DC PPT limits defined\n"); } - return; + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; -err_nomem: - pr_err("Failed to allocate memory for tunables\n"); + return 0; } static int __init asus_fw_init(void) { char *wmi_uid; + int err; wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); if (!wmi_uid) @@ -1147,10 +1146,21 @@ static int __init asus_fw_init(void) if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) return -ENODEV; - init_rog_tunables(); + err = init_rog_tunables(); + if (err) + return err; /* Must always be last step to ensure data is available */ - return asus_fw_attr_add(); + err = asus_fw_attr_add(); + if (err) + goto err_free_tunables; + + return 0; + +err_free_tunables: + kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); + kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); + return err; } static void __exit asus_fw_exit(void) From 7952692dce653d33e94d5482492278f822da7d19 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Tue, 7 Jul 2026 23:36:12 +0200 Subject: [PATCH 052/152] platform/x86: asus-armoury: use cleanup.h to manage tunables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert init_rog_tunables() to use the cleanup infrastructure to automatically free ac_rog_tunables if dc_rog_tunables allocation fails. By declaring local pointers with the __free(kfree) attribute, the manual kfree() in the error path can be removed. Upon successful initialization, the ownership is transferred to the global struct using no_free_ptr(). Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260707213741.6515-3-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index fa7d0485c712..93d9665717af 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1010,7 +1011,8 @@ static int asus_fw_attr_add(void) static int init_rog_tunables(void) { const struct power_limits *ac_limits, *dc_limits; - struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; + struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; + struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; const struct power_data *power_data; const struct dmi_system_id *dmi_id; @@ -1080,10 +1082,8 @@ static int init_rog_tunables(void) dc_limits = power_data->dc_data; if (dc_limits) { dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); - if (!dc_rog_tunables) { - kfree(ac_rog_tunables); + if (!dc_rog_tunables) return -ENOMEM; - } dc_rog_tunables->power_limits = dc_limits; @@ -1124,8 +1124,8 @@ static int init_rog_tunables(void) pr_debug("No DC PPT limits defined\n"); } - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; - asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); + asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); return 0; } From b461a88f2ee8c4ce735699159b6f423cf7648fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCr=C5=9Fat=20Abayl=C4=B1?= Date: Tue, 7 Jul 2026 23:37:40 +0300 Subject: [PATCH 053/152] platform/x86: hp-wmi: Add dual-channel PWM fan control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, manual fan control on supported HP models uses a single PWM value for both CPU and GPU fans, linking their speeds via a hardcoded gpu_delta offset. This prevents userspace tools from managing the thermal profiles of the CPU and GPU independently. Refactor the hwmon implementation to support independent dual-channel PWM control: - Split the single 'pwm' state into 'cpu_pwm' and 'gpu_pwm'. - Expose a second PWM channel ('pwm2') to userspace via hwmon_channel_info. - Remove the gpu_delta mechanism entirely. The 'pwm1_enable' mode remains shared, as the underlying hardware does not support per-fan modes. When switching to manual mode, both fans are smoothly initialized to their current RPMs. Additionally, ensure that the HP_FAN_SPEED_AUTOMATIC flag is isolated from rpm_to_pwm mathematical interpolations during mode resets to prevent unintended fan states. Tested on: HP Victus 16-s0xxx Tested-by: Radhey Kalra Signed-off-by: Kürşat Abaylı Link: https://patch.msgid.link/20260707203740.55369-1-hello@kursatabayli.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 60 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 34c9b941bdd8..5353d997d272 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -549,9 +549,9 @@ struct hp_wmi_hwmon_priv { struct mutex lock; /* protects mode, pwm */ u8 min_rpm; u8 max_rpm; - int gpu_delta; u8 mode; - u8 pwm; + u8 cpu_pwm; + u8 gpu_pwm; struct delayed_work keep_alive_dwork; }; @@ -878,24 +878,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } -static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) +static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) { u8 fan_speed[2]; - int gpu_speed, ret; + int ret; - fan_speed[CPU_FAN] = speed; - fan_speed[GPU_FAN] = speed; + if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) + fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; + else + fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); - /* - * GPU fan speed is always a little higher than CPU fan speed, we fetch - * this delta value from the fan table during hwmon init. - * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to - * automatic mode. - */ - if (speed != HP_FAN_SPEED_AUTOMATIC) { - gpu_speed = speed + priv->gpu_delta; - fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); - } + if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) + fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; + else + fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) @@ -912,7 +908,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) { - return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); + priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; + priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; + return hp_wmi_fan_speed_set(priv); } static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) @@ -2503,7 +2501,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) case PWM_MODE_MANUAL: if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); + ret = hp_wmi_fan_speed_set(priv); if (ret < 0) return ret; mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, @@ -2603,13 +2601,14 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct hp_wmi_hwmon_priv *priv; - int rpm; + int cpu_rpm, gpu_rpm; priv = dev_get_drvdata(dev); guard(mutex)(&priv->lock); switch (type) { case hwmon_pwm: if (attr == hwmon_pwm_input) { + int rpm; if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* PWM input is invalid when not in manual mode */ @@ -2619,7 +2618,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, /* ensure PWM input is within valid fan speeds */ rpm = pwm_to_rpm(val, priv); rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); - priv->pwm = rpm_to_pwm(rpm, priv); + if (channel == CPU_FAN) + priv->cpu_pwm = rpm_to_pwm(rpm, priv); + else if (channel == GPU_FAN) + priv->gpu_pwm = rpm_to_pwm(rpm, priv); return hp_wmi_apply_fan_settings(priv); } switch (val) { @@ -2633,10 +2635,14 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, * When switching to manual mode, set fan speed to * current RPM values to ensure a smooth transition. */ - rpm = hp_wmi_get_active_fan_speed(channel); - if (rpm < 0) - return rpm; - priv->pwm = rpm_to_pwm(rpm / 100, priv); + cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); + if (cpu_rpm < 0) + return cpu_rpm; + gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); + if (gpu_rpm < 0) + return gpu_rpm; + priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); + priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); priv->mode = PWM_MODE_MANUAL; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_AUTO: @@ -2652,7 +2658,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), - HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), NULL }; @@ -2693,7 +2699,7 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) struct victus_s_fan_table *fan_table; u8 min_rpm, max_rpm; u8 cpu_rpm, gpu_rpm, noise_db; - int gpu_delta, i, num_entries, ret; + int i, num_entries, ret; size_t header_size, entry_size; /* Default behaviour on hwmon init is automatic mode */ @@ -2739,10 +2745,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) if (min_rpm == U8_MAX || max_rpm == 0) return -EINVAL; - gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; priv->min_rpm = min_rpm; priv->max_rpm = max_rpm; - priv->gpu_delta = gpu_delta; return 0; } From 57de5fb319810561e8fa50a7c952977aebfcebc1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Jul 2026 13:12:35 +0200 Subject: [PATCH 054/152] platform/surface: surfacepro3_button: Stop setting acpi_device_name() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() to initialize an input class device name initialization and print a message on probe success, but the input class device name can be set directly to the "Surface Pro 3/4 Buttons" string literal and used for printing the message. Make the driver do so, stop setting acpi_device_name() in it and drop the symbol specifically defined for this purpose. No intentional functional impact. This will facilitate the removal of device_name from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Chen Yu Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/12927239.O9o76ZdvQC@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surfacepro3_button.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 388a3e1a488c..b38aa010053e 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -20,7 +20,6 @@ #define SURFACE_PRO3_BUTTON_HID "MSHW0028" #define SURFACE_PRO4_BUTTON_HID "MSHW0040" #define SURFACE_BUTTON_OBJ_NAME "VGBI" -#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision @@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev) goto err_free_button; } - strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); snprintf(button->phys, sizeof(button->phys), "%s/buttons", acpi_device_hid(device)); - input->name = acpi_device_name(device); + input->name = "Surface Pro 3/4 Buttons"; input->phys = button->phys; input->id.bustype = BUS_HOST; input->dev.parent = &pdev->dev; @@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev) goto err_free_button; } - dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device), - acpi_device_bid(device)); + dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); + return 0; err_free_input: From 1c8ea6e6c8a156f4771868d5a8fe167ca84b568d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:38:15 +0200 Subject: [PATCH 055/152] sonypi: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() and acpi_device_class() which are never read afterward, so make it stop doing that. No intentional functional impact. This will facilitate the removal of device_name and device_class from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3447860.aeNJFYEL58@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/char/sonypi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c index 9309cfb935be..9a88d287fa09 100644 --- a/drivers/char/sonypi.c +++ b/drivers/char/sonypi.c @@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev) return -ENODEV; sonypi_acpi_device = device; - strscpy(acpi_device_name(device), "Sony laptop hotkeys"); - strscpy(acpi_device_class(device), "sony/hotkey"); return 0; } From 14deb40886e3f98b335eff9da598c3ead98b9225 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:26:23 +0200 Subject: [PATCH 056/152] x86/platform/olpc: xo15: Stop setting acpi_device_name/class() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver sets acpi_device_name() and acpi_device_class() which are never read afterward, so make it stop doing that and drop the symbols defined specifically for this purpose. No intentional functional impact. This will facilitate the removal of device_name and device_class from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2866696.mvXUDI8C0e@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/platform/olpc/olpc-xo15-sci.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c index e486109e88c8..75ed6ceb9df3 100644 --- a/arch/x86/platform/olpc/olpc-xo15-sci.c +++ b/arch/x86/platform/olpc/olpc-xo15-sci.c @@ -18,8 +18,6 @@ #define DRV_NAME "olpc-xo15-sci" #define PFX DRV_NAME ": " -#define XO15_SCI_CLASS DRV_NAME -#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" static unsigned long xo15_sci_gpe; static bool lid_wake_on_close; @@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev) if (!device) return -ENODEV; - strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); - strscpy(acpi_device_class(device), XO15_SCI_CLASS); - /* Get GPE bit assignment (EC events). */ status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); if (ACPI_FAILURE(status)) From 62b57396c26a1ce54963709928ea0d01fa522eea Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Wed, 24 Jun 2026 09:49:09 +0800 Subject: [PATCH 057/152] platform/x86: ishtp_eclite: Fix ACPI device reference leak in probe error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ecl_ishtp_cl_probe() acquires a reference to an ACPI device via acpi_find_eclite_device() but fails to release it in the error path when acpi_opregion_init() fails. This results in a reference count leak, preventing proper cleanup of the ACPI device. Calling path: acpi_find_eclite_device() -> acpi_dev_get_first_match_dev() -> acpi_dev_get_next_match_dev() -> bus_find_device() -> get_device(). Found by code review. Signed-off-by: Ma Ke Acked-by: Srinivas Pandruvada Cc: stable@vger.kernel.org Fixes: 7b6bf51de974 ("platform/x86: Add Intel ishtp eclite driver") Link: https://patch.msgid.link/20260624014910.1226446-1-make_ruc2021@163.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/ishtp_eclite.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/ishtp_eclite.c b/drivers/platform/x86/intel/ishtp_eclite.c index 93ac8b2dbf38..bca7e217878b 100644 --- a/drivers/platform/x86/intel/ishtp_eclite.c +++ b/drivers/platform/x86/intel/ishtp_eclite.c @@ -600,13 +600,16 @@ static int ecl_ishtp_cl_probe(struct ishtp_cl_device *cl_device) rv = acpi_opregion_init(opr_dev); if (rv) { dev_err(cl_data_to_dev(opr_dev), "ACPI opregion init failed\n"); - goto err_exit; + goto err_put; } /* Reprobe devices depending on ECLite - battery, fan, etc. */ acpi_dev_clear_dependencies(opr_dev->adev); return 0; + +err_put: + acpi_dev_put(opr_dev->adev); err_exit: ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING); ishtp_cl_disconnect(ecl_ishtp_cl); From e0ddfd77c0c320b7d12b6c9169303b140b798775 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Thu, 9 Jul 2026 21:58:56 +0500 Subject: [PATCH 058/152] platform/x86: hp-bioscfg: pass validated element count to package parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-type package parsers are handed the wrong element count. hp_init_bios_package_attribute() validates obj->package.count and then calls one of the five hp_populate_*_package_data() wrappers (string, integer, enumeration, ordered list, password). Each wrapper forwards a count to its hp_populate_*_elements_from_package() parser, but instead of forwarding the validated obj->package.count it derives the count from elements[0]. elements[0] is the NAME field and is always an ACPI_TYPE_STRING, so reading ->package.count from it in fact reads ->string.length through the union acpi_object. The parsers thus bound themselves against the length of the name string rather than against the real number of elements in the package. This is safe today because hp_init_bios_package_attribute() refuses any package that has fewer than the type's element count, so a parser only ever runs on a full package and never reads past it regardless of the bogus bound. An upcoming change relaxes that check to accept shorter packages. Once a parser can receive fewer elements than its per-type count, a bound taken from the name length no longer reflects the array size, and the "elem < count" loop conditions and "elem + n >= count" sub-loop guards read past the end of elements[] - an out-of-bounds heap read. Forward the validated obj->package.count to every *_package_data() wrapper so the parsers bound themselves against the real package size. This does not change behaviour for the packages that enumerate correctly today and is a prerequisite for accepting shorter packages safely. Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260709165900.30615-2-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 5 +++++ drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 5 +++++ drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c | 4 +++- drivers/platform/x86/hp/hp-bioscfg/int-attributes.c | 4 +++- drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 6 ++++-- drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 6 ++++-- drivers/platform/x86/hp/hp-bioscfg/string-attributes.c | 4 +++- 7 files changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 27fd6cd21529..768330d291da 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -731,26 +731,31 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, switch (attr_type) { case HPWMI_STRING_TYPE: ret = hp_populate_string_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_INTEGER_TYPE: ret = hp_populate_integer_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_ENUMERATION_TYPE: ret = hp_populate_enumeration_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_ORDERED_LIST_TYPE: ret = hp_populate_ordered_list_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; case HPWMI_PASSWORD_TYPE: ret = hp_populate_password_package_data(elements, + obj->package.count, instance_id, attr_name_kobj); break; diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index f1eec0e4ba07..416d7e7aaaae 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -401,6 +401,7 @@ int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_string_data(void); void hp_exit_string_attributes(void); int hp_populate_string_package_data(union acpi_object *str_obj, + int str_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -411,6 +412,7 @@ int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_integer_data(void); void hp_exit_integer_attributes(void); int hp_populate_integer_package_data(union acpi_object *integer_obj, + int integer_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -421,6 +423,7 @@ int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int hp_alloc_enumeration_data(void); void hp_exit_enumeration_attributes(void); int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int enum_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -432,6 +435,7 @@ int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, int hp_alloc_ordered_list_data(void); void hp_exit_ordered_list_attributes(void); int hp_populate_ordered_list_package_data(union acpi_object *order_obj, + int order_obj_count, int instance_id, struct kobject *attr_name_kobj); @@ -440,6 +444,7 @@ int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id, struct kobject *attr_name_kobj); int hp_populate_password_package_data(union acpi_object *password_obj, + int password_obj_count, int instance_id, struct kobject *attr_name_kobj); int hp_alloc_password_data(void); diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index af4d1920d488..de156a9f88a1 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -300,10 +300,12 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum * Populate all properties of an instance under enumeration attribute * * @enum_obj: ACPI object with enumeration data + * @enum_obj_count: Number of elements in @enum_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_enumeration_package_data(union acpi_object *enum_obj, + int enum_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -312,7 +314,7 @@ int hp_populate_enumeration_package_data(union acpi_object *enum_obj, enum_data->attr_name_kobj = attr_name_kobj; hp_populate_enumeration_elements_from_package(enum_obj, - enum_obj->package.count, + enum_obj_count, instance_id); hp_update_attribute_permissions(enum_data->common.is_readonly, &enumeration_current_val); diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index d96e160953e3..f2fd966c9ca4 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -275,10 +275,12 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ * Populate all properties of an instance under integer attribute * * @integer_obj: ACPI object with integer data + * @integer_obj_count: Number of elements in @integer_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_integer_package_data(union acpi_object *integer_obj, + int integer_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -286,7 +288,7 @@ int hp_populate_integer_package_data(union acpi_object *integer_obj, integer_data->attr_name_kobj = attr_name_kobj; hp_populate_integer_elements_from_package(integer_obj, - integer_obj->package.count, + integer_obj_count, instance_id); hp_update_attribute_permissions(integer_data->common.is_readonly, &integer_current_val); 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 f09489a085c8..cc5bebe73a93 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -298,10 +298,12 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord * Populate all properties of an instance under ordered_list attribute * * @order_obj: ACPI object with ordered_list data + * @order_obj_count: Number of elements in @order_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ -int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id, +int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int order_obj_count, + int instance_id, struct kobject *attr_name_kobj) { struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id]; @@ -309,7 +311,7 @@ int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int inst ordered_list_data->attr_name_kobj = attr_name_kobj; hp_populate_ordered_list_elements_from_package(order_obj, - order_obj->package.count, + order_obj_count, instance_id); hp_update_attribute_permissions(ordered_list_data->common.is_readonly, &ordered_list_current_val); diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 4d79eb8056a5..ed5e2080f22b 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -385,10 +385,12 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor * Populate all properties for an instance under password attribute * * @password_obj: ACPI object with password data + * @password_obj_count: Number of elements in @password_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ -int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id, +int hp_populate_password_package_data(union acpi_object *password_obj, int password_obj_count, + int instance_id, struct kobject *attr_name_kobj) { struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; @@ -396,7 +398,7 @@ int hp_populate_password_package_data(union acpi_object *password_obj, int insta password_data->attr_name_kobj = attr_name_kobj; hp_populate_password_elements_from_package(password_obj, - password_obj->package.count, + password_obj_count, instance_id); hp_friendly_user_name_update(password_data->common.path, diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index fe5a9a3a4ef1..f98c32dacbc7 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -263,10 +263,12 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob * Populate all properties of an instance under string attribute * * @string_obj: ACPI object with string data + * @string_obj_count: Number of elements in @string_obj * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int hp_populate_string_package_data(union acpi_object *string_obj, + int string_obj_count, int instance_id, struct kobject *attr_name_kobj) { @@ -275,7 +277,7 @@ int hp_populate_string_package_data(union acpi_object *string_obj, string_data->attr_name_kobj = attr_name_kobj; hp_populate_string_elements_from_package(string_obj, - string_obj->package.count, + string_obj_count, instance_id); hp_update_attribute_permissions(string_data->common.is_readonly, From 1d143d78299d0eb4536698bf98c1815ec69f22a9 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Thu, 9 Jul 2026 21:58:57 +0500 Subject: [PATCH 059/152] platform/x86: hp-bioscfg: bound ordered-list parsing by the package count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_populate_ordered_list_elements_from_package() differs from the other per-type parsers: its main loop is bounded only by the fixed per-type count and never checks elem against the number of elements actually present in the package, for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) whereas the string, integer, enumeration and password parsers bound their main loop with "elem < count" as well. This is safe today because hp_init_bios_package_attribute() rejects any package with fewer than ORD_ELEM_CNT elements before the parser runs. An upcoming change, however, relaxes that check to accept shorter packages. Bound the loop by the validated element count as well, so it stops at whichever comes first, the per-type count or the real package size, for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT && elem < order_obj_count; elem++, eloc++) order_obj_count is the validated element count, now correctly forwarded from the caller. No functional change for packages that enumerate correctly today. Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260709165900.30615-3-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cc5bebe73a93..863e486474ad 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -145,7 +145,7 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord if (!order_obj) return -EINVAL; - for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) { + for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT && elem < order_obj_count; elem++, eloc++) { switch (order_obj[elem].type) { case ACPI_TYPE_STRING: From 40e10e6cc8f70c041431a1e30186807e28ec46e0 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Thu, 9 Jul 2026 21:58:58 +0500 Subject: [PATCH 060/152] platform/x86: hp-bioscfg: accept reduced ACPI packages from older HP BIOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_init_bios_package_attribute() hard-fails when a WMI ACPI package contains fewer elements than the type-specific expected count (e.g. 11 elements instead of 13 for INTEGER or ENUMERATION attributes). This causes the entire hp_bioscfg driver to skip attribute enumeration on older HP hardware whose BIOS returns shortened packages when optional fields like prerequisites or possible values are absent. Observed on HP EliteBook 840 G2 (BIOS M71 Ver. 01.31): hp_bioscfg: ACPI-package does not have enough elements: 11 < 13 The element layout has two tiers: - Elements 0-9 (SECURITY_LEVEL+1 = 10): common to all attribute types - Elements 10-N: type-specific (bounds, values, encodings, ...) The per-type populate functions (hp_populate_*_elements_from_package) already handle sparse packages correctly via their own elem < count loop guards and inner-loop bounds checks. The only unsafe case is when we lack even the common elements needed to register the attribute. Fix by introducing COMMON_ELEM_CNT to mark the hard minimum (10), and splitting the check into two tiers: - Fewer than COMMON_ELEM_CNT elements: hard fail, can't proceed. - Fewer than expected type-specific elements: warn, but let the populate function parse what is available. Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260709165900.30615-4-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 11 ++++++++--- drivers/platform/x86/hp/hp-bioscfg/bioscfg.h | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 768330d291da..78019644ec35 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -661,12 +661,17 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type, int ret = 0; /* Take action appropriate to each ACPI TYPE */ - if (obj->package.count < min_elements) { - pr_err("ACPI-package does not have enough elements: %d < %d\n", - obj->package.count, min_elements); + if (obj->package.count < COMMON_ELEM_CNT) { + pr_err("ACPI-package is missing common elements: %d < %d\n", + obj->package.count, COMMON_ELEM_CNT); goto pack_attr_exit; } + if (obj->package.count < min_elements) { + pr_warn("ACPI-package has fewer elements than expected: %d < %d, parsing available elements\n", + obj->package.count, min_elements); + } + elements = obj->package.elements; /* sanity checking */ diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index 416d7e7aaaae..ac57d6eab4c3 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -279,6 +279,9 @@ enum hp_wmi_data_elements { PSWD_ENCODINGS = 13, PSWD_IS_SET = 14, PSWD_ELEM_CNT = 15, + + /* Minimum elements shared by all attribute types (NAME..SECURITY_LEVEL) */ + COMMON_ELEM_CNT = SECURITY_LEVEL + 1, }; #define GET_INSTANCE_ID(type) \ From b0e2af3ec94e0431adb59d9f249ebbd3b7285158 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Thu, 9 Jul 2026 21:58:59 +0500 Subject: [PATCH 061/152] platform/x86: hp-bioscfg: warn on element type mismatch instead of failing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_populate_enumeration_elements_from_package() returns -EIO and aborts enumeration of the entire attribute when any single element has an unexpected ACPI type. This is observed on HP EliteBook 840 G2 when the BIOS returns malformed ACPI data following a failed WMI query: ACPI BIOS Error (bug): AE_AML_BUFFER_LIMIT, Index (0x000000032) is beyond end of object (length 0x32) ACPI Error: Aborting method \_SB.WMID.WQBE due to previous error Error expected type 2 for elem 13, but got type 1 instead hp_bioscfg: Returned error 0x3, "Invalid command value/Feature not supported" Aborting immediately discards the attribute entirely. Warn about the unexpected element type, free the temporary string, skip the offending element, and continue parsing the remaining package instead of failing the whole attribute. Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260709165900.30615-5-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index de156a9f88a1..21077d17113b 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -163,10 +163,11 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum /* Check that both expected and read object type match */ if (expected_enum_types[eloc] != enum_obj[elem].type) { - pr_err("Error expected type %d for elem %d, but got type %d instead\n", - expected_enum_types[eloc], elem, enum_obj[elem].type); + pr_warn("Unexpected element type at elem %d: expected %d, got %d, skipping\n", + elem, expected_enum_types[eloc], enum_obj[elem].type); kfree(str_value); - return -EIO; + str_value = NULL; + continue; } /* Assign appropriate element value to corresponding field */ From 4ed460ce13710f7f2cf22e0c9e66c7add13c7a53 Mon Sep 17 00:00:00 2001 From: "Yo-Jung Leo Lin (AMD)" Date: Thu, 9 Jul 2026 14:58:18 +0800 Subject: [PATCH 062/152] platform/x86/amd: Introduce Halo Box RGB LED driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Halo Box features an RGB LED light bar that can be controlled through WMI methods to display any color combination. The driver exposes the LED through the LED multicolor subsystem, allowing userspace to control RGB values via sysfs: /sys/class/leds/amd_halo:multicolor:status/multi_intensity /sys/class/leds/amd_halo:multicolor:status/brightness Hardware interface: - Three separate RGB channels (Red, Green, Blue) - All 3 channels are configured at once with a single WMI method call - Value range: 0-100 (matching hardware range directly) Co-developed-by: Mario Limonciello (AMD) Signed-off-by: Mario Limonciello (AMD) Reviewed-by: Shyam Sundar S K Reviewed-by: Armin Wolf Signed-off-by: Yo-Jung Leo Lin (AMD) Link: https://patch.msgid.link/20260709-halo-leds-v2-plus-v8-1-b34c64277193@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 7 + drivers/platform/x86/amd/Kconfig | 11 + drivers/platform/x86/amd/Makefile | 1 + drivers/platform/x86/amd/amd_halo_led.c | 315 ++++++++++++++++++++++++ 4 files changed, 334 insertions(+) create mode 100644 drivers/platform/x86/amd/amd_halo_led.c diff --git a/MAINTAINERS b/MAINTAINERS index 4a8b0fd665ce..1ef1ec374eab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1138,6 +1138,13 @@ F: drivers/char/hw_random/geode-rng.c F: drivers/crypto/geode* F: drivers/video/fbdev/geode/ +AMD HALO BOX RGB LED DRIVER +M: Mario Limonciello (AMD) +R: Yo-Jung Leo Lin (AMD) +L: platform-driver-x86@vger.kernel.org +S: Supported +F: drivers/platform/x86/amd/amd_halo_led.c + AMD HSMP DRIVER M: Naveen Krishna Chatradhi R: Carlos Bilbao diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig index b813f9265368..a1a74ef6c859 100644 --- a/drivers/platform/x86/amd/Kconfig +++ b/drivers/platform/x86/amd/Kconfig @@ -34,6 +34,17 @@ config AMD_WBRF This mechanism will only be activated on platforms that advertise a need for it. +config AMD_HALO_LED + tristate "AMD Halo Box RGB LED Driver" + depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR + help + This driver provides RGB LED control for AMD Halo Box devices + through the LED multicolor subsystem. The Halo Box light bar can + be controlled via sysfs to display any RGB color combination. + + To compile this driver as a module, choose M here: the module + will be called amd_halo_led. + config AMD_ISP_PLATFORM tristate "AMD ISP4 platform driver" depends on I2C && X86_64 && ACPI diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile index f6ff0c837f34..2f467dbbfc8a 100644 --- a/drivers/platform/x86/amd/Makefile +++ b/drivers/platform/x86/amd/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ obj-$(CONFIG_AMD_HSMP) += hsmp/ obj-$(CONFIG_AMD_PMF) += pmf/ obj-$(CONFIG_AMD_WBRF) += wbrf.o +obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o obj-$(CONFIG_AMD_HFI) += hfi/ diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c new file mode 100644 index 000000000000..b21d956c7d96 --- /dev/null +++ b/drivers/platform/x86/amd/amd_halo_led.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Halo Box RGB LED Driver + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * This driver provides RGB LED control for AMD Halo Box devices through + * the LED multicolor subsystem. The Halo Box light bar can be controlled + * via sysfs to display any RGB color combination. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" + +/* WMI method IDs from MOF */ +enum { + AMD_HALO_WMI_TURN_OFF = 0x04, + AMD_HALO_WMI_RGB = 0x07, +}; + +/* Arg0 of the AMD_HALO_WMI_RGB Method */ +enum { + AMD_HALO_RGB_CMD_GET = 0x0, + AMD_HALO_RGB_CMD_SET = 0x1, +}; + +/* Status codes from spec */ +#define AMD_HALO_STATUS_SUCCESS 0x0000 +#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD + +/* Brightness uses 0-100 range */ +#define AMD_HALO_MAX_HW_BRIGHTNESS 100 + +/** + * struct amd_halo_led_data - Driver private data + * @wdev: WMI device pointer + * @led_mc: LED multicolor class device + * @subled_info: RGB channel information + * @lock: Mutex to protect WMI calls + */ +struct amd_halo_led_data { + struct wmi_device *wdev; + struct led_classdev_mc led_mc; + struct mc_subled subled_info[3]; + struct mutex lock; +}; + +struct amd_halo_wmi_args { + __le32 arg0; + __le32 arg1; +}; + +struct amd_halo_wmi_args_rgb { + __le32 cmd; + __le32 red; + __le32 green; + __le32 blue; +}; + +struct amd_halo_wmi_output_rgb { + __le16 status; + u8 red; + u8 green; + u8 blue; +} __packed; + +static inline int wmi_status_to_err(u16 status) +{ + switch (status) { + case AMD_HALO_STATUS_SUCCESS: + return 0; + case AMD_HALO_STATUS_INVALID_PARAM: + return -EINVAL; + default: + return -EIO; + } +} + +static int __amd_halo_wmi_call(struct wmi_device *wdev, + u32 method_id, void *data, size_t length) +{ + struct wmi_buffer input = { + .length = length, + .data = data, + }; + struct wmi_buffer output = { }; + int ret; + + /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ + ret = wmidev_invoke_method(wdev, 0, method_id, + &input, &output, sizeof(__le16)); + if (ret) + return ret; + + __le16 *result_status __free(kfree) = output.data; + + return wmi_status_to_err(le16_to_cpu(*result_status)); +} + +/** + * amd_halo_wmi_turn_off - Turn off all LED channels + * @wdev: WMI device pointer + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_turn_off(struct wmi_device *wdev) +{ + struct amd_halo_wmi_args args = { }; + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_set_rgb - Set all RGB channels atomically + * @wdev: WMI device pointer + * @r: brightness for red channel (0 - 100) + * @g: brightness for green channel (0 - 100) + * @b: brightness for blue channel (0 - 100) + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), + .red = cpu_to_le32(r), + .green = cpu_to_le32(g), + .blue = cpu_to_le32(b), + }; + + if (r > AMD_HALO_MAX_HW_BRIGHTNESS || + g > AMD_HALO_MAX_HW_BRIGHTNESS || + b > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EINVAL; + } + + return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); +} + +/** + * amd_halo_wmi_get_rgb - Get RGB values + * @wdev: WMI device pointer + * @r: output buffer for red value + * @g: output buffer for green value + * @b: output buffer for blue value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) +{ + struct amd_halo_wmi_args_rgb args = { + .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), + }; + struct wmi_buffer input = { + .length = sizeof(args), + .data = &args, + }; + struct wmi_buffer output = { }; + int ret; + + ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, + &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); + if (ret) + return ret; + + struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; + + ret = wmi_status_to_err(le16_to_cpu(data->status)); + if (ret) + return ret; + + if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || + data->green > AMD_HALO_MAX_HW_BRIGHTNESS || + data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { + return -EPROTO; + } + + *r = data->red; + *g = data->green; + *b = data->blue; + + return 0; +} + +/** + * amd_halo_brightness_set - Set LED brightness + * @cdev: LED class device + * @brightness: Brightness value + * + * Return: 0 on success, negative error code on failure + */ +static int amd_halo_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct amd_halo_led_data *data = container_of(mc_cdev, + struct amd_halo_led_data, + led_mc); + u32 red_hw, green_hw, blue_hw; + int ret; + + guard(mutex)(&data->lock); + + led_mc_calc_color_components(mc_cdev, brightness); + + if (brightness == 0) + return amd_halo_wmi_turn_off(data->wdev); + + red_hw = mc_cdev->subled_info[0].brightness; + green_hw = mc_cdev->subled_info[1].brightness; + blue_hw = mc_cdev->subled_info[2].brightness; + + ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); + if (ret) + goto out; + + return 0; + +out: + /* + * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. + * Attempt to turn the LED off completely as clean-up. + */ + if (amd_halo_wmi_turn_off(data->wdev)) + dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); + + return ret; +} + +static int amd_halo_probe(struct wmi_device *wdev, const void *context) +{ + struct led_init_data led_init_data = { + .devicename = "amd_halo", + .default_label = "multicolor:" LED_FUNCTION_STATUS, + .devname_mandatory = true, + }; + struct amd_halo_led_data *data; + u8 r, g, b; + int ret; + + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = devm_mutex_init(&wdev->dev, &data->lock); + if (ret) + return ret; + + data->wdev = wdev; + dev_set_drvdata(&wdev->dev, data); + + data->subled_info[0].color_index = LED_COLOR_ID_RED; + data->subled_info[1].color_index = LED_COLOR_ID_GREEN; + data->subled_info[2].color_index = LED_COLOR_ID_BLUE; + + data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; + data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; + data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); + data->led_mc.subled_info = data->subled_info; + + ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); + if (ret) + return ret; + + data->subled_info[0].intensity = r; + data->subled_info[1].intensity = g; + data->subled_info[2].intensity = b; + + ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, + &led_init_data); + if (ret) + return dev_err_probe(&wdev->dev, ret, + "Failed to register multicolor LED\n"); + return 0; +} + +static const struct wmi_device_id amd_halo_id_table[] = { + { .guid_string = AMD_HALO_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); + +static struct wmi_driver amd_halo_driver = { + .driver = { + .name = "amd_halo_led", + }, + .id_table = amd_halo_id_table, + .probe = amd_halo_probe, + .no_singleton = true, +}; + +module_wmi_driver(amd_halo_driver); + +MODULE_AUTHOR("Mario Limonciello (AMD) "); +MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); +MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); +MODULE_LICENSE("GPL"); From 4aefd66ef7822cf7d3f53146dcee0b71021ed2b7 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 10 Jul 2026 17:43:55 +0800 Subject: [PATCH 063/152] platform/x86: asus-wireless: Fail probe when there is no ACPI match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that does not match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device ACPI companion object need to verify its presence. asus_wireless_probe() returns success when acpi_match_acpi_device() finds no match, leaving behind an input device that never reports anything because the notify handler is not installed. Worse, when the driver is force-bound to a device without an ACPI companion, probe still succeeds and stores a NULL companion pointer, which asus_wireless_remove() later passes to acpi_dev_remove_notify_handler(), leading to a NULL pointer dereference on unbind. Return -ENODEV when the device does not match the ID table. This also covers the missing-companion case, because acpi_match_acpi_device() rejects a NULL device. Perform the check before allocating any driver state, instead of after the input device has already been registered. Fixes: f7e648027d7e ("platform/x86: asus-wireless: Convert ACPI driver to a platform one") Suggested-by: Rafael J. Wysocki Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260710094355.186143-1-lilinmao@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wireless.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c index 2b494bf3cba8..aab45f0442c5 100644 --- a/drivers/platform/x86/asus-wireless.c +++ b/drivers/platform/x86/asus-wireless.c @@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev) const struct acpi_device_id *id; int err; + id = acpi_match_acpi_device(device_ids, adev); + if (!id) + return -ENODEV; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); data->adev = adev; + data->hswc_params = (const struct hswc_params *)id->driver_data; data->idev = devm_input_allocate_device(&pdev->dev); if (!data->idev) @@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev) if (err) return err; - id = acpi_match_acpi_device(device_ids, adev); - if (!id) - return 0; - - data->hswc_params = (const struct hswc_params *)id->driver_data; - data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); if (!data->wq) return -ENOMEM; From 98432eec31803ae8707a9fd593ba64cd2648aa74 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:38 +0530 Subject: [PATCH 064/152] platform/x86/amd/pmf: Add util layer and userspace character device interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a util layer to AMD PMF that exposes a minimal userspace interface via a character device for metrics monitoring and feature discovery. This creates /dev/amdpmf_interface with basic ioctl support to retrieve PMF metrics such as: * Power source and power slider position * Platform type, lid state, and user presence * Skin temperature and ambient light * BIOS input parameters (1-10) * Graphics workload metrics * CPU C-state residency (average and maximum) * Socket power consumption * Auto Mode: Automatic power profile switching based on system activity * Static Power Slider: User-selectable power profiles * Policy Builder (Smart PC): Action based policy management * Dynamic Power Slider AC: Adaptive power profiles when on AC power * Dynamic Power Slider DC: Adaptive power profiles when on battery The interface enables smoother integration with userspace tools such as AMD SystemDeck [1], which is widely used for monitoring and controlling power and thermal behavior on AMD platforms. These tools help designers keep major components within thermal limits to ensure proper operation and enhance overall system stability and reliability. The feature is gated behind the CONFIG_AMD_PMF_UTIL_SUPPORT Kconfig option, allowing it to be disabled if not needed. The implementation uses existing PMF infrastructure to populate data from the TA (Trusted Application) shared memory buffer. Link: https://docs.amd.com/v/u/en-US/68773_0.50 [1] Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-2-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/Kconfig | 10 ++++ drivers/platform/x86/amd/pmf/Makefile | 2 + drivers/platform/x86/amd/pmf/core.c | 5 ++ drivers/platform/x86/amd/pmf/pmf.h | 9 ++++ drivers/platform/x86/amd/pmf/util.c | 68 +++++++++++++++++++++++++ include/uapi/linux/amd-pmf.h | 71 +++++++++++++++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 drivers/platform/x86/amd/pmf/util.c create mode 100644 include/uapi/linux/amd-pmf.h diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig index 25b8f7ae3abd..ad4faf18de47 100644 --- a/drivers/platform/x86/amd/pmf/Kconfig +++ b/drivers/platform/x86/amd/pmf/Kconfig @@ -30,3 +30,13 @@ config AMD_PMF_DEBUG in the PMF config store. Say Y here to enable more debug logs and Say N here if you are not sure. + +config AMD_PMF_UTIL_SUPPORT + bool "AMD PMF Util layer support" + depends on AMD_PMF + help + Enabling this option provides a character device for userspace to capture + PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic + Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. + + Say Y here to enable it and Say N here if you are not sure. diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile index 5978464e0eb7..bf7aad80b9e9 100644 --- a/drivers/platform/x86/amd/pmf/Makefile +++ b/drivers/platform/x86/amd/pmf/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_AMD_PMF) += amd-pmf.o amd-pmf-y := core.o acpi.o sps.o \ auto-mode.o cnqf.o \ tee-if.o spc.o +# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled +amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index b9e5a2cf3aae..58d86b4c2828 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -634,6 +634,10 @@ static int amd_pmf_probe(struct platform_device *pdev) pmf_device = dev->dev; + err = amd_pmf_cdev_register(dev); + if (err) + dev_warn(dev->dev, "failed to register util interface: %d\n", err); + dev_info(dev->dev, "registered PMF device successfully\n"); return 0; @@ -643,6 +647,7 @@ static void amd_pmf_remove(struct platform_device *pdev) { struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + amd_pmf_cdev_unregister(); amd_pmf_deinit_features(dev); if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 69fef7448744..6f61076a9386 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -928,4 +928,13 @@ 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); +/* Util Layer */ +#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) +int amd_pmf_cdev_register(struct amd_pmf_dev *dev); +void amd_pmf_cdev_unregister(void); +#else +static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } +static inline void amd_pmf_cdev_unregister(void) {} +#endif + #endif /* PMF_H */ diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c new file mode 100644 index 000000000000..f8a283192ffe --- /dev/null +++ b/drivers/platform/x86/amd/pmf/util.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Util Layer + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K + * Sanket Goswami + */ + +#include +#include +#include +#include + +#include "pmf.h" + +static struct amd_pmf_dev *pmf_dev_handle; +static DEFINE_MUTEX(pmf_util_lock); + +static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +static int amd_pmf_open(struct inode *inode, struct file *filp) +{ + guard(mutex)(&pmf_util_lock); + if (!pmf_dev_handle) + return -ENODEV; + + filp->private_data = pmf_dev_handle; + return 0; +} + +static const struct file_operations pmf_if_ops = { + .owner = THIS_MODULE, + .open = amd_pmf_open, + .unlocked_ioctl = amd_pmf_set_ioctl, +}; + +static struct miscdevice amd_pmf_util_if = { + .minor = MISC_DYNAMIC_MINOR, + .name = "amdpmf_interface", + .fops = &pmf_if_ops, +}; + +int amd_pmf_cdev_register(struct amd_pmf_dev *dev) +{ + int ret; + + guard(mutex)(&pmf_util_lock); + pmf_dev_handle = dev; + ret = misc_register(&amd_pmf_util_if); + if (ret) + pmf_dev_handle = NULL; + + return ret; +} + +void amd_pmf_cdev_unregister(void) +{ + guard(mutex)(&pmf_util_lock); + if (pmf_dev_handle) + misc_deregister(&amd_pmf_util_if); + pmf_dev_handle = NULL; +} diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h new file mode 100644 index 000000000000..bbbd39b2ce15 --- /dev/null +++ b/include/uapi/linux/amd-pmf.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* + * AMD Platform Management Framework (PMF) UAPI Header + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * This file defines the user-space API for interacting with the AMD PMF + * driver. It provides ioctl interfaces to query platform-specific metrics + * such as power source, slider position, platform type, laptop placement, + * and various BIOS input/output parameters. + */ + +#ifndef _UAPI_LINUX_AMD_PMF_H +#define _UAPI_LINUX_AMD_PMF_H + +#include +#include +#include + +/** + * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands + * + * This magic number uniquely identifies AMD PMF ioctl operations. + */ +#define AMD_PMF_IOC_MAGIC 'p' + +/** + * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data + * + * This ioctl command is used to populate the amd_pmf_info structure + * with the requested PMF metrics information. + */ +#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) + +#define AMD_PMF_BIOS_PARAMS_MAX 10 + +/* AMD PMF feature flags - bitmask indicating supported features */ +#define AMD_PMF_FEAT_AUTO_MODE BIT(0) +#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) +#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) +#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) +#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) + +struct amd_pmf_info { + __u64 size; + + /* Feature info */ + __u32 features_supported; + + /* Power and state info */ + __u32 platform_type; + __u32 power_source; + __u32 laptop_placement; + __u32 lid_state; + __u32 user_presence; + __u32 slider_position; + + /* Thermal and power metrics */ + __s32 skin_temp; + __u32 gfx_busy; + __s32 ambient_light; + __u32 avg_c0_residency; + __u32 max_c0_residency; + __u32 socket_power; + + /* BIOS parameters */ + __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; +}; + +#endif /* _UAPI_LINUX_AMD_PMF_H */ From 1983d7dee5db2bd17bf99ac22bddeebbc5f3e38c Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:39 +0530 Subject: [PATCH 065/152] platform/x86/amd/pmf: store BIOS output values for user-space metrics via util IOCTL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a bios_output[] to amd_pmf_dev struct and store the latest values for BIOS output policies when applying PMF policies. This enables the AMD PMF util layer to expose these BIOS outputs alongside selected thermal and power metrics to user space via /dev/amdpmf_interface and a new IOCTL, supporting real-time monitoring tools such as SystemDeck. Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-3-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 2 ++ drivers/platform/x86/amd/pmf/tee-if.c | 13 +++++++++++-- include/uapi/linux/amd-pmf.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 6f61076a9386..ffe74ebc46f8 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -130,6 +130,7 @@ struct cookie_header { #define GET_CMD true #define METRICS_TABLE_ID 7 +#define BIOS_OUTPUT_MAX 10 typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); @@ -442,6 +443,7 @@ struct amd_pmf_dev { struct pmf_cbi_ring_buffer cbi_buf; struct mutex cbi_mutex; /* Protects ring buffer access */ struct mutex metrics_mutex; + u32 bios_output[BIOS_OUTPUT_MAX]; }; struct apmf_sps_prop_granular_v2 { diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c index 7ccd93f506b2..3fe79f0f37c5 100644 --- a/drivers/platform/x86/amd/pmf/tee-if.c +++ b/drivers/platform/x86/amd/pmf/tee-if.c @@ -8,7 +8,9 @@ * Author: Shyam Sundar S K */ +#include #include +#include #include #include #include "pmf.h" @@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) { - u32 bios_idx; + int bios_idx; + int ret; bios_idx = amd_pmf_get_bios_output_idx(action->action_index); + if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { + dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); + return; + } - amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + if (!ret) + pdev->bios_output[bios_idx] = action->value; } static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index bbbd39b2ce15..90bc2970665d 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -66,6 +66,7 @@ struct amd_pmf_info { /* BIOS parameters */ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; + __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; }; #endif /* _UAPI_LINUX_AMD_PMF_H */ From 08f3df928fc8cbab2cae09296f568ed294b3a81a Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:40 +0530 Subject: [PATCH 066/152] platform/x86/amd/pmf: Store commonly used enums in the header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relocate commonly used enums from multiple source files into a shared header file to simplify code structure, improve readability, and enhance maintainability. Also, remove the initialization of the first enum member, since it is not needed. Add the AMD_PMF_ prefix to the laptop_placement and platform_type enums since these names are overly generic for inclusion in a UAPI header Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-4-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../amd-sfh-hid/sfh1_1/amd_sfh_interface.c | 13 ++-- drivers/platform/x86/amd/pmf/pmf.h | 22 ------ drivers/platform/x86/amd/pmf/spc.c | 37 +++++----- include/linux/amd-pmf-io.h | 9 --- include/uapi/linux/amd-pmf.h | 74 +++++++++++++++++++ 5 files changed, 100 insertions(+), 55 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 837d59e7a661..8ac44368a37d 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -7,6 +7,7 @@ * * Author: Basavaraj Natikar */ +#include #include #include #include @@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) *platform_type = mode.op_mode.devicemode; if (mode.op_mode.ontablestate == 1) { - *laptop_placement = ON_TABLE; + *laptop_placement = AMD_PMF_ON_TABLE; } else if (mode.op_mode.ontablestate == 2) { - *laptop_placement = ON_LAP_MOTION; + *laptop_placement = AMD_PMF_ON_LAP_MOTION; } else if (mode.op_mode.inbagstate == 1) { - *laptop_placement = IN_BAG; + *laptop_placement = AMD_PMF_IN_BAG; } else if (mode.op_mode.outbagstate == 1) { - *laptop_placement = OUT_OF_BAG; + *laptop_placement = AMD_PMF_OUT_OF_BAG; } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || mode.op_mode.outbagstate == 0) { - *laptop_placement = LP_UNKNOWN; + *laptop_placement = AMD_PMF_LP_UNKNOWN; pr_warn_once("Unknown laptop placement\n"); } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || mode.op_mode.outbagstate == 3) { - *laptop_placement = LP_UNDEFINED; + *laptop_placement = AMD_PMF_LP_UNDEFINED; pr_warn_once("Undefined laptop placement\n"); } diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index ffe74ebc46f8..269c0a4b1cae 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -682,14 +682,6 @@ enum system_state { SYSTEM_STATE_MAX, }; -enum ta_slider { - TA_BEST_BATTERY, - TA_BETTER_BATTERY, - TA_BETTER_PERFORMANCE, - TA_BEST_PERFORMANCE, - TA_MAX, -}; - struct amd_pmf_pb_bitmap { const char *name; u32 bit_mask; @@ -721,20 +713,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, }; -enum platform_type { - PTYPE_UNKNOWN = 0, - LID_CLOSE, - CLAMSHELL, - FLAT, - TENT, - STAND, - TABLET, - BOOK, - PRESENTATION, - PULL_FWD, - PTYPE_INVALID = 0xf, -}; - /* Command ids for TA communication */ enum ta_pmf_command { TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index f48678a23cc7..05998946d1bd 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -20,21 +21,21 @@ static const char *platform_type_as_str(u16 platform_type) { switch (platform_type) { - case CLAMSHELL: + case AMD_PMF_CLAMSHELL: return "CLAMSHELL"; - case FLAT: + case AMD_PMF_FLAT: return "FLAT"; - case TENT: + case AMD_PMF_TENT: return "TENT"; - case STAND: + case AMD_PMF_STAND: return "STAND"; - case TABLET: + case AMD_PMF_TABLET: return "TABLET"; - case BOOK: + case AMD_PMF_BOOK: return "BOOK"; - case PRESENTATION: + case AMD_PMF_PRESENTATION: return "PRESENTATION"; - case PULL_FWD: + case AMD_PMF_PULL_FWD: return "PULL_FWD"; default: return "UNKNOWN"; @@ -44,13 +45,13 @@ static const char *platform_type_as_str(u16 platform_type) static const char *laptop_placement_as_str(u16 device_state) { switch (device_state) { - case ON_TABLE: + case AMD_PMF_ON_TABLE: return "ON_TABLE"; - case ON_LAP_MOTION: + case AMD_PMF_ON_LAP_MOTION: return "ON_LAP_MOTION"; - case IN_BAG: + case AMD_PMF_IN_BAG: return "IN_BAG"; - case OUT_OF_BAG: + case AMD_PMF_OUT_OF_BAG: return "OUT_OF_BAG"; default: return "UNKNOWN"; @@ -60,11 +61,11 @@ static const char *laptop_placement_as_str(u16 device_state) static const char *ta_slider_as_str(unsigned int state) { switch (state) { - case TA_BEST_PERFORMANCE: + case AMD_PMF_TA_BEST_PERFORMANCE: return "PERFORMANCE"; - case TA_BETTER_PERFORMANCE: + case AMD_PMF_TA_BETTER_PERFORMANCE: return "BALANCED"; - case TA_BEST_BATTERY: + case AMD_PMF_TA_BEST_BATTERY: return "POWER_SAVER"; default: return "Unknown TA Slider State"; @@ -287,14 +288,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ switch (dev->current_profile) { case PLATFORM_PROFILE_PERFORMANCE: case PLATFORM_PROFILE_BALANCED_PERFORMANCE: - val = TA_BEST_PERFORMANCE; + val = AMD_PMF_TA_BEST_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - val = TA_BETTER_PERFORMANCE; + val = AMD_PMF_TA_BETTER_PERFORMANCE; break; case PLATFORM_PROFILE_LOW_POWER: case PLATFORM_PROFILE_QUIET: - val = TA_BEST_BATTERY; + val = AMD_PMF_TA_BEST_BATTERY; break; default: dev_err(dev->dev, "Unknown Platform Profile.\n"); diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h index 55198d2875cc..e014d4ce5a20 100644 --- a/include/linux/amd-pmf-io.h +++ b/include/linux/amd-pmf-io.h @@ -52,15 +52,6 @@ struct amd_sfh_info { u32 laptop_placement; }; -enum laptop_placement { - LP_UNKNOWN = 0, - ON_TABLE, - ON_LAP_MOTION, - IN_BAG, - OUT_OF_BAG, - LP_UNDEFINED, -}; - /** * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver * @npuclk_freq: NPU clock frequency [MHz] diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index 90bc2970665d..bd802f25a717 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -42,6 +42,80 @@ #define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) #define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) +/** + * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop + * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined + * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk + * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion + * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case + * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case + * @AMD_PMF_LP_UNDEFINED: Placement state is undefined + * + * This enumeration represents the physical placement state of the laptop + * as detected by platform sensors. Used for adaptive power management + * and thermal policies. + */ +enum amd_pmf_laptop_placement { + AMD_PMF_LP_UNKNOWN, + AMD_PMF_ON_TABLE, + AMD_PMF_ON_LAP_MOTION, + AMD_PMF_IN_BAG, + AMD_PMF_OUT_OF_BAG, + AMD_PMF_LP_UNDEFINED, +}; + +/** + * enum amd_pmf_ta_slider - Trusted Application power slider positions + * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance + * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life + * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance + * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption + * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value + * + * This enumeration defines the power slider positions used by the + * AMD PMF Trusted Application for dynamic power management decisions. + * These correspond to the Windows power slider UI positions. + */ +enum amd_pmf_ta_slider { + AMD_PMF_TA_BEST_BATTERY, + AMD_PMF_TA_BETTER_BATTERY, + AMD_PMF_TA_BETTER_PERFORMANCE, + AMD_PMF_TA_BEST_PERFORMANCE, + AMD_PMF_TA_MAX, +}; + +/** + * enum amd_pmf_platform_type - Describes the physical form factor orientation + * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined + * @AMD_PMF_LID_CLOSE: Laptop lid is closed + * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen + * @AMD_PMF_FLAT: Device is lying flat on a surface + * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) + * @AMD_PMF_STAND: Device is propped up in stand orientation + * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden + * @AMD_PMF_BOOK: Device is in book reading orientation + * @AMD_PMF_PRESENTATION: Device is in presentation mode + * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user + * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker + * + * This enumeration describes the current physical orientation or form + * factor of convertible/2-in-1 devices. Used for optimizing power and + * thermal management based on device posture. + */ +enum amd_pmf_platform_type { + AMD_PMF_PTYPE_UNKNOWN, + AMD_PMF_LID_CLOSE, + AMD_PMF_CLAMSHELL, + AMD_PMF_FLAT, + AMD_PMF_TENT, + AMD_PMF_STAND, + AMD_PMF_TABLET, + AMD_PMF_BOOK, + AMD_PMF_PRESENTATION, + AMD_PMF_PULL_FWD, + AMD_PMF_PTYPE_INVALID = 0xf, +}; + struct amd_pmf_info { __u64 size; From c7bdedfcb8bab6dc5074623462bddd3241f0f311 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:41 +0530 Subject: [PATCH 067/152] platform/x86/amd/pmf: Move debug helper functions to UAPI header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These debug helper functions convert enumerated values to appropriate message and can be useful for userspace tools and other kernel components that need to interpret AMD PMF state values. By making them inline functions in the UAPI header, they become available to both kernel and userspace without code duplication. Also, prefix the function names with amd_pmf_. Also, include a case to cover unused enum entries. Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-5-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/spc.c | 62 +++--------------------------- include/uapi/linux/amd-pmf.h | 58 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 05998946d1bd..6e33824ccadc 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -18,60 +18,6 @@ #include "pmf.h" #ifdef CONFIG_AMD_PMF_DEBUG -static const char *platform_type_as_str(u16 platform_type) -{ - switch (platform_type) { - case AMD_PMF_CLAMSHELL: - return "CLAMSHELL"; - case AMD_PMF_FLAT: - return "FLAT"; - case AMD_PMF_TENT: - return "TENT"; - case AMD_PMF_STAND: - return "STAND"; - case AMD_PMF_TABLET: - return "TABLET"; - case AMD_PMF_BOOK: - return "BOOK"; - case AMD_PMF_PRESENTATION: - return "PRESENTATION"; - case AMD_PMF_PULL_FWD: - return "PULL_FWD"; - default: - return "UNKNOWN"; - } -} - -static const char *laptop_placement_as_str(u16 device_state) -{ - switch (device_state) { - case AMD_PMF_ON_TABLE: - return "ON_TABLE"; - case AMD_PMF_ON_LAP_MOTION: - return "ON_LAP_MOTION"; - case AMD_PMF_IN_BAG: - return "IN_BAG"; - case AMD_PMF_OUT_OF_BAG: - return "OUT_OF_BAG"; - default: - return "UNKNOWN"; - } -} - -static const char *ta_slider_as_str(unsigned int state) -{ - switch (state) { - case AMD_PMF_TA_BEST_PERFORMANCE: - return "PERFORMANCE"; - case AMD_PMF_TA_BETTER_PERFORMANCE: - return "BALANCED"; - case AMD_PMF_TA_BEST_BATTERY: - return "POWER_SAVER"; - default: - return "Unknown TA Slider State"; - } -} - static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { @@ -89,7 +35,8 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * int i; dev_dbg(dev->dev, "==== TA inputs START ====\n"); - dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Slider State: %s\n", + amd_pmf_get_slider_position(in->ev_info.power_slider)); dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); @@ -103,9 +50,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); - dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Platform type: %s\n", + amd_pmf_get_platform_type(in->ev_info.platform_type)); dev_dbg(dev->dev, "Laptop placement: %s\n", - laptop_placement_as_str(in->ev_info.device_state)); + amd_pmf_get_laptop_placement(in->ev_info.device_state)); for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, amd_pmf_get_ta_custom_bios_inputs(in, i)); diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index bd802f25a717..24cf9926dc59 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -116,6 +116,64 @@ enum amd_pmf_platform_type { AMD_PMF_PTYPE_INVALID = 0xf, }; +static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) +{ + switch (platform_type) { + case AMD_PMF_CLAMSHELL: + return "CLAMSHELL"; + case AMD_PMF_LID_CLOSE: + return "LID_CLOSE"; + case AMD_PMF_FLAT: + return "FLAT"; + case AMD_PMF_TENT: + return "TENT"; + case AMD_PMF_STAND: + return "STAND"; + case AMD_PMF_TABLET: + return "TABLET"; + case AMD_PMF_BOOK: + return "BOOK"; + case AMD_PMF_PRESENTATION: + return "PRESENTATION"; + case AMD_PMF_PULL_FWD: + return "PULL_FWD"; + default: + return "UNKNOWN"; + } +} + +static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) +{ + switch (device_state) { + case AMD_PMF_ON_TABLE: + return "ON_TABLE"; + case AMD_PMF_ON_LAP_MOTION: + return "ON_LAP_MOTION"; + case AMD_PMF_IN_BAG: + return "IN_BAG"; + case AMD_PMF_OUT_OF_BAG: + return "OUT_OF_BAG"; + default: + return "UNKNOWN"; + } +} + +static inline const char *amd_pmf_get_slider_position(unsigned int state) +{ + switch (state) { + case AMD_PMF_TA_BEST_PERFORMANCE: + return "PERFORMANCE"; + case AMD_PMF_TA_BETTER_PERFORMANCE: + return "BALANCED"; + case AMD_PMF_TA_BEST_BATTERY: + return "POWER_SAVER"; + case AMD_PMF_TA_BETTER_BATTERY: + return "BALANCED_BATTERY"; + default: + return "Unknown TA Slider State"; + } +} + struct amd_pmf_info { __u64 size; From 5bda82c797c95a4f9c7f9eb38a3899b0ec5bc290 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:42 +0530 Subject: [PATCH 068/152] platform/x86/amd/pmf: Implement util layer ioctl handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the ioctl handler for the util layer character device. This support adds the actual functionality to populate PMF metrics from the TA shared memory buffer and return them to userspace. The implementation includes: - amd_pmf_populate_data() to extract metrics from TA shared memory - amd_pmf_set_ioctl() to handle userspace ioctl requests - Size negotiation for forward/backward compatibility - Feature-based population of struct fields - Export amd_pmf_get_ta_custom_bios_inputs() Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-6-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 1 + drivers/platform/x86/amd/pmf/spc.c | 3 +- drivers/platform/x86/amd/pmf/util.c | 87 ++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 269c0a4b1cae..752fa5dd2267 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -903,6 +903,7 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq 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); int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 6e33824ccadc..94355b435a66 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -18,7 +18,7 @@ #include "pmf.h" #ifdef CONFIG_AMD_PMF_DEBUG -static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) +u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { case 0 ... 1: @@ -29,6 +29,7 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int return 0; } } +EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); 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/util.c b/drivers/platform/x86/amd/pmf/util.c index f8a283192ffe..4d1a61e4f1b9 100644 --- a/drivers/platform/x86/amd/pmf/util.c +++ b/drivers/platform/x86/amd/pmf/util.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -19,9 +20,93 @@ static struct amd_pmf_dev *pmf_dev_handle; static DEFINE_MUTEX(pmf_util_lock); +static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) +{ + struct ta_pmf_shared_memory *ta_sm = NULL; + struct ta_pmf_enact_table *in = NULL; + int idx; + + if (!pdev || !info) + return -EINVAL; + + if (!pdev->shbuf) + return -EINVAL; + + ta_sm = pdev->shbuf; + in = &ta_sm->pmf_input.enact_table; + + /* Set size */ + info->size = sizeof(*info); + + /* PMF Feature support flags */ + if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) + info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; + if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) + info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; + if (pdev->smart_pc_enabled) + info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; + if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) + info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; + if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) + info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; + + /* Device States */ + info->platform_type = in->ev_info.platform_type; + info->laptop_placement = in->ev_info.device_state; + info->lid_state = in->ev_info.lid_state; + info->user_presence = in->ev_info.user_present; + info->slider_position = in->ev_info.power_slider; + + /* Thermal and Power Metrics */ + info->power_source = in->ev_info.power_source; + info->skin_temp = in->ev_info.skin_temperature; + info->gfx_busy = in->ev_info.gfx_busy; + info->ambient_light = in->ev_info.ambient_light; + info->avg_c0_residency = in->ev_info.avg_c0residency; + info->max_c0_residency = in->ev_info.max_c0residency; + info->socket_power = in->ev_info.socket_power; + + /* Custom BIOS input parameters */ + for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) + info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); + + /* BIOS output parameters */ + for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) + info->bios_output[idx] = pdev->bios_output[idx]; + + return 0; +} + static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - return -ENOTTY; + struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; + size_t copy_size; + __u64 user_size; + int ret; + + if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) + return -ENOTTY; + + /* First read just the size field from userspace */ + if (copy_from_user(&user_size, argp, sizeof(user_size))) + return -EFAULT; + + guard(mutex)(&pmf_util_lock); + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; + + copy_size = min_t(size_t, user_size, sizeof(info)); + + /* Set actual size being copied */ + info.size = copy_size; + + if (copy_to_user(argp, &info, copy_size)) + return -EFAULT; + + return 0; } static int amd_pmf_open(struct inode *inode, struct file *filp) From a5edf10e3adaef5072e472fd822c6ec46652b96c Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:43 +0530 Subject: [PATCH 069/152] platform/x86/amd/pmf: Introduce AMD PMF testing tool for driver metrics and features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tool leverages amd-pmf ioctls exposed via the util layer, allowing validation of its newly integrated util layer and /dev/amdpmf_interface. It includes a user-space test application, test_amd_pmf, designed to interact with the PMF driver and retrieve relevant metrics for the testing and analysis. It provides definitions for test metrics, feature IDs, and device states, and includes tests for various AMD PMF metrics such as power source, skin temperature, battery state, and custom BIOS inputs/outputs. It also enables the testing of PMF metrics data and feature support reporting. Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-7-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- tools/platform/x86/amd/Makefile | 60 +++++++++++++ tools/platform/x86/amd/test-pmf.c | 142 ++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 tools/platform/x86/amd/Makefile create mode 100644 tools/platform/x86/amd/test-pmf.c diff --git a/tools/platform/x86/amd/Makefile b/tools/platform/x86/amd/Makefile new file mode 100644 index 000000000000..5d820df5871f --- /dev/null +++ b/tools/platform/x86/amd/Makefile @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Include common tools build infrastructure +include $(srctree)/tools/scripts/Makefile.include + +CC = $(CROSS_COMPILE)gcc +BUILD_OUTPUT := $(CURDIR) +PREFIX ?= /usr +DESTDIR ?= + +ifeq ("$(origin O)", "command line") + BUILD_OUTPUT := $(O) +endif + +# Include paths: tools/include has linux/kernel.h with ARRAY_SIZE +INCLUDES = -I$(srctree)/tools/include +INCLUDES += -I$(srctree)/tools/include/uapi +INCLUDES += -I$(srctree)/include/uapi +INCLUDES += -I$(srctree)/include + +override CFLAGS += -O2 -Wall -Wextra -D_GNU_SOURCE $(INCLUDES) +override CFLAGS += -D_FILE_OFFSET_BITS=64 +override CFLAGS += -D__EXPORTED_HEADERS__ + +TARGETS = test_amd_pmf + +all: $(TARGETS) + +test_amd_pmf: test-pmf.c + $(QUIET_CC)$(CC) $(CFLAGS) $< -o $(BUILD_OUTPUT)/$@ $(LDFLAGS) + +.PHONY: clean +clean: + $(call QUIET_CLEAN, test_amd_pmf)$(RM) $(BUILD_OUTPUT)/test_amd_pmf + +.PHONY: install +install: $(TARGETS) + $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin + $(INSTALL) $(BUILD_OUTPUT)/test_amd_pmf $(DESTDIR)$(PREFIX)/bin/test_amd_pmf + +.PHONY: help +help: + @echo "AMD Platform Tools Makefile" + @echo "" + @echo "Targets:" + @echo " all - Build all tools (default)" + @echo " test_amd_pmf - Build the PMF test tool" + @echo " clean - Remove built files" + @echo " install - Install tools to $(PREFIX)/bin" + @echo "" + @echo "Variables:" + @echo " O= - Build output directory" + @echo " PREFIX - Installation prefix (default: /usr)" + @echo " DESTDIR - Destination directory for install" diff --git a/tools/platform/x86/amd/test-pmf.c b/tools/platform/x86/amd/test-pmf.c new file mode 100644 index 000000000000..82978afa6e1b --- /dev/null +++ b/tools/platform/x86/amd/test-pmf.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Test Tool + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K + * Sanket Goswami + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DEVICE_NODE "/dev/amdpmf_interface" + +/* Feature flag names */ +static const char * const feature_names[] = { + "Auto Mode", + "Static Power Slider", + "Policy Builder (Smart PC)", + "Dynamic Power Slider AC", + "Dynamic Power Slider DC", +}; + +static const char *banner = + "====================================================\n" + " AMD PMF Metrics info and Feature Status\n" + "====================================================\n\n"; + +/* Print feature flags */ +static void pmf_print_features(uint32_t flags) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(feature_names); i++) + printf(" [%c] %s\n", (flags & (1U << i)) ? 'x' : ' ', feature_names[i]); +} + +/* Print BIOS parameters */ +static void pmf_print_bios_params(const char *type, const __u32 *params) +{ + int i; + + for (i = 0; i < AMD_PMF_BIOS_PARAMS_MAX; i++) + printf(" Custom BIOS %s%d: %u\n", type, i + 1, params[i]); +} + +/* Open the PMF device */ +static int pmf_open_device(void) +{ + int fd; + + fd = open(DEVICE_NODE, O_RDONLY); + if (fd < 0) + fprintf(stderr, "Error: Cannot open %s: %s\n", DEVICE_NODE, strerror(errno)); + + return fd; +} + +/* Query PMF info using the single IOCTL */ +static int pmf_get_info(int fd, struct amd_pmf_info *info) +{ + int ret; + + /* Zero-initialize and set size for versioning */ + memset(info, 0, sizeof(*info)); + info->size = sizeof(*info); + + ret = ioctl(fd, IOCTL_AMD_PMF_POPULATE_DATA, info); + if (ret < 0) { + fprintf(stderr, "Error: IOCTL_AMD_PMF_POPULATE_DATA failed: %s\n", strerror(errno)); + return ret; + } + + return 0; +} + +static void pmf_print_info(const struct amd_pmf_info *info) +{ + printf("%s", banner); + + /* Feature status */ + printf("Feature Status:\n"); + pmf_print_features(info->features_supported); + + /* Device states */ + printf("\nDevice States:\n"); + printf(" Platform Type: %s\n", amd_pmf_get_platform_type(info->platform_type)); + printf(" Laptop Placement: %s\n", amd_pmf_get_laptop_placement(info->laptop_placement)); + printf(" Lid State: %s\n", info->lid_state ? "Closed" : "Open"); + printf(" User Presence: %s\n", info->user_presence ? "Present" : "Away"); + printf(" Slider Position: %s\n", amd_pmf_get_slider_position(info->slider_position)); + + /* Thermal and power metrics */ + printf("\nThermal/Power Metrics:\n"); + printf(" Skin Temperature: %d\n", info->skin_temp / 100); + printf(" GFX Busy: %u\n", info->gfx_busy); + printf(" Ambient Light: %d\n", info->ambient_light); + printf(" Avg C0 Residency: %u\n", info->avg_c0_residency); + printf(" Max C0 Residency: %u\n", info->max_c0_residency); + printf(" Socket Power: %u\n", info->socket_power); + + /* BIOS parameters */ + printf("\nCustom BIOS Input Parameters:\n"); + pmf_print_bios_params("Input", info->bios_input); + printf("\nCustom BIOS Output Parameters:\n"); + pmf_print_bios_params("Output", info->bios_output); + + printf("\n=================================================\n"); +} + +int main(void) +{ + struct amd_pmf_info info; + int fd, ret; + + fd = pmf_open_device(); + if (fd < 0) + return -1; + + /* Query all info with single IOCTL */ + ret = pmf_get_info(fd, &info); + close(fd); + + if (ret < 0) + return -1; + + pmf_print_info(&info); + + return 0; +} From f6ee11d77d15ff10f8a804eec529a7092bbb46a6 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:44 +0530 Subject: [PATCH 070/152] Documentation/ABI: add testing entry for AMD PMF character device interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Documentation/ABI/testing entry describing the AMD PMF util layer character device (/dev/amdpmf_interface) and the initial ioctl used to query feature support and metrics data information. This interface is available when CONFIG_AMD_PMF_UTIL_SUPPORT=y. Also update the MAINTAINERS record with the new UAPI header. Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-8-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/ABI/testing/amdpmf-interface | 73 ++++++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 74 insertions(+) create mode 100644 Documentation/ABI/testing/amdpmf-interface diff --git a/Documentation/ABI/testing/amdpmf-interface b/Documentation/ABI/testing/amdpmf-interface new file mode 100644 index 000000000000..625f90a013d0 --- /dev/null +++ b/Documentation/ABI/testing/amdpmf-interface @@ -0,0 +1,73 @@ +What: /dev/amdpmf_interface +Date: June 2026 +KernelVersion: 7.2 +Contact: Shyam Sundar S K +Description: + The AMD Platform Management Framework (PMF) util layer exposes a + minimal user-space interface via a character device for feature + discovery and metrics monitoring. + + When CONFIG_AMD_PMF_UTIL_SUPPORT is enabled, the driver creates + a character device: + + ====================== + /dev/amdpmf_interface + ====================== + + The interface supports a single ioctl: + + ============================ ======================================= + IOCTL Usage + IOCTL_AMD_PMF_POPULATE_DATA User passes a struct amd_pmf_info with + the size field set to sizeof(struct + amd_pmf_info). The driver returns all + available metrics and feature status + in the structure. + ============================ ======================================= + + struct amd_pmf_info layout: + + ======================= ========== ================================ + Field Type Description + size __u64 Structure size for versioning + features_supported __u32 Bitmask of supported features + platform_type __u32 Platform form factor orientation + power_source __u32 AC/DC power source + laptop_placement __u32 Device placement state + lid_state __u32 Lid open/closed status + user_presence __u32 User presence detection + slider_position __u32 Current power slider position + skin_temp __s32 Skin temperature (centidegrees) + gfx_busy __u32 Graphics workload percentage + ambient_light __s32 Ambient light sensor reading + avg_c0_residency __u32 Average C0 state residency + max_c0_residency __u32 Maximum C0 state residency + socket_power __u32 Socket power consumption + bios_input[10] __u32 Custom BIOS input parameters + bios_output[10] __u32 Custom BIOS output parameters + ======================= ========== ================================ + + Feature Support Flags (features_supported bitmask): + + ===================================== ==== ============================= + Flag Bit Description + AMD_PMF_FEAT_AUTO_MODE 0 Auto Mode feature support + AMD_PMF_FEAT_STATIC_POWER_SLIDER 1 Static Power Slider support + AMD_PMF_FEAT_POLICY_BUILDER 2 Policy Builder (Smart PC) + AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC 3 Dynamic slider on AC + AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC 4 Dynamic slider on DC + ===================================== ==== ============================= + + Return codes: + + ============= ============================================================ + Return code Description + 0 Success + EINVAL Invalid size or parameter + EFAULT copy_to_user/copy_from_user failures + ENODEV PMF device not available + ENOTTY Unknown ioctl command + ============= ============================================================ + + User-space tools integrating with AMD PMF to discover capabilities and + monitor real-time metrics for thermal and power management validation. diff --git a/MAINTAINERS b/MAINTAINERS index 1ef1ec374eab..3820b286f09f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1263,6 +1263,7 @@ L: platform-driver-x86@vger.kernel.org S: Supported F: Documentation/ABI/testing/sysfs-amd-pmf F: drivers/platform/x86/amd/pmf/ +F: include/uapi/linux/amd-pmf.h AMD POWERPLAY AND SWSMU M: Kenneth Feng From ef3daa2b84a2b8499ce9e2ce1c865dca36d39f95 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 17 Jun 2026 17:51:04 +0200 Subject: [PATCH 071/152] platform/x86: asus-wmi: fix resource leaks on probe failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During driver initialization in asus_wmi_add(), various subsystems are registered sequentially. However, the error path labels are out of order relative to the registration sequence. Specifically: 1. If asus_wmi_custom_fan_curve_init() fails, the driver jumps to fail_custom_fan_curve. Because this label is placed below fail_sysfs, it bypasses the cleanup calls for the input device and sysfs groups, which were successfully registered before, leaking those resources. 2. If asus_screenpad_init() fails, the driver jumps to fail_screenpad. Because fail_screenpad is placed below fail_backlight, it bypasses the cleanup calls for backlight and rfkill, leaking those resources. Fix these resource leaks by reordering the error path labels in asus_wmi_add() to match the exact reverse order of the resource allocations. Fixes: 0f0ac158d28f ("platform/x86: asus-wmi: Add support for custom fan curves") Fixes: 2c97d3e55b70 ("platform/x86: asus-wmi: add support for ASUS screenpad") Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Marco Scardovi Link: https://patch.msgid.link/20260617155104.10111-1-scardracs@disroot.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-wmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index c7c8fcfc1d72..8610663b8269 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -5248,20 +5248,20 @@ static int asus_wmi_add(struct platform_device *pdev) return 0; fail_wmi_handler: + asus_screenpad_exit(asus); +fail_screenpad: asus_wmi_backlight_exit(asus); fail_backlight: asus_wmi_rfkill_exit(asus); -fail_screenpad: - asus_screenpad_exit(asus); fail_rfkill: asus_wmi_led_exit(asus); fail_leds: +fail_custom_fan_curve: fail_hwmon: asus_wmi_input_exit(asus); fail_input: asus_wmi_sysfs_exit(asus->platform_device); fail_sysfs: -fail_custom_fan_curve: fail_platform_profile_setup: fail_fan_boost_mode: fail_platform: From c38cce70adef874c2a7b5132c14d6c221401deff Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 6 Jul 2026 09:20:56 +0800 Subject: [PATCH 072/152] platform/mellanox: mlxbf-pmc: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. mlxbf_pmc_probe() passes the result of ACPI_COMPANION() to acpi_device_hid(), which dereferences it, so force-binding the driver to a device without an ACPI companion leads to a NULL pointer dereference. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the mlxbf-pmc driver and return -ENODEV when the companion is missing. Fixes: 1a218d312e65 ("platform/mellanox: mlxbf-pmc: Add Mellanox BlueField PMC driver") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260706012056.524096-1-lilinmao@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/mellanox/mlxbf-pmc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c index 5ec1ad471696..2ad9e2b0493c 100644 --- a/drivers/platform/mellanox/mlxbf-pmc.c +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) static int mlxbf_pmc_probe(struct platform_device *pdev) { - struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); - const char *hid = acpi_device_hid(acpi_dev); struct device *dev = &pdev->dev; + struct acpi_device *acpi_dev; struct arm_smccc_res res; + const char *hid; guid_t guid; int ret; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + + hid = acpi_device_hid(acpi_dev); + /* Ensure we have the UUID we expect for this service. */ arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); guid_parse(mlxbf_pmc_svc_uuid_str, &guid); From 2b3a5dabe89e330413af403246b648c1890f368f Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 6 Jul 2026 09:25:11 +0800 Subject: [PATCH 073/152] platform/surface: acpi-notify: Check ACPI companion before use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), platform drivers that rely on the existence of a device's ACPI companion object should verify its presence. san_probe() dereferences the result of ACPI_COMPANION() when installing the GSBUS address space handler, so force-binding the driver to a device without an ACPI companion leads to a NULL pointer dereference. The dereference was introduced when the probe function was switched from ACPI_HANDLE() to ACPI_COMPANION(). Check the ACPI companion against NULL and return -ENODEV when it is missing, like commit e4865a56d013 ("ACPI: driver: Check ACPI_COMPANION() against NULL during probe") does for the core ACPI platform drivers. Fixes: a9e10e587304 ("ACPI: scan: Extend acpi_walk_dep_device_list()") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260706012512.524359-2-lilinmao@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_acpi_notify.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index a9dcb0bbe90e..593a7aba6243 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) static int san_probe(struct platform_device *pdev) { - struct acpi_device *san = ACPI_COMPANION(&pdev->dev); struct ssam_controller *ctrl; + struct acpi_device *san; struct san_data *data; acpi_status astatus; int status; + san = ACPI_COMPANION(&pdev->dev); + if (!san) + return -ENODEV; + ctrl = ssam_client_bind(&pdev->dev); if (IS_ERR(ctrl)) return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); From 10bae492e814eeb1013ab9bc08f655d14fca46e4 Mon Sep 17 00:00:00 2001 From: Vishnu Sankar Date: Wed, 15 Jul 2026 10:02:05 +0900 Subject: [PATCH 074/152] platform/x86: thinkpad_acpi: Fix USB-C Security probe failure on unsupported platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On systems where the USCS ACPI method is absent, acpi_evalf() returns AE_NOT_FOUND which maps to -EIO. This caused tpacpi_usbc_security_init() to propagate the error and thinkpad_acpi failed to probe entirely on unsupported platforms. Fix this by checking for USCS method presence with acpi_has_method() before attempting to call it, returning -ENODEV immediately if absent. This follows the same pattern used by other subdrivers in thinkpad_acpi.c. Fixes: 67e8d1e9cacd ("platform/x86: thinkpad_acpi: Add USB-C Security (USCS) support") Reported-by: Oliver Lin Closes: https://lore.kernel.org/platform-driver-x86/239c8162-e1e6-4b49-8292-35547c5a525c@liuxiaozhen.dev/ Tested-by: Oliver Lin Signed-off-by: Vishnu Sankar Link: https://patch.msgid.link/20260715010205.514132-1-vishnuocv@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 6dd7c28fc0db..efc7784f11d1 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -11368,6 +11368,9 @@ static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) { int err; + if (!acpi_has_method(hkey_handle, "USCS")) + return -ENODEV; + err = usbc_security_query(&tp_features.usbc_security_enabled); if (err == -ENODEV) return 0; From 1aa6d7c4cfb84ae750391042c2266e4acce827d8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 20 Jul 2026 15:26:10 +0200 Subject: [PATCH 075/152] platform/x86: uniwill-laptop: Split uniwill_kbd_led_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function uniwill_kbd_led_init() is quite large and doing multiple things at once: - general hardware initialisation - single color keyboard backlight registration - RGB keyboard backlight registration Move the last two things into separate functions to increase the maintainability of uniwill_kbd_led_init(). Suggested-by: Werner Sembach Reviewed-by: Werner Sembach Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260720132611.374073-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 123 +++++++++++--------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index f55b239bd4d1..d5a8a2b362fb 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1733,7 +1733,24 @@ static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev return uniwill_kbd_led_read_brightness(data); } -static int uniwill_kbd_led_init(struct uniwill_data *data) +static int uniwill_white_kbd_led_init(struct uniwill_data *data) +{ + struct led_init_data init_data = { + .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + + data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; + data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; + data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; + + return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); +} + +static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) { unsigned int color_indices[KBD_LED_CHANNELS] = { LED_COLOR_ID_RED, @@ -1741,6 +1758,7 @@ static int uniwill_kbd_led_init(struct uniwill_data *data) LED_COLOR_ID_BLUE, }; struct led_init_data init_data = { + .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, .devicename = DRIVER_NAME, .devname_mandatory = true, }; @@ -1749,57 +1767,6 @@ static int uniwill_kbd_led_init(struct uniwill_data *data) unsigned int regval; int ret; - if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) - return 0; - - ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); - if (ret < 0) - return ret; - - if (!(regval & CHINA_MODE)) { - ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); - if (ret < 0) - return ret; - } - - ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); - if (ret < 0) - return ret; - - regval |= KBD_APPLY; - regval &= ~KBD_POWER_OFF; - ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); - if (ret < 0) - return ret; - - switch (data->project_id) { - case PROJECT_ID_PF: - case PROJECT_ID_PF4MU_PF4MN_PF5MU: - case PROJECT_ID_PH4TRX1: - case PROJECT_ID_PH4TUX1: - case PROJECT_ID_PH4TQX1: - case PROJECT_ID_PH6TRX1: - case PROJECT_ID_PH6TQXX: - case PROJECT_ID_PHXAXXX: - case PROJECT_ID_PHXPXXX: - data->single_color_kbd = true; - break; - default: - data->single_color_kbd = regval & KBD_WHITE_ONLY; - break; - } - - if (data->single_color_kbd) { - init_data.default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT; - data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; - data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; - data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; - data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; - data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; - - return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); - } - for (int i = 0; i < KBD_LED_CHANNELS; i++) { data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; @@ -1851,7 +1818,6 @@ static int uniwill_kbd_led_init(struct uniwill_data *data) if (ret < 0) return ret; - init_data.default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT; 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.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; @@ -1864,6 +1830,57 @@ static int uniwill_kbd_led_init(struct uniwill_data *data) &init_data); } +static int uniwill_kbd_led_init(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); + if (ret < 0) + return ret; + + if (!(regval & CHINA_MODE)) { + ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + regval |= KBD_APPLY; + regval &= ~KBD_POWER_OFF; + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); + if (ret < 0) + return ret; + + switch (data->project_id) { + case PROJECT_ID_PF: + case PROJECT_ID_PF4MU_PF4MN_PF5MU: + case PROJECT_ID_PH4TRX1: + case PROJECT_ID_PH4TUX1: + case PROJECT_ID_PH4TQX1: + case PROJECT_ID_PH6TRX1: + case PROJECT_ID_PH6TQXX: + case PROJECT_ID_PHXAXXX: + case PROJECT_ID_PHXPXXX: + data->single_color_kbd = true; + break; + default: + data->single_color_kbd = regval & KBD_WHITE_ONLY; + break; + } + + if (data->single_color_kbd) + return uniwill_white_kbd_led_init(data); + + return uniwill_rgb_kbd_led_init(data); +} + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) { /* 0 means "charging threshold not active" */ From 79c5e5d6909ab0c060b4dcf406b50d139c09a168 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 20 Jul 2026 15:26:11 +0200 Subject: [PATCH 076/152] platform/x86: uniwill-laptop: Remove single color keyboard detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having a ad-hoc device whitelist inside uniwill_kbd_led_init() to work around unreliable KBD_WHITE_ONLY values conflicts with the idea of the device descriptor infrastructure. Remove the ad-hoc device whitelist and use the device descriptor infrastructure instead. Suggested-by: Werner Sembach Reviewed-by: Werner Sembach Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260720132611.374073-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 32 +++++++-------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index d5a8a2b362fb..ac8df579903a 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -255,6 +255,7 @@ #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C +/* Unreliable on some models, use the device descriptor instead. */ #define KBD_WHITE_ONLY BIT(0) #define KBD_POWER_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) @@ -400,7 +401,7 @@ struct uniwill_data { u8 lightbar_max_brightness; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; - bool single_color_kbd; + bool kbd_led_single_color; u8 kbd_led_max_brightness; unsigned int last_kbd_status; union { @@ -426,6 +427,7 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; + bool kbd_led_single_color; u8 kbd_led_max_brightness; u8 lightbar_max_brightness; /* Executed during driver probing */ @@ -1629,7 +1631,7 @@ static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) struct led_classdev *led_cdev; int ret; - if (data->single_color_kbd) + if (data->kbd_led_single_color) led_cdev = &data->kbd_led_cdev; else led_cdev = &data->kbd_led_mc_cdev.led_cdev; @@ -1858,24 +1860,7 @@ static int uniwill_kbd_led_init(struct uniwill_data *data) if (ret < 0) return ret; - switch (data->project_id) { - case PROJECT_ID_PF: - case PROJECT_ID_PF4MU_PF4MN_PF5MU: - case PROJECT_ID_PH4TRX1: - case PROJECT_ID_PH4TUX1: - case PROJECT_ID_PH4TQX1: - case PROJECT_ID_PH6TRX1: - case PROJECT_ID_PH6TQXX: - case PROJECT_ID_PHXAXXX: - case PROJECT_ID_PHXPXXX: - data->single_color_kbd = true; - break; - default: - data->single_color_kbd = regval & KBD_WHITE_ONLY; - break; - } - - if (data->single_color_kbd) + if (data->kbd_led_single_color) return uniwill_white_kbd_led_init(data); return uniwill_rgb_kbd_led_init(data); @@ -2351,6 +2336,7 @@ static int uniwill_probe(struct platform_device *pdev) return ret; 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; @@ -2580,7 +2566,7 @@ static int uniwill_resume_kbd_led(struct uniwill_data *data) if (ret < 0) return ret; - if (data->single_color_kbd) + if (data->kbd_led_single_color) return 0; return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); @@ -2687,6 +2673,7 @@ static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | UNIWILL_FEATURE_AC_AUTO_BOOT | UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_single_color = false, .kbd_led_max_brightness = 4, }; @@ -2854,6 +2841,7 @@ static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | UNIWILL_FEATURE_AC_AUTO_BOOT | UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_single_color = true, .kbd_led_max_brightness = 2, }; @@ -3341,6 +3329,8 @@ static int __init uniwill_init(void) if (force) { /* Assume that the device supports all features except the charge limit */ device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; + /* Some models only have a (white) single color keyboard backlight */ + 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 */ From 6303451189a6d8f304221065c44ccd597e0acbe5 Mon Sep 17 00:00:00 2001 From: Nizar Al-Kabbani Date: Sun, 19 Jul 2026 12:08:43 +0300 Subject: [PATCH 077/152] platform/x86: asus-nb-wmi: map ProArt key (0x8b) to KEY_PROG3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dedicated "ProArt" key on the ASUS ProArt PX13 (HN7306) emits ASUS WMI event code 0x8b. This code is absent from the asus-nb-wmi sparse keymap, so pressing the key only produces a "Unknown key code 0x8b" message and does nothing in userspace. On this model the key is exposed solely through the WMI interface and has no HID scancode, so it can only be handled inside asus-nb-wmi. Map 0x8b to KEY_PROG3 so the key emits a bindable event, consistent with the existing KEY_PROG3 mapping used for the ASUS programmable/Armoury key. Signed-off-by: Nizar Al-Kabbani Reviewed-by: Denis Benato Link: https://patch.msgid.link/20260719090843.66777-1-nizar.m.kabbani@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-nb-wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 8005c088e9ee..aeb461b1644d 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ + { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ From e25fb2258bddbcf70df69d1358be3e1274bdc5f2 Mon Sep 17 00:00:00 2001 From: Yap Weei Choong Date: Fri, 17 Jul 2026 14:07:45 +0800 Subject: [PATCH 078/152] platform/x86/amd/pmc: Add T14 Gen2 AMD (20XL) to s2idle quirk list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ThinkPad T14 Gen 2 AMD was sold under two machine types, 20XK and 20XL, but only 20XK has been in the s2idle quirk list since the quirk was introduced in commit 455cd867b85b ("platform/x86: thinkpad_acpi: Add a s2idle resume quirk for a number of laptops"). On 20XL machines the firmware SMI handler therefore still runs on the NVMe D3->D0 transition when exiting suspend-to-idle. With IOMMU translation enabled (the default), this intermittently stalls resume for ~10.25 seconds: seven devices across three root ports (nvme, both xhci_hcd functions, xhci-pci-renesas, snd_hda_intel x2, snd_rn_pci_acp3x) block in pci_pm_resume_noirq and are released simultaneously, consistent with all cores being held in SMM. Add the missing 20XL machine type, mirroring the existing 20XK entry. This is the same class of omission fixed by commit 9a469c6dfab3 ("platform/x86: thinkpad_acpi: Add missing T14s Gen1 type to s2idle quirk list"). Verified on a ThinkPad T14 Gen 2a (type 20XLS41C00, BIOS R1MET62W 1.32, kernel 6.12.95): with this entry the quirk message appears at probe and a 14-hour suspend resumes in ~1s with no noirq stalls, where previously even short suspends could hit the ~10s delay. A further two-day soak (short naps and two ~14-hour suspends) showed every resume clean. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221755 Signed-off-by: Yap Weei Choong Tested-by: Yap Weei Choong Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260717060744.1252065-2-ywc8891@gmail.com 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(+) diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 74ddf1d8289a..09a30f333759 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "20XK"), } }, + { + .ident = "T14 Gen2 AMD", + .driver_data = &quirk_s2idle_spurious_8042, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), + } + }, { .ident = "T14 Gen1 AMD", .driver_data = &quirk_s2idle_spurious_8042, From 527b1169526de6dbaeb646d48b20720a12b5a233 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Wed, 8 Jul 2026 23:18:21 +0200 Subject: [PATCH 079/152] platform/x86: uniwill-laptop: Add 2 new feature defines for TUXEDO devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 2 of the new feature defines, UNIWILL_FEATURE_AC_AUTO_BOOT and UNIWILL_FEATURE_USB_POWERSHARE, to TUXEDO devices where applicable. Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260708211950.568799-1-wse@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 69 +++++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index ac8df579903a..b3c8727403d4 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2752,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini UNIWILL_FEATURE_USB_C_POWER_PRIORITY, }; -static struct uniwill_device_descriptor tux_featureset_3_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, -}; - static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | @@ -2772,6 +2763,30 @@ 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 = { + .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_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, +}; + +static struct uniwill_device_descriptor tux_featureset_4_nvidia_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, +}; + static int phxtxx1_probe(struct uniwill_data *data) { unsigned int value; @@ -2972,7 +2987,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_3_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", @@ -2980,7 +2995,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_3_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -2988,7 +3003,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_3_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -2996,7 +3011,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_3_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", @@ -3004,7 +3019,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_3_descriptor, + .driver_data = &tux_featureset_4_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", @@ -3012,7 +3027,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3020,7 +3035,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3028,7 +3043,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", @@ -3036,7 +3051,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", @@ -3044,7 +3059,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -3204,7 +3219,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", @@ -3212,7 +3227,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3220,7 +3235,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3228,7 +3243,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", @@ -3236,7 +3251,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 AMD", @@ -3244,7 +3259,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3252,7 +3267,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3260,7 +3275,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_3_nvidia_descriptor, + .driver_data = &tux_featureset_4_nvidia_descriptor, }, { .ident = "TUXEDO Book BA15 Gen10 AMD", From e6137bbef0fe23feaf19e4714892457853064eb6 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Wed, 8 Jul 2026 23:18:22 +0200 Subject: [PATCH 080/152] platform/x86: uniwill-laptop: Add Avell A60 MUV support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the Avell A60 MUV based on information from tuxedo-drivers, formerly known as tuxedo-keyboard: https://github.com/tuxedocomputers/tuxedo-keyboard/pull/91 Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260708211950.568799-2-wse@tuxedocomputers.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index b3c8727403d4..7a2eeaec4c96 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2909,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { }, .driver_data = &lapqc71a_lapqc71b_descriptor, }, + { + .ident = "Avell A60 MUV", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, { .ident = "Intel NUC x15", .matches = { From 408b87a481e3ab33cba00292e4c80a30ba97f995 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Tue, 30 Jun 2026 12:51:01 +0200 Subject: [PATCH 081/152] platform/x86/intel/pmc: initialize empty PMT read result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pmc_core_pmt_get_lpm_req() returns the last telemetry read status. When firmware exposes no enabled low-power modes, the loop does not run and the function returns an uninitialized stack value. Initialize the status to success so an empty mode set is handled deterministically. Signed-off-by: Yousef Alhouseen Reviewed-by: David E. Box Link: https://patch.msgid.link/20260630105101.54016-1-alhouseenyousef@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmc/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 825ba5fa0bcb..9f77c0716e59 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel { const u8 *lpm_indices; int num_maps, mode_offset = 0; - int ret, lpm_size; + int ret = 0, lpm_size; u8 mode; lpm_indices = pmc->map->lpm_reg_index; From caf8342512c3056005f475d350eeca089c3c6623 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 1 Jul 2026 18:42:04 +0200 Subject: [PATCH 082/152] platform/x86: acer-wmi: reject missing gaming WMI results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WMI_gaming_execute_u32_u64() returns success when firmware supplies no output object, leaving the caller output untouched. Gaming getters then inspect an uninitialized result value. When the caller requests an output value, return -ENOMSG if firmware supplies no object. Preserve a NULL output pointer as the supported way for callers to ignore the result. Fixes: 2d76708c2221 ("platform/x86: acer-wmi: use WMI calls for platform profile handling") Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260701164208.8998-1-alhouseenyousef@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wmi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index e0eaaefb13d0..61ae622c93d9 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) return -EIO; obj = result.pointer; - if (obj && out) { + if (!obj && out) { + ret = -ENOMSG; + } else if (obj && out) { switch (obj->type) { case ACPI_TYPE_INTEGER: *out = obj->integer.value; From 59f586eb9394c7697cfcd2e1bb172c95e772559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BCr=C5=9Fat=20Abayl=C4=B1?= Date: Thu, 23 Jul 2026 20:27:34 +0300 Subject: [PATCH 083/152] platform/x86: hp-wmi: Add GPU MUX switch support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for querying and switching the graphics MUX mode on HP systems via WMI. This introduces the 'gpu_mux_mode' sysfs attribute under the hp-wmi platform device, allowing userspace tools to check and safely switch between available graphics modes (e.g., UMA, Hybrid, Discrete). The hardware capabilities mask is primarily read using the modern 128-byte System Design Data query. However, to ensure backward compatibility with older models, a fallback mechanism is implemented. By mirroring the behavior of the Windows Omen Gaming Hub software, if the modern query fails but the MUX WMI endpoint (0x52) responds successfully to a read request, the driver defaults to a standard Hybrid + Discrete support mask (0x06). Signed-off-by: Kürşat Abaylı Link: https://patch.msgid.link/20260723172734.18361-1-hello@kursatabayli.dev [ij: add kstrtox.h] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 5353d997d272..808b8f7d0adf 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -14,6 +14,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +61,12 @@ enum hp_ec_offsets { #define HP_FAN_SPEED_AUTOMATIC 0x00 #define HP_POWER_LIMIT_DEFAULT 0x00 #define HP_POWER_LIMIT_NO_CHANGE 0xFF +#define HPWMI_MUX_MODE_UMA BIT(0) +#define HPWMI_MUX_MODE_HYBRID BIT(1) +#define HPWMI_MUX_MODE_DISCRETE BIT(2) +#define HPWMI_MUX_MODE_OPTIMUS BIT(3) +#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) +#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required @@ -381,6 +390,7 @@ enum hp_wmi_commandtype { HPWMI_POSTCODEERROR_QUERY = 0x2a, HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, + HPWMI_GRAPHICS_MUX_QUERY = 0x52, }; struct victus_power_limits { @@ -1171,12 +1181,134 @@ static int camera_shutter_input_setup(void) return err; } +static const u8 mux_bitmask_map[] = { + [0] = HPWMI_MUX_MODE_HYBRID, + [1] = HPWMI_MUX_MODE_DISCRETE, + [2] = HPWMI_MUX_MODE_OPTIMUS, + [3] = HPWMI_MUX_MODE_UMA, +}; + +static int hp_wmi_get_mux_supported_modes(u8 *supported) +{ + u8 legacy_buffer[4] = {}; + u8 buffer[128] = {}; + u32 req_packet = 0; + int ret; + + if (!supported) + return -EINVAL; + + /* Try modern BIOS design data query (128-byte buffer) */ + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + buffer, zero_if_sup(req_packet), sizeof(buffer)); + if (ret == 0) { + *supported = buffer[7]; + return 0; + } + + /* + * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. + * If the modern query is not supported, check if the MUX query endpoint + * responds to a read request. If it succeeds, the hardware has MUX + * capability but lacks the mode map, defaulting to Hybrid + Discrete. + */ + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, + legacy_buffer, sizeof(legacy_buffer), 0); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + *supported = HPWMI_MUX_LEGACY_MASK; + return 0; +} + +static int hp_wmi_get_mux_mode(u8 *mode) +{ + u8 buffer[4] = {}; + int ret; + + if (!mode) + return -EINVAL; + + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, + buffer, sizeof(buffer), sizeof(buffer)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + /* Mask the highest bit, which might be used as a BIOS status flag */ + *mode = buffer[0] & HPWMI_MUX_MODE_MASK; + return 0; +} + +static int hp_wmi_set_mux_mode(u8 mode) +{ + u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; + int ret; + + ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, + buffer, sizeof(buffer), sizeof(buffer)); + if (ret < 0) + return ret; + if (ret > 0) + return -EINVAL; + + return 0; +} + +static ssize_t gpu_mux_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 mode; + int ret; + + ret = hp_wmi_get_mux_mode(&mode); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", mode); +} + +static ssize_t gpu_mux_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + u32 requested; + u8 supported; + int ret; + + ret = kstrtou32(buf, 0, &requested); + if (ret) + return ret; + if (requested >= ARRAY_SIZE(mux_bitmask_map)) + return -EINVAL; + + ret = hp_wmi_get_mux_supported_modes(&supported); + if (ret) + return ret; + + /* Verify if the requested mode is allowed by the hardware mask */ + if (!(supported & mux_bitmask_map[requested])) + return -EOPNOTSUPP; + + ret = hp_wmi_set_mux_mode(requested); + if (ret) + return ret; + + return count; +} + static DEVICE_ATTR_RO(display); static DEVICE_ATTR_RO(hddtemp); static DEVICE_ATTR_RW(als); static DEVICE_ATTR_RO(dock); static DEVICE_ATTR_RO(tablet); static DEVICE_ATTR_RW(postcode); +static DEVICE_ATTR_RW(gpu_mux_mode); static struct attribute *hp_wmi_attrs[] = { &dev_attr_display.attr, @@ -1185,6 +1317,7 @@ static struct attribute *hp_wmi_attrs[] = { &dev_attr_dock.attr, &dev_attr_tablet.attr, &dev_attr_postcode.attr, + &dev_attr_gpu_mux_mode.attr, NULL, }; ATTRIBUTE_GROUPS(hp_wmi); From 30c906cff490c3601ee9ff110fe8115fabe75fd4 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Thu, 23 Jul 2026 18:14:15 +0530 Subject: [PATCH 084/152] platform/x86: int1092: Fix potential memory leak in sar_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The memory allocated for device_mode_info in parse_package() called by sar_get_data() is not freed in some of the error paths in sar_probe(). Fix that by converting to use device managed allocations. Fixes: dcfbd31ef4bc ("platform/x86: BIOS SAR driver for Intel M.2 Modem") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260723-platx86-v4-1-93b4a178b595@cse.iitm.ac.in Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/intel/int1092/intel_sar.c | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c index 849f7b415c1e..7263114f0b3d 100644 --- a/drivers/platform/x86/intel/int1092/intel_sar.c +++ b/drivers/platform/x86/intel/int1092/intel_sar.c @@ -91,8 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob item->package.count <= data->total_dev_mode) return AE_ERROR; - data->device_mode_info = kmalloc_objs(struct wwan_device_mode_info, - data->total_dev_mode); + data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, + data->total_dev_mode, + sizeof(*data->device_mode_info), + GFP_KERNEL); if (!data->device_mode_info) return AE_ERROR; @@ -253,7 +255,7 @@ static int sar_probe(struct platform_device *device) if (!handle) return -ENODEV; - context = kzalloc_obj(*context); + context = devm_kzalloc(&device->dev, sizeof(*context), GFP_KERNEL); if (!context) return -ENOMEM; @@ -264,7 +266,7 @@ static int sar_probe(struct platform_device *device) result = guid_parse(SAR_DSM_UUID, &context->guid); if (result) { dev_err(&device->dev, "SAR UUID parse error: %d\n", result); - goto r_free; + return result; } for (reg = 0; reg < MAX_REGULATORY; reg++) @@ -272,43 +274,29 @@ static int sar_probe(struct platform_device *device) if (sar_get_device_mode(device) != AE_OK) { dev_err(&device->dev, "Failed to get device mode\n"); - result = -EIO; - goto r_free; + return -EIO; } result = sysfs_create_group(&device->dev.kobj, &intcsar_group); if (result) { dev_err(&device->dev, "sysfs creation failed\n"); - goto r_free; + return result; } if (acpi_install_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY, sar_notify, (void *)device) != AE_OK) { dev_err(&device->dev, "Failed acpi_install_notify_handler\n"); - result = -EIO; - goto r_sys; + sysfs_remove_group(&device->dev.kobj, &intcsar_group); + return -EIO; } return 0; - -r_sys: - sysfs_remove_group(&device->dev.kobj, &intcsar_group); -r_free: - kfree(context); - return result; } static void sar_remove(struct platform_device *device) { - struct wwan_sar_context *context = dev_get_drvdata(&device->dev); - int reg; - acpi_remove_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY, sar_notify); sysfs_remove_group(&device->dev.kobj, &intcsar_group); - for (reg = 0; reg < MAX_REGULATORY; reg++) - kfree(context->config_data[reg].device_mode_info); - - kfree(context); } static struct platform_driver sar_driver = { From f5eb833730c071b68a68de5d1df442aae4ae7e6e Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Thu, 23 Jul 2026 18:14:16 +0530 Subject: [PATCH 085/152] platform/x86: int1092: Fix info leak in parse_package() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sashiko reports a possible information leak that can occur as follows: 1. In parse_package(), memory allocated for data->device_mode_info is not zerozed initially as it is allocated with devm_kmalloc_array(). 2. In the for loop after the allocation, a malformed ACPI package provided by firmware can cause some fields in data->device_mode_info to remain uninitialized. 3. Later in update_sar_data(), the uninitialized fields gets copied to the fields of context->sar_data, which can be exposed to userspace through sysfs attribute read (intc_data_show()). Fix the leak by switching to use devm_kcalloc() for allocation. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260710052806.100107-1-nihaal%40cse.iitm.ac.in Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260723-platx86-v4-2-93b4a178b595@cse.iitm.ac.in Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/int1092/intel_sar.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c index 7263114f0b3d..f506155f35d4 100644 --- a/drivers/platform/x86/intel/int1092/intel_sar.c +++ b/drivers/platform/x86/intel/int1092/intel_sar.c @@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob item->package.count <= data->total_dev_mode) return AE_ERROR; - data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, - data->total_dev_mode, - sizeof(*data->device_mode_info), - GFP_KERNEL); + data->device_mode_info = devm_kcalloc(&context->sar_device->dev, + data->total_dev_mode, + sizeof(*data->device_mode_info), + GFP_KERNEL); if (!data->device_mode_info) return AE_ERROR; From 5029bff09e79ad80883c5ee2828b8c4a263cf3dd Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 23 Jul 2026 02:44:37 +0800 Subject: [PATCH 086/152] platform/x86: ideapad-laptop: Fix driver unregistration order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two drivers in the ideapad-laptop module: a wmi_driver and a platform_driver. The former is registered before the latter, but the unregistration order is the same as the init one instead of the reverse. Fix the driver unregistration order by reversing it. Suggested-by: Ilpo Järvinen Reviewed-by: Mark Pearson Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260723-ideapad-rev-exit-v2-1-9c31159022de@rong.moe Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ideapad-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 4fbc904f1fc3..8213524504ee 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init) static void __exit ideapad_laptop_exit(void) { - ideapad_wmi_driver_unregister(); platform_driver_unregister(&ideapad_acpi_driver); + ideapad_wmi_driver_unregister(); } module_exit(ideapad_laptop_exit) From 9c40a57258f6cb4109de46467966498e6f90ece6 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:51 +0530 Subject: [PATCH 087/152] platform/x86/amd/hsmp: Serialize ACPI HSMP probe and remove with an rwsem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add hsmp_sock_rwsem and export it, then hold it for write across ACPI probe, remove and init_acpi() so concurrent per-socket platform probes cannot race the is_probed handshake or the one-time socket-array allocation. Use lockdep_assert_held_write() in init_acpi() to catch incorrect locking under lockdep. An rw_semaphore is used rather than a plain mutex because an upcoming change adds a read side so data-plane messages run concurrently with each other while probe/remove hold it for write to drain in-flight messages. Introducing it as an rwsem now keeps the lock type stable across that change. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-2-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 22 ++++++++++++++++++++++ drivers/platform/x86/amd/hsmp/hsmp.c | 9 +++++++++ drivers/platform/x86/amd/hsmp/hsmp.h | 7 +++++++ 3 files changed, 38 insertions(+) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 72f68cef1297..a23797bd1dd5 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -15,12 +15,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -482,11 +485,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att return len; } +/* + * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox + * handshake and register its sysfs/hwmon interfaces. + * + * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the + * per-socket bring-up cannot race a concurrent probe or remove. + */ static int init_acpi(struct device *dev) { u16 sock_ind; int ret; + lockdep_assert_held_write(&hsmp_sock_rwsem); + ret = hsmp_get_uid(dev, &sock_ind); if (ret) return ret; @@ -607,6 +619,14 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev) return -ENOMEM; + /* + * Multiple ACPI socket devices probe in parallel, but the is_probed + * handshake and the one-time socket-array allocation below must run + * exactly once. Serialize the whole bring-up against concurrent + * probe/remove by holding the socket rwsem for write. + */ + guard(rwsem_write)(&hsmp_sock_rwsem); + if (!hsmp_pdev->is_probed) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { @@ -642,6 +662,8 @@ static int hsmp_acpi_probe(struct platform_device *pdev) static void hsmp_acpi_remove(struct platform_device *pdev) { + guard(rwsem_write)(&hsmp_sock_rwsem); + /* * We register only one misc_device even on multi-socket system. * So, deregister should happen only once. diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 1a87931136fd..e9c17698983c 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,14 @@ static struct hsmp_plat_device hsmp_pdev; +/* + * Serializes AMD HSMP socket bring-up and teardown: ACPI probe and remove take + * it for write so concurrent per-socket probes cannot race the is_probed + * handshake or the one-time socket-array allocation. + */ +DECLARE_RWSEM(hsmp_sock_rwsem); +EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); + /* * Send a message to the HSMP port via PCI-e config space registers * or by writing to MMIO space. diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 0509a442eaae..129200d0cf81 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -71,4 +72,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } #endif int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); + +/* + * Serializes HSMP socket bring-up and teardown. ACPI probe and remove take it + * for write. + */ +extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ From 5622564fc6924c1607944f0ad22be81ed6fd38fb Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:52 +0530 Subject: [PATCH 088/152] platform/x86/amd/hsmp: Map the metric table with ioremap() and unmap it explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metric-table DRAM region is mapped with devm_ioremap(), which ties the mapping to the socket device's devres scope. An upcoming change lets the ACPI front-end share the socket array across sockets and run its own coordinated teardown, so the mapping can no longer be pinned to a single per-socket devres scope. Map it with plain ioremap() instead and add hsmp_unmap_metric_tbls(), which drops every socket's metric_tbl_addr mapping. The platform driver registers that helper with devm_add_action_or_reset() so the mappings are released on both remove and probe failure, while the socket array itself stays devm-managed. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-3-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.c | 19 +++++++++++++++++-- drivers/platform/x86/amd/hsmp/hsmp.h | 1 + drivers/platform/x86/amd/hsmp/plat.c | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index e9c17698983c..008f3c0b2ad7 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -423,6 +424,21 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) } EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) +{ + struct hsmp_socket *sock; + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) { + sock = &pdev->sock[i]; + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + } +} +EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); + int hsmp_get_tbl_dram_base(u16 sock_ind) { struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; @@ -447,8 +463,7 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } - sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, - sizeof(struct hsmp_metric_table)); + sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 129200d0cf81..b0d67b93363d 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -64,6 +64,7 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index e07f68575055..b5f2120765c8 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -201,6 +201,19 @@ static int init_platform_device(struct device *dev) return 0; } +/* + * The socket array is devm-managed and freed by the driver core, but the + * metric-table DRAM regions are mapped with plain ioremap() during probe and + * are therefore not covered by devres. + * + * Drop those mappings from a devres action so both remove and probe failure + * unmap them exactly once, before the socket array they refer to is freed. + */ +static void hsmp_pltdrv_release(void *data) +{ + hsmp_unmap_metric_tbls(hsmp_pdev); +} + static int hsmp_pltdrv_probe(struct platform_device *pdev) { int ret; @@ -211,6 +224,10 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); + if (ret) + return ret; + ret = init_platform_device(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); From 8da4fedd5d1147d26e6ddbd7be2f1f5519df37dd Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:53 +0530 Subject: [PATCH 089/152] platform/x86/amd/hsmp: Serialize per-socket metric table reads with a mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HSMP_GET_METRIC_TABLE makes the firmware refill a shared per-socket metric DRAM region, which hsmp_metric_tbl_read() then copies out with memcpy_fromio(). Two concurrent readers of the metrics_bin sysfs attribute on the same socket can race: one can trigger a fresh fill while the other is mid-copy and return a torn snapshot. (The hwmon path does not touch this region; it only issues power messages via hsmp_send_message().) Embed a struct mutex metric_read_lock in each hsmp_socket and hold it across the fill-and-copy in hsmp_metric_tbl_read(). Add hsmp_init_metric_read_locks() and hsmp_destroy_metric_read_locks(), which take only struct hsmp_plat_device and iterate pdev->sock[] over pdev->num_sockets so the caller cannot pass a count that disagrees with the array. Wire them into both front-ends' probe and teardown paths so the mutex is always initialized before metrics_bin is exposed: the platform driver and the ACPI driver both drive hsmp_metric_tbl_read() through the same 0444 metrics_bin attribute. Doing this in one patch avoids a bisection point where an ACPI read would lock an uninitialized mutex. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 3 +++ drivers/platform/x86/amd/hsmp/hsmp.c | 28 ++++++++++++++++++++++++++++ drivers/platform/x86/amd/hsmp/hsmp.h | 5 +++++ drivers/platform/x86/amd/hsmp/plat.c | 3 +++ 4 files changed, 39 insertions(+) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index a23797bd1dd5..a092d7589bcb 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -639,6 +639,8 @@ static int hsmp_acpi_probe(struct platform_device *pdev) GFP_KERNEL); if (!hsmp_pdev->sock) return -ENOMEM; + + hsmp_init_metric_read_locks(hsmp_pdev); } ret = init_acpi(&pdev->dev); @@ -670,6 +672,7 @@ static void hsmp_acpi_remove(struct platform_device *pdev) */ if (hsmp_pdev->is_probed) { hsmp_misc_deregister(); + hsmp_destroy_metric_read_locks(hsmp_pdev); hsmp_pdev->is_probed = false; } } diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 008f3c0b2ad7..fd36c8f142c0 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -10,9 +10,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -415,6 +417,14 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) msg.msg_id = HSMP_GET_METRIC_TABLE; msg.sock_ind = sock->sock_ind; + /* + * HSMP_GET_METRIC_TABLE 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. + */ + guard(mutex)(&sock->metric_read_lock); + ret = hsmp_send_message(&msg); if (ret) return ret; @@ -424,6 +434,24 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) } EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_init(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); + +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_destroy(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); + void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) { struct hsmp_socket *sock; diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index b0d67b93363d..ec92c2a429bb 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,8 @@ struct hsmp_socket { void __iomem *metric_tbl_addr; void __iomem *virt_base_addr; struct semaphore hsmp_sem; + /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ + struct mutex metric_read_lock; char name[HSMP_ATTR_GRP_NAME_SIZE]; struct device *dev; u16 sock_ind; @@ -65,6 +68,8 @@ void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index b5f2120765c8..7a16d1ab463b 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -212,6 +212,7 @@ static int init_platform_device(struct device *dev) static void hsmp_pltdrv_release(void *data) { hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); } static int hsmp_pltdrv_probe(struct platform_device *pdev) @@ -224,6 +225,8 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; + hsmp_init_metric_read_locks(hsmp_pdev); + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); if (ret) return ret; From 6bd243d5abd91ee7d7fa58a9ed60381da49b4b0a Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:54 +0530 Subject: [PATCH 090/152] platform/x86/amd/hsmp: Clear mdev.this_device on deregister MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit misc_deregister() destroys the device but leaves miscdevice.this_device pointing at the freed struct device. Clear it so any later check of this_device, and a subsequent re-register, does not observe a stale pointer. An upcoming change uses this_device to track whether /dev/hsmp is registered across the shared ACPI sockets and relies on it being NULL after deregister. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-5-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index fd36c8f142c0..584fd9b1d31f 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -539,6 +539,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); void hsmp_misc_deregister(void) { misc_deregister(&hsmp_pdev.mdev); + hsmp_pdev.mdev.this_device = NULL; } EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); From 9b2895edb2b3ec1fb8a5adadb5305f6459151d38 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:55 +0530 Subject: [PATCH 091/152] platform/x86/amd/hsmp: ACPI HSMP refcounted sockets and coordinated release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI driver binds one platform device per socket but shares a single socket array and a single /dev/hsmp misc device across them. Replace the is_probed flag with state that tracks this shared ownership: - miscdevice.this_device tells whether /dev/hsmp is registered, so the misc device is registered on the first socket and torn down last. A preceding change clears mdev.this_device on deregister so this gate stays reliable across a re-probe. - a kref tracks the sockets that share the array. The first probe initializes it, each further probe takes a reference and every remove (or probe failure) drops one; the last put runs the release callback. All get/put happen under hsmp_sock_rwsem held for write, so the counting is already serialized and kref's atomic is not strictly needed, but kref gives the clearer get/put interface and a release callback. The shared socket array is allocated with kcalloc() on the first probe and freed by the release callback once the last reference is dropped. hsmp_acpi_sock_release() is the single teardown helper, run from kref_put(): it deregisters /dev/hsmp if registered, unmaps any metric-table DRAM, destroys the per-socket mutexes and frees the array. The remove path and the probe-failure path both reach it through the last put, so the teardown lives in one place. Both paths also clear this socket's dev, so a message issued after a non-final unbind (or to a socket that failed to probe on a multi-socket system, whose array stays alive and whose remove() is never called) cannot reach the mailbox that devres is about to unmap. Two lifetime fixes fall out of the array persisting across a non-final unbind: - hsmp_get_tbl_dram_base() iounmap()s any stale metric_tbl_addr before remapping, so a rebind does not leak one mapping per cycle. It runs during (re)probe before the metric sysfs attribute is exposed, so no reader can be using the old mapping. - The ACPI path registers /dev/hsmp unparented by passing NULL to hsmp_misc_register(). Its per-socket devices can be unbound individually and out of order and the misc device outlives all but the last of them, so parenting it to one socket's device would leave a dangling parent. hsmp_misc_register() now takes the parent from its caller, so the platform driver keeps parenting /dev/hsmp to its single device. hsmp_sock_rwsem is held for write across probe and remove, so the release and probe-failure cleanup run with it already held; an upcoming change adds its read side so the same lock also drains the data plane. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-6-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 131 +++++++++++++++++++++++---- drivers/platform/x86/amd/hsmp/hsmp.c | 20 ++++ drivers/platform/x86/amd/hsmp/hsmp.h | 1 - 3 files changed, 132 insertions(+), 20 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index a092d7589bcb..24f54dc7254f 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -19,11 +19,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -42,6 +44,17 @@ static struct hsmp_plat_device *hsmp_pdev; +/* + * Tracks the ACPI socket platform devices that share the socket array and the + * /dev/hsmp misc device. The first probe initializes it, each further probe + * takes a reference and every remove (or probe failure) drops one; the last + * put frees the shared state via hsmp_acpi_sock_release(). All get/put run + * under hsmp_sock_rwsem held for write, so the counting is already serialized + * and the atomic in kref is not strictly needed; kref is used for the clearer + * get/put interface and its release callback. + */ +static struct kref hsmp_acpi_sock_kref; + struct hsmp_sys_attr { struct device_attribute dattr; u32 msg_id; @@ -611,6 +624,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); +/* + * kref release: tear down the shared ACPI socket state once the last socket + * drops its reference. Deregister /dev/hsmp if it was registered, unmap any + * metric-table DRAM, destroy the per-socket mutexes and free the socket array. + * + * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove + * and probe-failure paths both drop their reference under that lock. The write + * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox + * and freeing the array cannot race the data plane. + */ +static void hsmp_acpi_sock_release(struct kref *kref) +{ + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) + hsmp_misc_deregister(); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + kfree(hsmp_pdev->sock); + hsmp_pdev->sock = NULL; + hsmp_pdev->num_sockets = 0; + hsmp_pdev->proto_ver = 0; +} + +/** + * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. + * @dev: ACPI companion device whose probe failed. + * + * This device already took a reference on entry to hsmp_acpi_probe(), so clear + * its sock->dev and drop that reference; the shared state is released if it was + * the last one. + * + * Clearing sock->dev matters on multi-socket systems: when a non-first socket + * fails, the array stays alive (owned by an already-probed socket) and + * remove() is never called for this device, yet devres unmaps its mailbox once + * probe() returns. Without clearing dev, a later message to this index would + * pass every gate in hsmp_send_message() and reach the unmapped mailbox. + * + * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. + * + * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. + */ +static void hsmp_acpi_probe_failure_cleanup(struct device *dev) +{ + struct hsmp_socket *sock = dev_get_drvdata(dev); + + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (sock) + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); +} + static int hsmp_acpi_probe(struct platform_device *pdev) { int ret; @@ -620,43 +687,60 @@ static int hsmp_acpi_probe(struct platform_device *pdev) return -ENOMEM; /* - * Multiple ACPI socket devices probe in parallel, but the is_probed - * handshake and the one-time socket-array allocation below must run - * exactly once. Serialize the whole bring-up against concurrent - * probe/remove by holding the socket rwsem for write. + * Multiple ACPI socket devices probe in parallel, but the one-time + * socket-array allocation and /dev/hsmp registration below must run + * exactly once. Hold the socket rwsem for write across the whole + * bring-up so it cannot race a concurrent probe or remove, and so the + * probe-failure teardown drains the data plane. */ guard(rwsem_write)(&hsmp_sock_rwsem); - if (!hsmp_pdev->is_probed) { + if (!hsmp_pdev->sock) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { dev_err(&pdev->dev, "No CPU sockets detected\n"); return -ENODEV; } - hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, - sizeof(*hsmp_pdev->sock), - GFP_KERNEL); + hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, + sizeof(*hsmp_pdev->sock), + GFP_KERNEL); if (!hsmp_pdev->sock) return -ENOMEM; hsmp_init_metric_read_locks(hsmp_pdev); + kref_init(&hsmp_acpi_sock_kref); + } else { + kref_get(&hsmp_acpi_sock_kref); } + /* + * This socket now holds a reference (kref_init on the first socket, + * kref_get afterwards). Every failure path below drops it via + * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to + * hsmp_acpi_remove(). + */ ret = init_acpi(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - if (!hsmp_pdev->is_probed) { - ret = hsmp_misc_register(&pdev->dev); + if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { + /* + * Register /dev/hsmp unparented. It is a singleton shared by all + * ACPI sockets and outlives all but the last of them, so + * parenting it to this socket's device would leave a dangling + * parent once that socket is unbound. + */ + ret = hsmp_misc_register(NULL); if (ret) { dev_err(&pdev->dev, "Failed to register misc device\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - hsmp_pdev->is_probed = true; - dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); + dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); } return 0; @@ -664,17 +748,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) static void hsmp_acpi_remove(struct platform_device *pdev) { + struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); + + /* + * Serialize the kref_put() and any release it triggers against a + * concurrent probe, and drain the data plane for the whole + * teardown: this covers the per-socket unbind, whose mailbox devres + * unmaps once we return, and the last unbind that frees the socket + * array in hsmp_acpi_sock_release(). + */ guard(rwsem_write)(&hsmp_sock_rwsem); /* - * We register only one misc_device even on multi-socket system. - * So, deregister should happen only once. + * Clear this socket's dev so hsmp_send_message() rejects it before + * devres unmaps the mailbox. On a non-final unbind the socket array + * stays alive, so without this a later message to this index would + * reach an unmapped iomem region. */ - if (hsmp_pdev->is_probed) { - hsmp_misc_deregister(); - hsmp_destroy_metric_read_locks(hsmp_pdev); - hsmp_pdev->is_probed = false; - } + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); } static struct platform_driver amd_hsmp_driver = { diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 584fd9b1d31f..967307abe641 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -491,6 +491,18 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } + /* + * The ACPI socket array is shared across sockets and outlives a + * per-socket unbind, so metric_tbl_addr may hold a mapping from an + * earlier bind of this socket. Unmap it before remapping so an + * unbind/rebind cycle does not leak a metric-table mapping. This runs + * during probe before the metric sysfs attribute is exposed, so no + * reader can be using it. + */ + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); @@ -528,6 +540,14 @@ int hsmp_misc_register(struct device *dev) hsmp_pdev.mdev.name = HSMP_CDEV_NAME; hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; hsmp_pdev.mdev.fops = &hsmp_fops; + /* + * The caller chooses the parent. The platform driver has a single + * device whose lifetime matches /dev/hsmp and parents it there. The + * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by + * per-socket devices that can be unbound individually and out of order, + * so parenting it to one would leave it attached to an already-removed + * device. + */ hsmp_pdev.mdev.parent = dev; hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; hsmp_pdev.mdev.mode = 0644; diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index ec92c2a429bb..45dab9253c13 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -58,7 +58,6 @@ struct hsmp_plat_device { struct hsmp_socket *sock; u32 proto_ver; u16 num_sockets; - bool is_probed; }; int hsmp_cache_proto_ver(u16 sock_ind); From c7acedb2db001160b1ec41cdcc759dd664150666 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Thu, 23 Jul 2026 15:16:56 +0530 Subject: [PATCH 092/152] platform/x86/amd/hsmp: Serialize the data plane against socket teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this change the HSMP data plane runs without any coordination with driver teardown: open /dev/hsmp fds and hwmon sysfs reads call hsmp_send_message() while probe and remove bring sockets up and down. misc_deregister() does not drain already-open fds, so an in-flight message can race a concurrent unbind and touch a freed socket array or an unmapped mailbox. Add the read side of hsmp_sock_rwsem to the data plane. Split the message send into hsmp_send_message_locked(), which does the bounds check and MMIO access and asserts the rwsem is held, and hsmp_send_message(), which wraps it in guard(rwsem_read). Probe and remove hold the rwsem for write, so they drain in-flight messages and keep new ones out while they tear a socket down. The probe-time senders run under the probe write lock and so must not take the rwsem again: route hsmp_test(), hsmp_cache_proto_ver() and hsmp_get_tbl_dram_base() through hsmp_send_message_locked() to avoid recursive locking. A single rwsem therefore covers both the data plane and the probe/remove handshake, with no separate probe lock: - acpi.c already holds it for write across probe for the socket-array and misc-registration handshake, so the mailbox handshake now nests under that same lock. - plat.c takes it for write around init_platform_device(). It is not held across devm_add_action_or_reset() so the release action, which also takes it for write, cannot deadlock if that registration fails. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-7-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.c | 41 ++++++++++++++++++++++------ drivers/platform/x86/amd/hsmp/hsmp.h | 4 +-- drivers/platform/x86/amd/hsmp/plat.c | 24 +++++++++++++--- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 967307abe641..a457831c9c6b 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -45,9 +45,12 @@ static struct hsmp_plat_device hsmp_pdev; /* - * Serializes AMD HSMP socket bring-up and teardown: ACPI probe and remove take - * it for write so concurrent per-socket probes cannot race the is_probed - * handshake or the one-time socket-array allocation. + * Gates the AMD HSMP data plane against socket bring-up and teardown. + * + * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads + * run concurrently. Probe and remove take it for write: probe brings sockets + * up (running the mailbox handshake via hsmp_send_message_locked()) and remove + * tears them down, both excluding and draining the data plane. */ DECLARE_RWSEM(hsmp_sock_rwsem); EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); @@ -211,12 +214,20 @@ static int validate_message(struct hsmp_message *msg) return 0; } -int hsmp_send_message(struct hsmp_message *msg) +/* + * Core message send. The caller must hold hsmp_sock_rwsem: the data plane + * takes it for read so many messages run concurrently, while the probe-time + * senders run under the write lock taken by probe. Holding it here serializes + * every message against socket teardown, which also holds it for write. + */ +static int hsmp_send_message_locked(struct hsmp_message *msg) { struct hsmp_socket *sock; unsigned int sock_ind; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!msg) return -EINVAL; ret = validate_message(msg); @@ -243,7 +254,8 @@ int hsmp_send_message(struct hsmp_message *msg) * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and * the semaphore are visible. * - * Pairs with smp_store_release(&sock->dev) in hsmp_parse_acpi_table(). + * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) + * in hsmp_parse_acpi_table(). */ if (!smp_load_acquire(&sock->dev)) return -ENODEV; @@ -258,6 +270,19 @@ int hsmp_send_message(struct hsmp_message *msg) return ret; } + +int hsmp_send_message(struct hsmp_message *msg) +{ + /* + * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue + * messages from here. Take hsmp_sock_rwsem for read so messages run + * concurrently with each other but are drained and kept out while + * probe/remove hold it for write to tear a socket down. + */ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_send_message_locked(msg); +} EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) @@ -298,7 +323,7 @@ int hsmp_test(u16 sock_ind, u32 value) msg.args[0] = value; msg.sock_ind = sock_ind; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -478,7 +503,7 @@ int hsmp_get_tbl_dram_base(u16 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; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -521,7 +546,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (!ret) hsmp_pdev.proto_ver = msg.args[0]; diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 45dab9253c13..cfd1a8cbd459 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -79,8 +79,8 @@ static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); /* - * Serializes HSMP socket bring-up and teardown. ACPI probe and remove take it - * for write. + * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and + * remove take it for write to bring sockets up and tear them down. */ extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index 7a16d1ab463b..e9b2b809c0f5 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -13,12 +13,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -204,15 +206,20 @@ static int init_platform_device(struct device *dev) /* * The socket array is devm-managed and freed by the driver core, but the * metric-table DRAM regions are mapped with plain ioremap() during probe and - * are therefore not covered by devres. + * the per-socket mutexes need an explicit mutex_destroy(), neither of which + * devres covers. * - * Drop those mappings from a devres action so both remove and probe failure - * unmap them exactly once, before the socket array they refer to is freed. + * Take the data-plane rwsem for write to drain any in-flight + * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop + * the global socket pointer, all before devres frees the array. Registered as + * a devres action so it runs on both remove and probe failure. */ static void hsmp_pltdrv_release(void *data) { + guard(rwsem_write)(&hsmp_sock_rwsem); hsmp_unmap_metric_tbls(hsmp_pdev); hsmp_destroy_metric_read_locks(hsmp_pdev); + hsmp_pdev->sock = NULL; } static int hsmp_pltdrv_probe(struct platform_device *pdev) @@ -231,7 +238,16 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (ret) return ret; - ret = init_platform_device(&pdev->dev); + /* + * init_platform_device() runs the mailbox handshake via the probe-only + * senders, which issue messages through hsmp_send_message_locked() and + * so require hsmp_sock_rwsem held. Hold it for write, matching probe's + * role as a socket bring-up path. The lock is not held across + * devm_add_action_or_reset() above so the release action, which also + * takes it for write, does not deadlock if that registration fails. + */ + scoped_guard(rwsem_write, &hsmp_sock_rwsem) + ret = init_platform_device(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); return ret; From 9cef693bce96bb4c6952f48d855284cf7fa4f367 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:51 -0500 Subject: [PATCH 093/152] platform/x86/amd/pmc: Restore msg_port on amd_stb_s2d_init() error paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev->msg_port is switched to MSG_PORT_S2D before issuing the S2D SMU commands but is only restored to MSG_PORT_PMC on the success path. The early "return -EIO" and "return -ENOMEM" leave the port stuck on MSG_PORT_S2D, so all subsequent SMU communication - including the s2idle prepare/restore handlers - is directed at the wrong mailbox. Consolidate the exit path through a single label so the message port is always restored. Fixes: 3d7d407dfb05 ("platform/x86: amd-pmc: Add support for AMD Spill to DRAM STB feature") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-2-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/mp1_stb.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c index 753d630f3283..83645c0a8d78 100644 --- a/drivers/platform/x86/amd/pmc/mp1_stb.c +++ b/drivers/platform/x86/amd/pmc/mp1_stb.c @@ -289,7 +289,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) u32 phys_addr_low, phys_addr_hi; u64 stb_phys_addr; u32 size = 0; - int ret; + int ret = 0; if (!enable_stb) return 0; @@ -307,8 +307,10 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) dev->msg_port = MSG_PORT_S2D; amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); - if (size != S2D_TELEMETRY_BYTES_MAX) - return -EIO; + if (size != S2D_TELEMETRY_BYTES_MAX) { + ret = -EIO; + goto out; + } /* Get DRAM size */ ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true); @@ -321,12 +323,16 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); - /* Clear msg_port for other SMU operation */ - dev->msg_port = MSG_PORT_PMC; - dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); - if (!dev->stb_virt_addr) - return -ENOMEM; + if (!dev->stb_virt_addr) { + ret = -ENOMEM; + goto out; + } - return 0; + ret = 0; + +out: + /* Restore the default message port for subsequent SMU operations */ + dev->msg_port = MSG_PORT_PMC; + return ret; } From cbb32ff92f8a62212e0f7384b1de986ced92082b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:52 -0500 Subject: [PATCH 094/152] platform/x86/amd/pmc: Fix msg_port restoration in amd_stb_debugfs_open_v2() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_stb_debugfs_open_v2() switches dev->msg_port to MSG_PORT_S2D to query S2D telemetry but only restores it to MSG_PORT_PMC on one path. The early return on the dump_custom_stb path (and the error/allocation returns) leave the port stuck on MSG_PORT_S2D, so subsequent SMU communication - including the s2idle prepare/restore handlers - is directed at the wrong mailbox. Consolidate the exit path through a single label so the message port is always restored, mirroring the fix in amd_stb_s2d_init(). Reported-by: sashiko.dev Link: https://sashiko.dev/#/patchset/20260717162023.956346-1-mario.limonciello%40amd.com Fixes: 2851f4f8ed4e ("platform/x86/amd/pmc: Define enum for S2D/PMC msg_port and add helper function") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-3-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/mp1_stb.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c index 83645c0a8d78..90b8a5cebfa8 100644 --- a/drivers/platform/x86/amd/pmc/mp1_stb.c +++ b/drivers/platform/x86/amd/pmc/mp1_stb.c @@ -157,7 +157,7 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) struct amd_pmc_dev *dev = filp->f_inode->i_private; u32 fsize, num_samples, val, stb_rdptr_offset = 0; struct amd_stb_v2_data *stb_data_arr; - int ret; + int ret = 0; /* Write dummy postcode while reading the STB buffer */ ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC); @@ -176,22 +176,24 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) * the enhanced dram size. Note that we land here only for the * platforms that support enhanced dram size reporting. */ - if (dump_custom_stb) - return amd_stb_handle_efr(filp); + if (dump_custom_stb) { + ret = amd_stb_handle_efr(filp); + goto out; + } /* Get the num_samples to calculate the last push location */ ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true); - /* Clear msg_port for other SMU operation */ - dev->msg_port = MSG_PORT_PMC; if (ret) { dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret); - return ret; + goto out; } fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX); stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize); - if (!stb_data_arr) - return -ENOMEM; + if (!stb_data_arr) { + ret = -ENOMEM; + goto out; + } stb_data_arr->size = fsize; @@ -214,7 +216,10 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) filp->private_data = stb_data_arr; - return 0; +out: + /* Restore the default message port for subsequent SMU operations */ + dev->msg_port = MSG_PORT_PMC; + return ret; } static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, From 0225c1d637687b03726f00ac65b6def843d2c464 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:53 -0500 Subject: [PATCH 095/152] platform/x86/amd/pmc: Propagate SMU errors and validate S2D address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_stb_s2d_init() discards the return value of several S2D SMU commands. When the SMU refuses a command (e.g. "SMU cmd failed. err: 0xff") the failure is only noticed indirectly - if at all - and reported as -EIO, masking the real error. More seriously, the S2D_PHYS_ADDR_LOW/HIGH return values are ignored, so on failure phys_addr_low/hi are left uninitialised and the assembled address is passed straight to devm_ioremap(). When the SMU leaves them at zero this maps physical address 0 and trips the ioremap-on-RAM warning: amd_pmc AMDI000B:00: SMU cmd failed. err: 0xff ioremap on RAM at 0x0000000000000000 - 0x0000000000ffffff WARNING: CPU: 13 PID: 4592 at arch/x86/mm/ioremap.c:... Check the return value of each SMU command and propagate it, and reject a zero physical address before calling devm_ioremap(). Reported-by: Francis De Brabandere Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221759 Tested-by: Francis De Brabandere Fixes: 3d7d407dfb05 ("platform/x86: amd-pmc: Add support for AMD Spill to DRAM STB feature") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-4-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/mp1_stb.c | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c index 90b8a5cebfa8..1ec0e599df7f 100644 --- a/drivers/platform/x86/amd/pmc/mp1_stb.c +++ b/drivers/platform/x86/amd/pmc/mp1_stb.c @@ -311,30 +311,39 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) /* Spill to DRAM feature uses separate SMU message port */ dev->msg_port = MSG_PORT_S2D; - amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); + ret = amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; if (size != S2D_TELEMETRY_BYTES_MAX) { ret = -EIO; goto out; } - /* Get DRAM size */ - ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true); - if (ret || !dev->dram_size) + /* Get DRAM size; fall back to the default if the query fails */ + if (amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true) || + !dev->dram_size) dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX; /* Get STB DRAM address */ - amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true); - amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true); + ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, + dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; + ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, + dev->stb_arg.s2d_msg_id, true); + if (ret) + goto out; stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); - - dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); - if (!dev->stb_virt_addr) { - ret = -ENOMEM; + if (!stb_phys_addr) { + dev_err(dev->dev, "S2D phys addr query returned invalid address\n"); + ret = -ENXIO; goto out; } - ret = 0; + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); + if (!dev->stb_virt_addr) + ret = -ENOMEM; out: /* Restore the default message port for subsequent SMU operations */ From 34d145254ca655ead49d00025981f2f5b200a62f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:54 -0500 Subject: [PATCH 096/152] platform/x86/amd/pmc: Only expose stb_read after telemetry buffer is mapped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_stb_s2d_init() creates the v2 "stb_read" debugfs node before mapping the telemetry buffer into dev->stb_virt_addr, leaving a window during probe where a read faults on a NULL dev->stb_virt_addr in amd_stb_debugfs_open_v2()/amd_stb_handle_efr(). This becomes trivial to hit once a failed STB init no longer aborts probe (next patch), which leaves the node registered with a NULL buffer. Create it only after dev->stb_virt_addr is mapped. Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-5-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/mp1_stb.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c index 1ec0e599df7f..8a33e75b71c3 100644 --- a/drivers/platform/x86/amd/pmc/mp1_stb.c +++ b/drivers/platform/x86/amd/pmc/mp1_stb.c @@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) if (!enable_stb) return 0; - if (amd_is_stb_supported(dev)) { - debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, - &amd_stb_debugfs_fops_v2); - } else { + if (!amd_is_stb_supported(dev)) { debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, &amd_stb_debugfs_fops); return 0; @@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) } dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); - if (!dev->stb_virt_addr) + if (!dev->stb_virt_addr) { ret = -ENOMEM; + goto out; + } + + /* + * Only expose stb_read once the buffer is mapped; otherwise a read + * faults on a NULL dev->stb_virt_addr, now that a failed STB init no + * longer aborts probe. + */ + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops_v2); out: /* Restore the default message port for subsequent SMU operations */ From 76f650a76d6a36a4bee79d94db90a0e935a95477 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:55 -0500 Subject: [PATCH 097/152] platform/x86/amd/pmc: Fix LPS0 and debugfs leaks when STB init fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_pmc_probe() registers the LPS0 s2idle handler with acpi_register_lps0_dev() and creates the driver's debugfs directory before calling amd_stb_s2d_init(), which is the last step in probe that can fail. When amd_stb_s2d_init() fails (for example the S2D telemetry region cannot be ioremapped on a long-running system, or the SMU rejects the S2D setup) the error path only calls pci_dev_put() and returns. This leaves amd_pmc_s2idle_dev_ops on the global lps0_s2idle_devops_head list and leaks the debugfs directory, while the devm-managed resources backing the handler are torn down. Reloading the module then walks the corrupted list in acpi_register_lps0_dev() and hits: list_add corruption. next->prev should be prev, but was NULL. kernel BUG at lib/list_debug.c:29! acpi_register_lps0_dev+0x44/0x80 amd_pmc_probe+0x224/0x380 [amd_pmc] platform_probe+0x67/0x90 Even without a reload, the stale registration means the next s2idle transition calls into torn-down driver state. Unwind the debugfs directory and the LPS0 registration on the amd_stb_s2d_init() error path. acpi_unregister_lps0_dev() is safe to call unconditionally here: it is guarded on the same conditions as acpi_register_lps0_dev(), which is exactly what amd_pmc_remove() already relies on. Reported-by: Francis De Brabandere Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221759 Tested-by: Francis De Brabandere Fixes: 83ad6974dd3b ("platform/x86/amd/pmc: Move STB block into amd_pmc_s2d_init()") Cc: stable@vger.kernel.org Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-6-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index d50ea62fa2f3..630a664bdd2f 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -919,13 +919,17 @@ static int amd_pmc_probe(struct platform_device *pdev) amd_pmc_dbgfs_register(dev); err = amd_stb_s2d_init(dev); if (err) - goto err_pci_dev_put; + goto err_dbgfs_unregister; if (IS_ENABLED(CONFIG_AMD_MP2_STB)) amd_mp2_stb_init(dev); pm_report_max_hw_sleep(U64_MAX); return 0; +err_dbgfs_unregister: + amd_pmc_dbgfs_unregister(dev); + if (IS_ENABLED(CONFIG_SUSPEND)) + acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); err_pci_dev_put: pci_dev_put(rdev); return err; From 29412292e4fbe6cf4dfd36711fb056ab2b6c52b4 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 21 Jul 2026 13:17:56 -0500 Subject: [PATCH 098/152] platform/x86/amd/pmc: Do not fail probe when STB init fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit STB (Spill to DRAM) is an optional debugging facility that is only enabled through the enable_stb module parameter. On some platforms the SMU refuses the S2D setup outright, and on long-running systems the large telemetry region can fail to ioremap. In either case amd_stb_s2d_init() returns an error and, because probe treated that as fatal, the entire PMC driver failed to load - silently disabling s0i3 support even though STB is only a debug aid. Downgrade the failure to a warning and continue probing so that s0i3 support via the LPS0 handler no longer depends on an optional debug feature. Since probe no longer aborts on this path, the LPS0 and debugfs unwinding added by the earlier fix in this series becomes unreachable and is removed. Reported-by: Francis De Brabandere Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221759 Tested-by: Francis De Brabandere Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260721181756.143084-7-mario.limonciello@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 630a664bdd2f..0e013402c1c6 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -917,19 +917,22 @@ static int amd_pmc_probe(struct platform_device *pdev) } amd_pmc_dbgfs_register(dev); + + /* + * STB is an optional debug facility (enable_stb); a failure to set it + * up must not stop the rest of the driver - most importantly the s0i3 + * LPS0 handler - from working, so treat it as non-fatal. + */ err = amd_stb_s2d_init(dev); if (err) - goto err_dbgfs_unregister; + dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", + err); if (IS_ENABLED(CONFIG_AMD_MP2_STB)) amd_mp2_stb_init(dev); pm_report_max_hw_sleep(U64_MAX); return 0; -err_dbgfs_unregister: - amd_pmc_dbgfs_unregister(dev); - if (IS_ENABLED(CONFIG_SUSPEND)) - acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); err_pci_dev_put: pci_dev_put(rdev); return err; From 41427211f663c6eb36986ca2ea68d7e87aac52e9 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 20 Jul 2026 15:19:19 +0200 Subject: [PATCH 099/152] platform/x86: dell-smbios-wmi: Fix chardev resource management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When unbinding the WMI driver while a userspace application has an open file descriptor for the character device, a UAF occurs: KASAN: slab-use-after-free in _copy_to_user from platform/x86/dell-smbios-wmi The reason for this is that even after calling misc_deregister(), userspace appications can still call read() and/or ioctl() on open file descriptors associated with the already unregistered character device. This causes a UAF by attempting to access the already freed state container of the WMI driver. Fix this by no longer storing the state container inside filp->private_data. Instead retrieve the state container using get_first_smbios_priv() and return -ENODEV if the state container does not exist anymore. Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/platform-driver-x86/178144969601.60470.13396800403157907003@gmail.com/ Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260720131921.368000-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-smbios-wmi.c | 69 +++++++++++---------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 64d0871b706e..231fc90878bc 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -6,6 +6,7 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -13,14 +14,13 @@ #include #include #include +#include #include #include #include #include "dell-smbios.h" #include "dell-wmi-descriptor.h" -static DEFINE_MUTEX(call_mutex); -static DEFINE_MUTEX(list_mutex); static int wmi_supported; struct misc_bios_flags_structure { @@ -32,6 +32,7 @@ struct misc_bios_flags_structure { #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" struct wmi_smbios_priv { + struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ struct dell_wmi_smbios_buffer *buf; struct list_head list; struct wmi_device *wdev; @@ -39,6 +40,8 @@ struct wmi_smbios_priv { u64 req_buf_size; struct miscdevice char_dev; }; + +static DECLARE_RWSEM(list_lock); /* Protects access to wmi_list */ static LIST_HEAD(wmi_list); static inline struct wmi_smbios_priv *get_first_smbios_priv(void) @@ -87,40 +90,35 @@ static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) size_t size; int ret; - mutex_lock(&call_mutex); + guard(rwsem_read)(&list_lock); + priv = get_first_smbios_priv(); - if (!priv) { - ret = -ENODEV; - goto out_wmi_call; - } + if (!priv) + return -ENODEV; size = sizeof(struct calling_interface_buffer); difference = priv->req_buf_size - sizeof(u64) - size; + guard(mutex)(&priv->call_lock); + memset(&priv->buf->ext, 0, difference); memcpy(&priv->buf->std, buffer, size); ret = run_smbios_call(priv->wdev); memcpy(buffer, &priv->buf->std, size); -out_wmi_call: - mutex_unlock(&call_mutex); return ret; } -static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) -{ - struct wmi_smbios_priv *priv; - - priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); - filp->private_data = priv; - - return nonseekable_open(inode, filp); -} - static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) { - struct wmi_smbios_priv *priv = filp->private_data; + struct wmi_smbios_priv *priv; + + guard(rwsem_read)(&list_lock); + + priv = get_first_smbios_priv(); + if (!priv) + return -ENODEV; return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, sizeof(priv->req_buf_size)); @@ -167,22 +165,24 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; - struct wmi_smbios_priv *priv = filp->private_data; - long ret; + struct wmi_smbios_priv *priv; if (cmd != DELL_WMI_SMBIOS_CMD) return -ENOIOCTLCMD; - mutex_lock(&call_mutex); - ret = dell_smbios_wmi_do_ioctl(priv, input); - mutex_unlock(&call_mutex); + guard(rwsem_read)(&list_lock); - return ret; + priv = get_first_smbios_priv(); + if (!priv) + return -ENODEV; + + guard(mutex)(&priv->call_lock); + + return dell_smbios_wmi_do_ioctl(priv, input); } static const struct file_operations dell_smbios_wmi_fops = { .owner = THIS_MODULE, - .open = dell_smbios_wmi_open, .read = dell_smbios_wmi_read, .unlocked_ioctl = dell_smbios_wmi_ioctl, .compat_ioctl = compat_ptr_ioctl, @@ -254,6 +254,10 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) if (!priv->buf) return -ENOMEM; + ret = devm_mutex_init(&wdev->dev, &priv->call_lock); + if (ret) + return ret; + ret = dell_smbios_wmi_register_chardev(priv); if (ret) return ret; @@ -262,9 +266,8 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) if (ret) return ret; - mutex_lock(&list_mutex); + guard(rwsem_write)(&list_lock); list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); return 0; } @@ -273,12 +276,10 @@ static void dell_smbios_wmi_remove(struct wmi_device *wdev) { struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); - mutex_lock(&call_mutex); - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); dell_smbios_unregister_device(&wdev->dev); - mutex_unlock(&call_mutex); + + guard(rwsem_write)(&list_lock); + list_del(&priv->list); } static const struct wmi_device_id dell_smbios_wmi_id_table[] = { From 08d31d42b054eae62ff57bfd88b639f111b2b8fa Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 20 Jul 2026 15:19:20 +0200 Subject: [PATCH 100/152] platform/x86: dell-smbios: Pass device to callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WMI SMBIOS backend needs to access the its driver state container when performing SMBIOS calls. Pass the device associated with a given backend to the callback function to allow the WMI backend to retrieve said state container in a more straightforward manner. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260720131921.368000-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-smbios-base.c | 6 +++--- drivers/platform/x86/dell/dell-smbios-smm.c | 8 ++++---- drivers/platform/x86/dell/dell-smbios-wmi.c | 10 ++-------- drivers/platform/x86/dell/dell-smbios.h | 4 +++- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c index 4b7c54b76228..d8c55ec6acd4 100644 --- a/drivers/platform/x86/dell/dell-smbios-base.c +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -40,7 +40,7 @@ struct smbios_device { struct list_head list; struct device *device; int priority; - int (*call_fn)(struct calling_interface_buffer *arg); + smbios_callback_fn_t call_fn; }; struct smbios_call { @@ -146,7 +146,7 @@ int dell_smbios_error(int value) } EXPORT_SYMBOL_GPL(dell_smbios_error); -int dell_smbios_register_device(struct device *d, int priority, void *call_fn) +int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) { struct smbios_device *priv; @@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) goto out_smbios_call; } - ret = selected->call_fn(buffer); + ret = selected->call_fn(selected->device, buffer); out_smbios_call: mutex_unlock(&smbios_mutex); diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c index 7055e2c40f34..fc4a674c692e 100644 --- a/drivers/platform/x86/dell/dell-smbios-smm.c +++ b/drivers/platform/x86/dell/dell-smbios-smm.c @@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) } } -static int dell_smbios_smm_call(struct calling_interface_buffer *input) +static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) { struct smi_cmd command; size_t size; @@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) } /* When enabled this indicates that SMM won't work */ -static bool test_wsmt_enabled(void) +static bool test_wsmt_enabled(struct device *dev) { struct calling_interface_token *wsmt; @@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) memset(buffer, 0, sizeof(struct calling_interface_buffer)); buffer->input[0] = wsmt->location; buffer->output[0] = 99; - dell_smbios_smm_call(buffer); + dell_smbios_smm_call(dev, buffer); if (buffer->output[0] == 99) return true; @@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) dmi_walk(find_cmd_address, NULL); - if (test_wsmt_enabled()) { + if (test_wsmt_enabled(&platform_device->dev)) { pr_debug("Disabling due to WSMT enabled\n"); ret = -ENODEV; goto fail_wsmt; diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 231fc90878bc..2108de4e48a3 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -83,19 +83,13 @@ static int run_smbios_call(struct wmi_device *wdev) return 0; } -static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) +static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) { - struct wmi_smbios_priv *priv; + struct wmi_smbios_priv *priv = dev_get_drvdata(dev); size_t difference; size_t size; int ret; - guard(rwsem_read)(&list_lock); - - priv = get_first_smbios_priv(); - if (!priv) - return -ENODEV; - size = sizeof(struct calling_interface_buffer); difference = priv->req_buf_size - sizeof(u64) - size; diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h index f421b8533a9e..1e64803bc00d 100644 --- a/drivers/platform/x86/dell/dell-smbios.h +++ b/drivers/platform/x86/dell/dell-smbios.h @@ -47,6 +47,8 @@ struct notifier_block; +typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); + struct calling_interface_token { u16 tokenID; u16 location; @@ -64,7 +66,7 @@ struct calling_interface_structure { struct calling_interface_token tokens[]; } __packed; -int dell_smbios_register_device(struct device *d, int priority, void *call_fn); +int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); void dell_smbios_unregister_device(struct device *d); int dell_smbios_error(int value); From b844001890c5bc15adf3b16c44d99edd2005d393 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 20 Jul 2026 15:19:21 +0200 Subject: [PATCH 101/152] platform/x86: dell-smbios-wmi: Replace global list with single item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There can only exist a single instance of the dell-smbios-wmi driver at the same time because of naming conflicts with the character device ("wmi/dell-smbios"). Having a global list for all instances thus makes no sense. Replace the global list with a single item used by the character device. This simplifies the driver and allows us to mark it as being multi-instance safe. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260720131921.368000-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-smbios-wmi.c | 68 ++++++++++----------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 2108de4e48a3..f9443ddfff55 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -34,22 +33,14 @@ struct misc_bios_flags_structure { struct wmi_smbios_priv { struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ struct dell_wmi_smbios_buffer *buf; - struct list_head list; struct wmi_device *wdev; struct device *child; u64 req_buf_size; struct miscdevice char_dev; }; -static DECLARE_RWSEM(list_lock); /* Protects access to wmi_list */ -static LIST_HEAD(wmi_list); - -static inline struct wmi_smbios_priv *get_first_smbios_priv(void) -{ - return list_first_entry_or_null(&wmi_list, - struct wmi_smbios_priv, - list); -} +static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ +static struct wmi_smbios_priv *chardev_priv; static int run_smbios_call(struct wmi_device *wdev) { @@ -106,16 +97,13 @@ static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buf static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) { - struct wmi_smbios_priv *priv; + guard(rwsem_read)(&chardev_lock); - guard(rwsem_read)(&list_lock); - - priv = get_first_smbios_priv(); - if (!priv) + if (!chardev_priv) return -ENODEV; - return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, - sizeof(priv->req_buf_size)); + return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, + sizeof(chardev_priv->req_buf_size)); } static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, @@ -159,20 +147,18 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; - struct wmi_smbios_priv *priv; if (cmd != DELL_WMI_SMBIOS_CMD) return -ENOIOCTLCMD; - guard(rwsem_read)(&list_lock); + guard(rwsem_read)(&chardev_lock); - priv = get_first_smbios_priv(); - if (!priv) + if (!chardev_priv) return -ENODEV; - guard(mutex)(&priv->call_lock); + guard(mutex)(&chardev_priv->call_lock); - return dell_smbios_wmi_do_ioctl(priv, input); + return dell_smbios_wmi_do_ioctl(chardev_priv, input); } static const struct file_operations dell_smbios_wmi_fops = { @@ -189,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) misc_deregister(char_dev); } +static void dell_smbios_wmi_clear_chardev(void *data) +{ + guard(rwsem_write)(&chardev_lock); + + chardev_priv = NULL; +} + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) { int ret; + scoped_guard(rwsem_write, &chardev_lock) { + /* We can only have a single chardev at a time */ + if (chardev_priv) + return -EBUSY; + + chardev_priv = priv; + } + + ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); + if (ret < 0) + return ret; + priv->char_dev.minor = MISC_DYNAMIC_MINOR; priv->char_dev.name = "wmi/dell-smbios"; priv->char_dev.fops = &dell_smbios_wmi_fops; @@ -256,24 +261,12 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) if (ret) return ret; - ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); - if (ret) - return ret; - - guard(rwsem_write)(&list_lock); - list_add_tail(&priv->list, &wmi_list); - - return 0; + return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); } static void dell_smbios_wmi_remove(struct wmi_device *wdev) { - struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); - dell_smbios_unregister_device(&wdev->dev); - - guard(rwsem_write)(&list_lock); - list_del(&priv->list); } static const struct wmi_device_id dell_smbios_wmi_id_table[] = { @@ -311,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { .probe = dell_smbios_wmi_probe, .remove = dell_smbios_wmi_remove, .id_table = dell_smbios_wmi_id_table, + .no_singleton = true, }; int init_dell_smbios_wmi(void) From 946000e1d594a9005e178f4166a09941ad5d454a Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Fri, 24 Jul 2026 17:32:55 +0530 Subject: [PATCH 102/152] platform/x86: hp-wmi: Add OMEN board 8BA9 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 16-wd0xxx (board ID: 8BA9) 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_board_params. Without this entry, platform profile switching is unavailable, preventing fan RPM reporting and controlling. Tested on: HP OMEN 16-wd0012TX DMI Board Name: 8BA9 It has been confirmed that the platform profile is registered successfully, and the fan RPMs are readable and controllable. Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260724120255.49649-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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 808b8f7d0adf..3c737ef3809c 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -255,6 +255,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, + .driver_data = (void *)&omen_v1_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, .driver_data = (void *)&omen_v1_board_params, From e860e561926bf75e323d43fe59a9bf25d2ef33a3 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:28 +0530 Subject: [PATCH 103/152] platform/x86/amd/pmf: Use per-SoC smu_regs struct for SMU mailbox registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Different AMD platforms use varying SMU register layouts for PMF-SMU mailbox communication. The register offsets are currently hardcoded as AMD_PMF_REGISTER_MESSAGE, AMD_PMF_REGISTER_RESPONSE and AMD_PMF_REGISTER_ARGUMENT directly in amd_pmf_send_cmd() and amd_pmf_dump_registers(), making it difficult to support platforms that use a different mailbox register layout without scattering per-platform conditionals across the send path. Introduce struct amd_pmf_smu_regs to capture the SoC-specific SMU mailbox register offsets (msg_reg, resp_reg, arg_reg) and add a pointer to it in struct amd_pmf_dev. RMB, PS, 1AH_M20H and 1AH_M60H all share the same legacy register layout and point to a single shared amd_pmf_smu_regs_v1 instance, avoiding redundant struct definitions. Convert the pmf_pci_ids[] table from PCI_DEVICE() to PCI_DEVICE_DATA(), embedding the smu_regs pointer directly as driver_data. Introduce amd_pmf_get_smu_mb_offset() which resolves the matching PCI entry via pci_match_id() at probe time and assigns driver_data to dev->smu_regs. Update all SMU register accesses in amd_pmf_send_cmd() and amd_pmf_dump_registers() to go through dev->smu_regs. Remove the hardcoded register offset references from the send path. New platform support requires only a new smu_regs instance and a corresponding PCI_DEVICE_DATA() entry. No functional changes for existing platforms. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-2-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/core.c | 51 +++++++++++++++++++++-------- drivers/platform/x86/amd/pmf/pmf.h | 12 +++++++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index 58d86b4c2828..3018c861776b 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -176,13 +176,13 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) { u32 value; - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); + value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); - value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); + value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); } @@ -208,7 +208,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 guard(mutex)(&dev->lock); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, val, val != 0, PMF_MSG_DELAY_MIN_US, PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -217,16 +217,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 } /* Write zero to response register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); + amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); /* Write argument into argument register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); + amd_pmf_reg_write(dev, dev->smu_regs->arg_reg, arg); /* Write message ID to message ID register */ - amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); + amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, val, val != 0, PMF_MSG_DELAY_MIN_US, PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -239,7 +239,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 if (get) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); + *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg); } break; case AMD_PMF_RESULT_CMD_REJECT_BUSY: @@ -262,11 +262,18 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 return rc; } +/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ +static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { + .msg_reg = AMD_PMF_REGISTER_MESSAGE, + .resp_reg = AMD_PMF_REGISTER_RESPONSE, + .arg_reg = AMD_PMF_REGISTER_ARGUMENT, +}; + static const struct pci_device_id pmf_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, { } }; @@ -536,6 +543,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) } } +static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) +{ + const struct pci_device_id *id; + + id = pci_match_id(pmf_pci_ids, rdev); + if (!id) + return -ENODEV; + + pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; + + return 0; +} + static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0100", 0x100}, {"AMDI0102", 0}, @@ -624,6 +644,11 @@ static int amd_pmf_probe(struct platform_device *pdev) if (err) return err; + /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ + err = amd_pmf_get_smu_mb_offset(dev, rdev); + if (err) + return err; + apmf_acpi_init(dev); platform_set_drvdata(pdev, dev); amd_pmf_dbgfs_register(dev); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 752fa5dd2267..7a8fd9d399de 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -29,6 +29,10 @@ #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 +/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ +#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB +#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS + struct cookie_header { u32 sign; u32 length; @@ -392,6 +396,13 @@ struct pmf_cbi_ring_buffer { int tail; }; +/* SoC-specific SMU mailbox register offsets */ +struct amd_pmf_smu_regs { + u32 msg_reg; + u32 resp_reg; + u32 arg_reg; +}; + struct amd_pmf_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -444,6 +455,7 @@ struct amd_pmf_dev { struct mutex cbi_mutex; /* Protects ring buffer access */ struct mutex metrics_mutex; u32 bios_output[BIOS_OUTPUT_MAX]; + const struct amd_pmf_smu_regs *smu_regs; }; struct apmf_sps_prop_granular_v2 { From 2f9db5881fb37e5723bd88f939c7bffb8e7789a3 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:29 +0530 Subject: [PATCH 104/152] platform/x86/amd/pmf: Add 1AH_M80H device IDs and extended SMU mailbox registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PCI device ID (0x115b) and ACPI ID (AMDI0109) to enable PMF driver support for the AMD 1AH_M80H (Family 1AH Model 80H). The 1AH_M80H platform introduces an extended SMU mailbox interface that uses three argument registers instead of the single register used by earlier platforms. Define five new register offsets for the 1AH_M80H mailbox: message, response and three argument registers. The extended argument registers are required because 1AH_M80H exposes metrics through a firmware managed DRAM region. The GET_METRICS_TABLE_DRAM_ADDR command returns a 64-bit physical address split across arg_reg[0] (low 32-bit) and arg_reg[1] (high 32-bit), with the metrics table size in arg_reg[2]. Define amd_pmf_smu_regs_v2 to capture this extended register layout and register it in pmf_pci_ids[] via PCI_DEVICE_DATA(), keeping the existing amd_pmf_smu_regs_v1 shared instance for all prior platforms unchanged. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-3-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/core.c | 28 ++++++++++++++++++++++++---- drivers/platform/x86/amd/pmf/pmf.h | 3 ++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index 3018c861776b..0f85ac0ba803 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -26,6 +26,13 @@ #define AMD_PMF_REGISTER_RESPONSE 0xA78 #define AMD_PMF_REGISTER_ARGUMENT 0xA58 +/* PMF-SMU communication registers for 1AH_M80H */ +#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 +#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 +#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C +#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC +#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 + /* Base address of SMU for mapping physical address to virtual address */ #define AMD_PMF_MAPPING_SIZE 0x01000 #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 @@ -179,7 +186,7 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); - value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg); + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); @@ -220,7 +227,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); /* Write argument into argument register */ - amd_pmf_reg_write(dev, dev->smu_regs->arg_reg, arg); + amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); /* Write message ID to message ID register */ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); @@ -239,7 +246,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 if (get) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg); + *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); } break; case AMD_PMF_RESULT_CMD_REJECT_BUSY: @@ -266,7 +273,18 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { .msg_reg = AMD_PMF_REGISTER_MESSAGE, .resp_reg = AMD_PMF_REGISTER_RESPONSE, - .arg_reg = AMD_PMF_REGISTER_ARGUMENT, + .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, +}; + +/* 1AH_M80H uses an extended mailbox with three argument registers */ +static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { + .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, + .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, + .arg_reg = { + AMD_PMF_REGISTER_ARGUMENT0_V2, + AMD_PMF_REGISTER_ARGUMENT1_V2, + AMD_PMF_REGISTER_ARGUMENT2_V2, + }, }; static const struct pci_device_id pmf_pci_ids[] = { @@ -274,6 +292,7 @@ static const struct pci_device_id pmf_pci_ids[] = { { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, + { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, { } }; @@ -563,6 +582,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0105", 0}, {"AMDI0107", 0}, {"AMDI0108", 0}, + {"AMDI0109", 0}, { } }; MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 7a8fd9d399de..088edfab08f0 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -28,6 +28,7 @@ #define AMD_CPU_ID_PS 0x14e8 #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 /* Aliases required by PCI_DEVICE_DATA() macro naming convention */ #define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB @@ -400,7 +401,7 @@ struct pmf_cbi_ring_buffer { struct amd_pmf_smu_regs { u32 msg_reg; u32 resp_reg; - u32 arg_reg; + u32 arg_reg[3]; }; struct amd_pmf_dev { From 2e34aca48f42ef0d3ff95d79e82268b2eacae486 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:30 +0530 Subject: [PATCH 105/152] platform/x86/amd/pmf: Move metrics code to dedicated file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor metrics related code from core.c into a new metrics.c file to improve code organization and maintainability. The metrics functionality is evolving with new platform support, warranting a separate file. No functional changes. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-4-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/Makefile | 2 +- drivers/platform/x86/amd/pmf/core.c | 157 +-------------------- drivers/platform/x86/amd/pmf/metrics.c | 181 +++++++++++++++++++++++++ drivers/platform/x86/amd/pmf/pmf.h | 3 + 4 files changed, 187 insertions(+), 156 deletions(-) create mode 100644 drivers/platform/x86/amd/pmf/metrics.c diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile index bf7aad80b9e9..8cac51182433 100644 --- a/drivers/platform/x86/amd/pmf/Makefile +++ b/drivers/platform/x86/amd/pmf/Makefile @@ -7,6 +7,6 @@ obj-$(CONFIG_AMD_PMF) += amd-pmf.o amd-pmf-y := core.o acpi.o sps.o \ auto-mode.o cnqf.o \ - tee-if.o spc.o + tee-if.o spc.o metrics.o # Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index 0f85ac0ba803..f557d55b51f2 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include "pmf.h" @@ -55,7 +54,7 @@ #define DELAY_MAX_US 3000 /* override Metrics Table sample size time (in ms) */ -static int metrics_table_loop_ms = 1000; +int metrics_table_loop_ms = 1000; module_param(metrics_table_loop_ms, int, 0644); MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); @@ -68,8 +67,6 @@ static bool smart_pc_support = true; module_param(smart_pc_support, bool, 0444); MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); -static struct device *pmf_device; - static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) { struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier); @@ -138,37 +135,6 @@ int amd_pmf_get_power_source(void) return POWER_SOURCE_DC; } -static void amd_pmf_get_metrics(struct work_struct *work) -{ - struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); - ktime_t time_elapsed_ms; - int socket_power; - - guard(mutex)(&dev->update_mutex); - - /* Transfer table contents */ - memset(dev->buf, 0, sizeof(dev->m_table)); - amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); - memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); - - time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; - /* Calculate the avg SoC power consumption */ - socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; - - if (dev->amt_enabled) { - /* Apply the Auto Mode transition */ - amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); - } - - if (dev->cnqf_enabled) { - /* Apply the CnQF transition */ - amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); - } - - dev->start_time = ktime_to_ms(ktime_get()); - schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); -} - static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) { return ioread32(dev->regbase + reg_offset); @@ -296,125 +262,6 @@ static const struct pci_device_id pmf_pci_ids[] = { { } }; -int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) -{ - u64 phys_addr; - u32 hi, low; - - /* Get Metrics Table Address */ - if (alloc_buffer) { - switch (dev->cpu_id) { - case AMD_CPU_ID_PS: - case AMD_CPU_ID_RMB: - dev->mtable_size = sizeof(dev->m_table); - break; - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - dev->mtable_size = sizeof(dev->m_table_v2); - break; - default: - dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); - } - - dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); - if (!dev->buf) - return -ENOMEM; - } - - phys_addr = virt_to_phys(dev->buf); - hi = phys_addr >> 32; - low = phys_addr & GENMASK(31, 0); - - amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); - amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); - - return 0; -} - -int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) -{ - int ret; - - INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); - - ret = amd_pmf_set_dram_addr(dev, true); - if (ret) - return ret; - - /* - * Start collecting the metrics data after a small delay - * or else, we might end up getting stale values from PMFW. - */ - schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); - - return 0; -} - -static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) -{ - switch (pdev->cpu_id) { - case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: - case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: - return 0; - default: - return -EOPNOTSUPP; - } -} - -static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) -{ - int ret, i; - - guard(mutex)(&dev->metrics_mutex); - - ret = is_npu_metrics_supported(dev); - if (ret) - return ret; - - ret = amd_pmf_set_dram_addr(dev, true); - if (ret) - return ret; - - memset(dev->buf, 0, dev->mtable_size); - - /* Send SMU command to get NPU metrics */ - ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); - if (ret) { - dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); - return ret; - } - - memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); - - data->npuclk_freq = dev->m_table_v2.npuclk_freq; - for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) - data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; - data->npu_power = dev->m_table_v2.npu_power; - data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; - data->npu_reads = dev->m_table_v2.npu_reads; - data->npu_writes = dev->m_table_v2.npu_writes; - - return 0; -} - -int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) -{ - struct amd_pmf_dev *pdev; - - if (!info) - return -EINVAL; - - if (!pmf_device) - return -ENODEV; - - pdev = dev_get_drvdata(pmf_device); - if (!pdev) - return -ENODEV; - - return amd_pmf_get_smu_metrics(pdev, info); -} -EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); - static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) { bool status; @@ -677,7 +524,7 @@ static int amd_pmf_probe(struct platform_device *pdev) if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); - pmf_device = dev->dev; + amd_pmf_set_device(dev->dev); err = amd_pmf_cdev_register(dev); if (err) diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c new file mode 100644 index 000000000000..e457bcf490b6 --- /dev/null +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Driver - Metrics Support + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K + * Patil Rajesh Reddy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmf.h" + +static struct device *pmf_device; + +static void amd_pmf_get_metrics(struct work_struct *work) +{ + struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); + ktime_t time_elapsed_ms; + int socket_power; + + guard(mutex)(&dev->update_mutex); + + /* Transfer table contents */ + memset(dev->buf, 0, sizeof(dev->m_table)); + amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); + memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); + + time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; + /* Calculate the avg SoC power consumption */ + socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; + + if (dev->amt_enabled) { + /* Apply the Auto Mode transition */ + amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); + } + + if (dev->cnqf_enabled) { + /* Apply the CnQF transition */ + amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); + } + + dev->start_time = ktime_to_ms(ktime_get()); + schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +} + +int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +{ + u64 phys_addr; + u32 hi, low; + + /* Get Metrics Table Address */ + if (alloc_buffer) { + switch (dev->cpu_id) { + case AMD_CPU_ID_PS: + case AMD_CPU_ID_RMB: + dev->mtable_size = sizeof(dev->m_table); + break; + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + dev->mtable_size = sizeof(dev->m_table_v2); + break; + default: + dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); + } + + dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); + if (!dev->buf) + return -ENOMEM; + } + + phys_addr = virt_to_phys(dev->buf); + hi = phys_addr >> 32; + low = phys_addr & GENMASK(31, 0); + + amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); + amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); + + return 0; +} + +int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +{ + int ret; + + INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); + + ret = amd_pmf_set_dram_addr(dev, true); + if (ret) + return ret; + + /* + * Start collecting the metrics data after a small delay + * or else, we might end up getting stale values from PMFW. + */ + schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); + + return 0; +} + +void amd_pmf_set_device(struct device *p_device) +{ + pmf_device = p_device; +} + +static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +{ + switch (pdev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +{ + int ret, i; + + guard(mutex)(&dev->metrics_mutex); + + ret = is_npu_metrics_supported(dev); + if (ret) + return ret; + + ret = amd_pmf_set_dram_addr(dev, true); + if (ret) + return ret; + + memset(dev->buf, 0, dev->mtable_size); + + /* Send SMU command to get NPU metrics */ + ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); + if (ret) { + dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); + return ret; + } + + memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); + + data->npuclk_freq = dev->m_table_v2.npuclk_freq; + for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) + data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; + data->npu_power = dev->m_table_v2.npu_power; + data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; + data->npu_reads = dev->m_table_v2.npu_reads; + data->npu_writes = dev->m_table_v2.npu_writes; + + return 0; +} + +int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +{ + struct amd_pmf_dev *pdev; + + if (!info) + return -EINVAL; + + if (!pmf_device) + return -ENODEV; + + pdev = dev_get_drvdata(pmf_device); + if (!pdev) + return -ENODEV; + + return amd_pmf_get_smu_metrics(pdev, info); +} +EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 088edfab08f0..4873685c84ad 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -137,6 +137,8 @@ struct cookie_header { #define METRICS_TABLE_ID 7 #define BIOS_OUTPUT_MAX 10 +extern int metrics_table_loop_ms; + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, @@ -864,6 +866,7 @@ 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); /* SPS Layer */ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); From d19eca503861824a6c8f8eea7eabc79e14479f84 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:31 +0530 Subject: [PATCH 106/152] platform/x86/amd/pmf: Add missing newline in dev_err message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing trailing newline to the dev_err() message printed when an invalid CPU id is encountered. Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-5-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/metrics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c index e457bcf490b6..a5444ad52cf4 100644 --- a/drivers/platform/x86/amd/pmf/metrics.c +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -73,7 +73,7 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) dev->mtable_size = sizeof(dev->m_table_v2); break; default: - dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); + dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); } dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); From 9f29ec1f46d03a7b633c5d9a668ece059d539d4c Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:32 +0530 Subject: [PATCH 107/152] platform/x86/amd/pmf: Use upper/lower_32_bits() in amd_pmf_set_dram_addr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the open coded manual bit shifting used to split a 64-bit physical address into its high and low 32-bit halves with the standard kernel helpers upper_32_bits() and lower_32_bits(). No functional changes. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-6-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/metrics.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c index a5444ad52cf4..8ee60d455ec3 100644 --- a/drivers/platform/x86/amd/pmf/metrics.c +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -10,7 +10,6 @@ */ #include -#include #include #include #include @@ -19,6 +18,7 @@ #include #include #include +#include #include #include "pmf.h" @@ -82,8 +82,8 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) } phys_addr = virt_to_phys(dev->buf); - hi = phys_addr >> 32; - low = phys_addr & GENMASK(31, 0); + hi = upper_32_bits(phys_addr); + low = lower_32_bits(phys_addr); amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); From 17f3e140d09da4d17dadef82ab67547e71215a64 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:33 +0530 Subject: [PATCH 108/152] platform/x86/amd/pmf: Refactor NPU metrics for platform extensibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the NPU metrics retrieval code to use a switch-case structure based on CPU ID, preparing the driver for supporting additional platforms with different metrics table formats. This change restructures amd_pmf_get_smu_metrics() to handle platform-specific metrics retrieval paths. The existing logic for 1AH_M20H and 1AH_M60H platforms is preserved within the switch-case block. No functional changes. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-7-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/metrics.c | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c index 8ee60d455ec3..5635b1a01827 100644 --- a/drivers/platform/x86/amd/pmf/metrics.c +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -136,29 +136,34 @@ static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_m if (ret) return ret; - ret = amd_pmf_set_dram_addr(dev, true); - if (ret) - return ret; + switch (dev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + ret = amd_pmf_set_dram_addr(dev, true); + if (ret) + return ret; - memset(dev->buf, 0, dev->mtable_size); + memset(dev->buf, 0, dev->mtable_size); - /* Send SMU command to get NPU metrics */ - ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); - if (ret) { - dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); - return ret; + /* Send SMU command to get NPU metrics */ + ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); + if (ret) { + dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); + return ret; + } + + memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); + + data->npuclk_freq = dev->m_table_v2.npuclk_freq; + for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) + data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; + data->npu_power = dev->m_table_v2.npu_power; + data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; + data->npu_reads = dev->m_table_v2.npu_reads; + data->npu_writes = dev->m_table_v2.npu_writes; + break; } - memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); - - data->npuclk_freq = dev->m_table_v2.npuclk_freq; - for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) - data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; - data->npu_power = dev->m_table_v2.npu_power; - data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; - data->npu_reads = dev->m_table_v2.npu_reads; - data->npu_writes = dev->m_table_v2.npu_writes; - return 0; } From 641b41a7a12537f8898b1e14c62e0d85a8b872c2 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Thu, 23 Jul 2026 16:45:34 +0530 Subject: [PATCH 109/152] platform/x86/amd/pmf: Add 1AH_M80H metrics table and NPU metrics support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1AH_M80H platform introduces a new firmware managed DRAM based metrics table (amd_pmf_metrics_v3) covering the full platform telemetry including power, voltages, frequencies, throttlers and activity monitors. As a first consumer of this table, add NPU metrics retrieval. Unlike earlier platforms that use a transfer table command, 1AH_M80H metrics are accumulator based and require delta calculation between consecutive samples. Extend amd_pmf_npu_metrics with npu_temp, populated from the npu_temp_acc accumulator field available on 1AH_M80H. Key changes include: - Add DRAM based metrics table support for the 1AH_M80H platform - Introduce amd_pmf_get_tbl_dram_addr() to obtain the DRAM address - Add amd_pmf_get_metrics_table_log_sample() to trigger metrics updates - Add struct amd_pmf_metrics_v3 for the 1AH_M80H metrics format - Implement accumulator based delta calculation for metrics - Introduce amd_pmf_calculate_acc_npu_metrics() to get NPU metrics - Introduce amd_pmf_supports_accumulator_metrics() to check the accumulator based metrics support. Reviewed-by: Mario Limonciello (AMD) Co-developed-by: Patil Rajesh Reddy Signed-off-by: Patil Rajesh Reddy Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260723111534.1940925-8-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/core.c | 30 ++++ drivers/platform/x86/amd/pmf/metrics.c | 146 +++++++++++++++- drivers/platform/x86/amd/pmf/pmf.h | 220 +++++++++++++++++++++++++ include/linux/amd-pmf-io.h | 2 + 4 files changed, 397 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index f557d55b51f2..b4eae65e675b 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,16 @@ static bool smart_pc_support = true; module_param(smart_pc_support, bool, 0444); MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); +static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) +{ + switch (pdev->cpu_id) { + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: + return true; + default: + return false; + } +} + static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) { struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier); @@ -154,6 +165,12 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); + if (amd_pmf_supports_accumulator_metrics(dev)) { + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); + value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); + } value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); @@ -213,6 +230,13 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + if (amd_pmf_supports_accumulator_metrics(dev) && + message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { + dev->dram_addr.hi = amd_pmf_reg_read(dev, + dev->smu_regs->arg_reg[1]); + dev->dram_addr.size = amd_pmf_reg_read(dev, + dev->smu_regs->arg_reg[2]); + } } break; case AMD_PMF_RESULT_CMD_REJECT_BUSY: @@ -516,6 +540,12 @@ static int amd_pmf_probe(struct platform_device *pdev) if (err) return err; + if (amd_pmf_supports_accumulator_metrics(dev)) { + err = amd_pmf_get_tbl_dram_addr(dev); + if (err) + return err; + } + apmf_acpi_init(dev); platform_set_drvdata(pdev, dev); amd_pmf_dbgfs_register(dev); diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c index 5635b1a01827..5c0e368ec8df 100644 --- a/drivers/platform/x86/amd/pmf/metrics.c +++ b/drivers/platform/x86/amd/pmf/metrics.c @@ -16,8 +16,11 @@ #include #include #include +#include +#include +#include #include -#include +#include #include #include @@ -25,6 +28,89 @@ static struct device *pmf_device; +static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) +{ + u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; + + return min_t(u16, val, U16_MAX); +} + +static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) +{ + u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); + + return min_t(u16, val, U16_MAX); +} + +static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, + u64 curr_value, u64 prev_value) +{ + u32 counter_diff; + u64 val_diff; + + /* Check for counter reset */ + if (curr_counter <= prev_counter) + return 0; + + counter_diff = curr_counter - prev_counter; + + if (curr_value < prev_value) + return 0; + + val_diff = curr_value - prev_value; + + return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); +} + +int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) +{ + int ret; + + ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, + ARG_NONE, &dev->dram_addr.lo); + if (ret) { + dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); + return ret; + } + + dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; + dev->mtable_size = dev->dram_addr.size; + + if (dev->mtable_size != sizeof(dev->mtable_v3)) { + dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", + dev->dram_addr.size, sizeof(dev->mtable_v3)); + return -EINVAL; + } + + dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); + if (!dev->metrics_table_virt) { + dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); + dev->metrics_table_phys = 0; + return -ENOMEM; + } + + return 0; +} + +static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) +{ + int ret; + + if (!dev->metrics_table_virt) { + dev_err(dev->dev, "Metrics DRAM not mapped\n"); + return -EINVAL; + } + + /* Send command to PMFW to update metrics table log sample */ + ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); + if (ret) { + dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); + return ret; + } + + return 0; +} + static void amd_pmf_get_metrics(struct work_struct *work) { struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); @@ -120,12 +206,46 @@ static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) switch (pdev->cpu_id) { case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: return 0; default: return -EOPNOTSUPP; } } +static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, + struct amd_pmf_npu_metrics *data) +{ + struct amd_pmf_metrics_iod *curr, *prev; + u64 val; + int i; + + curr = &dev->mtable_v3.iod; + prev = &dev->prev_metrics.iod; + + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_temp_acc, prev->npu_temp_acc); + data->npu_temp = amd_pmf_q10_acc_to_int(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_power_acc, prev->npu_power_acc); + data->npu_power = amd_pmf_q10_acc_to_mW(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npuhclk_freq_eff_acc, + prev->npuhclk_freq_eff_acc); + data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->aieclk_freq_eff_acc, + prev->aieclk_freq_eff_acc); + data->npuclk_freq = amd_pmf_q10_acc_to_int(val); + + for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { + val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, + curr->npu_busy_acc[i], + prev->npu_busy_acc[i]); + data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); + } +} + static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) { int ret, i; @@ -136,6 +256,8 @@ static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_m if (ret) return ret; + memset(data, 0, sizeof(*data)); + switch (dev->cpu_id) { case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: @@ -162,6 +284,28 @@ static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_m data->npu_reads = dev->m_table_v2.npu_reads; data->npu_writes = dev->m_table_v2.npu_writes; break; + case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: + ret = amd_pmf_get_metrics_table_log_sample(dev); + if (ret) + return ret; + + memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); + + /* + * Ignore the first sample, as previous metrics is uninitialized (zero) + * Metrics are calculated as: + * metrics = current_metrics - previous_metrics + * Skipping the initial sample ensures accurate delta calculations. + */ + if (!dev->npu_metrics_have_prev) { + memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); + dev->npu_metrics_have_prev = true; + return 0; + } + + amd_pmf_calculate_acc_npu_metrics(dev, data); + memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); + break; } return 0; diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 4873685c84ad..07ec00684233 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -14,10 +14,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #define POLICY_BUF_MAX_SZ 0x4b000 #define POLICY_SIGN_COOKIE 0x31535024 @@ -75,6 +78,10 @@ struct cookie_header { #define SET_PMF_PPT 0x25 #define SET_PMF_PPT_APU_ONLY 0x26 +/* Message IDs for 1AH_M80H platform */ +#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E +#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F + /* OS slider update notification */ #define DC_BEST_PERF 0 #define DC_BETTER_PERF 1 @@ -139,6 +146,11 @@ struct cookie_header { extern int metrics_table_loop_ms; +#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 +#define AMD_PMF_METRIC_CCX_MAX 4 +#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 +#define AMD_PMF_NUM_MAX_CORES 12 + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, @@ -250,6 +262,199 @@ struct apmf_fan_idx { u32 fan_ctl_idx; } __packed; +struct amd_pmf_metrics_iod { + u32 counter_acc; + /* Set Voltage */ + u64 vddcr_set_voltage; + u64 vddcr_soc_set_voltage; + u64 vddcr_npu_set_voltage; + u64 vddcr_lp_set_voltage; + u64 vddcr_gfx_set_voltage; + u64 vdd_misc_set_voltage; + /* Telemetry Voltages */ + u64 vddcr_telemetry_voltage; + u64 vddcr_soc_telemetry_voltage; + u64 vddcr_npu_telemetry_voltage; + u64 vddcr_lp_telemetry_voltage; + u64 vddcr_gfx_telemetry_voltage; + u64 vdd_misc_telemetry_voltage; + /* Telemetry Powers */ + u64 vddcr_telemetry_power; + u64 vddcr_soc_telemetry_power; + u64 vddcr_npu_telemetry_power; + u64 vddcr_lp_telemetry_power; + u64 vddcr_gfx_telemetry_power; + u64 vdd_misc_telemetry_power; + /* Throttlers - Fast PPT */ + u32 fppt_fused_limit; + u32 fppt_max_irm_limit; + u32 fppt_max_pbo_limit; + u32 fppt_limit; + u64 fppt_value_acc; + u32 fppt_residency_acc; + /* Throttlers - Slow PPT */ + u32 sppt_fused_limit; + u32 sppt_max_irm_limit; + u32 sppt_max_pbo_limit; + u32 sppt_limit; + u64 sppt_value_acc; + u32 sppt_residency_acc; + /* Throttlers - STAPM */ + u32 spl_fused_limit; + u32 spl_max_irm_limit; + u32 spl_max_pbo_limit; + u32 spl_limit; + u64 spl_value_acc; + u32 spl_residency_acc; + /* Throttlers - TDC VDDCR */ + u32 tdc_vddcr_fused_limit; + u32 tdc_vddcr_max_irm_limit; + u32 tdc_vddcr_max_pbo_limit; + u32 tdc_vddcr_limit; + u64 tdc_vddcr_value_acc; + u32 tdc_vddcr_residency_acc; + /* Throttlers - TDC VDDCR_SOC */ + u32 tdc_vddcr_soc_fused_limit; + u32 tdc_vddcr_soc_max_irm_limit; + u32 tdc_vddcr_soc_max_pbo_limit; + u32 tdc_vddcr_soc_limit; + u64 tdc_vddcr_soc_value_acc; + u32 tdc_vddcr_soc_residency_acc; + /* Throttlers - TDC VDDCR_NPU */ + u32 tdc_vddcr_npu_fused_limit; + u32 tdc_vddcr_npu_max_irm_limit; + u32 tdc_vddcr_npu_max_pbo_limit; + u32 tdc_vddcr_npu_limit; + u64 tdc_vddcr_npu_value_acc; + u32 tdc_vddcr_npu_residency_acc; + /* Throttlers - TDC VDDCR_LP */ + u32 tdc_vddcr_lp_fused_limit; + u32 tdc_vddcr_lp_max_irm_limit; + u32 tdc_vddcr_lp_max_pbo_limit; + u32 tdc_vddcr_lp_limit; + u64 tdc_vddcr_lp_value_acc; + u32 tdc_vddcr_lp_residency_acc; + /* Throttlers - TDC VDDCR_GFX */ + u32 tdc_vddcr_gfx_fused_limit; + u32 tdc_vddcr_gfx_max_irm_limit; + u32 tdc_vddcr_gfx_max_pbo_limit; + u32 tdc_vddcr_gfx_limit; + u64 tdc_vddcr_gfx_value_acc; + u32 tdc_vddcr_gfx_residency_acc; + /* Throttlers - EDC VDDCR */ + u32 edc_vddcr_fused_limit; + u32 edc_vddcr_max_irm_limit; + u32 edc_vddcr_max_pbo_limit; + u32 edc_vddcr_limit; + /* Throttlers - Thermal */ + u32 thm_fused_limit; + u32 thm_limit; + u64 thm_value_acc; + u32 thm_residency_acc; + u32 prochot_residency_acc; + u64 gfx_temp_acc; + u64 soc_temp_acc; + u32 p3t_fused_limit; + u64 p3t_value_acc; + /* Power */ + u64 system_power_acc; + u64 apu_power_acc; + u64 dgpu_power_acc; + u64 npu_power_acc; + /* Frequencies */ + u64 fclk_freq_eff_acc; + u64 memclk_freq_eff_acc; + u64 lclk_freq_eff_acc; + u64 gfxclk_freq_eff_acc; + u64 socclk_freq_eff_acc; + u64 vclk_freq_eff_acc; + u64 vpeclk_freq_eff_acc; + u64 aieclk_freq_eff_acc; + u64 npuhclk_freq_eff_acc; + /* Bandwidth */ + u64 dram_read_bandwidth; + u64 dram_write_bandwidth; + /* Activity Monitors */ + u64 gfx_busy_acc; + u64 vcn_busy_acc; + u64 npu_busy_acc[3]; + /* STT Limits */ + u32 stt_min_limit; + u64 stt_apu_hotspot_temp_acc; + u64 stt_hs2_hotspot_temp_acc; + u32 stt_apu_temp_limit; + u64 stt_apu_skin_temp_acc; + /* Residencies */ + u64 cpuoff_residency_ccx0; + u64 cpuoff_residency_ccx1; + u64 cpuoff_residency_ccx2; + u64 cpuoff_residency_ccx3; + /* DF-pstates */ + u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; + /* System */ + u8 gfx_disabled; + u8 spare2[3]; + u32 gfxclk_fmax; + u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; + /* Overclock Capable */ + u8 cpu_precise_and_direct_oc_capable; + u8 gfx_precise_and_direct_oc_capable; + u8 pbo_basic_oc_capable; + u8 pbo_advanced_oc_capable; + u8 pbo_nitro_oc_capable; + u8 memory_and_fabric_oc_capable; + u8 misc_oc_capable; + u8 extreme_cold_oc_capable; + u8 down_config_control_capable; + u8 spare0[3]; + /* Overclock Status */ + u32 fit_limit_scalar; + u8 ln2_enabled; + u8 cpu_precise_and_direct_oc_enabled; + u8 gfx_precise_and_direct_oc_enabled; + u8 spare1[2]; + /* Voltage Guardband in PSM count */ + s8 psm_guardband[5][5][3]; + s32 core_power_limit_offset; + u32 max_freq_offset[5]; + u64 npu_temp_acc; + u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; + u32 cclk_fboost; + /* PMF */ + u32 pmf_fast_apu_ppt_limit; + u64 pmf_fast_apu_ppt_value_acc; + u32 pmf_fast_apu_ppt_residency_acc; + u32 pmf_slow_apu_ppt_limit; + u64 pmf_slow_apu_ppt_value_acc; + u32 pmf_slow_apu_ppt_residency_acc; + u32 pmf_fast_spm_limit; + u64 pmf_fast_spm_value_acc; + u32 pmf_fast_spm_residency_acc; + u32 pmf_slow_spm_limit; + u64 pmf_slow_spm_value_acc; + u32 pmf_slow_spm_residency_acc; + u32 spare3[5]; +} __packed __aligned(4); + +struct amd_pmf_metrics_ccx { + u64 core_c0[AMD_PMF_NUM_MAX_CORES]; + u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; + u64 core_freq[AMD_PMF_NUM_MAX_CORES]; + u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; + u64 core_temp[AMD_PMF_NUM_MAX_CORES]; + u64 core_power[AMD_PMF_NUM_MAX_CORES]; +} __packed __aligned(4); + +struct amd_pmf_metrics_v3 { + struct amd_pmf_metrics_iod iod; + struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; +} __packed __aligned(4); + struct smu_pmf_metrics_v2 { u16 core_frequency[16]; /* MHz */ u16 core_power[16]; /* mW */ @@ -406,6 +611,12 @@ struct amd_pmf_smu_regs { u32 arg_reg[3]; }; +struct amd_pmf_arg_data { + u32 lo; + u32 hi; + u32 size; +}; + struct amd_pmf_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -459,6 +670,12 @@ struct amd_pmf_dev { struct mutex metrics_mutex; u32 bios_output[BIOS_OUTPUT_MAX]; const struct amd_pmf_smu_regs *smu_regs; + void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ + phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ + struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ + struct amd_pmf_arg_data dram_addr; + struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ + bool npu_metrics_have_prev; }; struct apmf_sps_prop_granular_v2 { @@ -868,6 +1085,9 @@ 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); +/* Metrics layer */ +int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); + /* SPS Layer */ 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, diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h index e014d4ce5a20..b84c200b0f8e 100644 --- a/include/linux/amd-pmf-io.h +++ b/include/linux/amd-pmf-io.h @@ -60,6 +60,7 @@ struct amd_sfh_info { * @mpnpuclk_freq: MPNPU [MHz] * @npu_reads: NPU read bandwidth [MB/sec] * @npu_writes: NPU write bandwidth [MB/sec] + * @npu_temp: NPU temperature [C] */ struct amd_pmf_npu_metrics { u16 npuclk_freq; @@ -68,6 +69,7 @@ struct amd_pmf_npu_metrics { u16 mpnpuclk_freq; u16 npu_reads; u16 npu_writes; + u16 npu_temp; }; int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); From d30569cb5aadcc623accf0422132d0bc0286a9ff Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 27 Jul 2026 19:45:38 +0530 Subject: [PATCH 110/152] platform/x86/amd/hsmp: Add HSMP messages for Family 1Ah, Model 50h-5Fh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Family 1Ah Model 50h-5Fh firmware exposes new HSMP messages (0x29-0x2A, 0x33-0x3A) for PC6/CC6 control, CCD power/thermal monitoring, DIMM sideband access, floor- and SDPS-limit control, and command-enable discovery. The same firmware extends three existing SET-only messages (HSMP_SET_XGMI_LINK_WIDTH 0x0C, HSMP_SET_DF_PSTATE 0x0D, HSMP_SET_PSTATE_MAX_MIN 0x22) with a read-back path selected by bit[31] of args[0] (0 = set, 1 = get). Add the new IDs and convert the three messages to HSMP_SET_GET. Also add PQoS-related HSMP messages HSMP_PQOS_TRAFFIC_PRIORITY (0x3B) and HSMP_PQOS_FLOATING_BW (0x3C) with matching hsmp_msg_desc_table[] descriptors so userspace can reach the new functionality. Backward compatibility is preserved on prior platforms: new IDs previously occupied HSMP_RSVD slots, and existing userspace that leaves bit[31] = 0 continues to take a pure SET path. Converting the three SET messages to HSMP_SET_GET also keeps them accepted by validate_message(), which already applies a relaxed upper-bound check on response_sz for that type. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-2-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/include/uapi/asm/amd_hsmp.h | 172 ++++++++++++++++++++++++--- 1 file changed, 158 insertions(+), 14 deletions(-) diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h index 92d8f256d096..eff35c3c6ce0 100644 --- a/arch/x86/include/uapi/asm/amd_hsmp.h +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -53,9 +53,21 @@ enum hsmp_message_ids { HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ + HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ + HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ + HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ + HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ + HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ + HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ + HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ + HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ + HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ + HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ + HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ + HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ HSMP_MSG_ID_MAX, }; @@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] {0, 1, HSMP_GET}, /* - * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 - * input: args[0] = min link width[15:8] + max link width[7:0] + * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 + * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + + * min link width[15:8] + max link width[7:0] + * Link width encoding: 0 = x4, 1 = x8, 2 = x16. + * On SET, max must be >= min. On GET, [15:0] are reserved. + * output: args[0] = reserved[31:16] + min link width[15:8] + + * max link width[7:0] */ - {1, 0, HSMP_SET}, + {1, 1, HSMP_SET_GET}, /* - * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 - * input: args[0] = df pstate[7:0] + * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 + * input: args[0] = set APB_DISABLE / get APB state[31] + * (0 = set & lock DF P-state, 1 = get) + + * reserved[30:8] + + * DF P-state[7:0] (0..2; reserved on GET) + * output: args[0] = reserved[31:9] + + * APB state[8] (1 = disabled, 0 = enabled) + + * locked DF P-state[7:0] if [8] = 1, else reserved */ - {1, 0, HSMP_SET}, + {1, 1, HSMP_SET_GET}, /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ {0, 0, HSMP_SET}, @@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] {1, 1, HSMP_SET}, /* - * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0 - * input: args[0] = power efficiency mode[2:0] + * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), + * num_args = 1, response_sz = 1 + * input: args[0] = set/get policy[31] (0 = set, 1 = get) + + * high util point[30:24] + + * low util point[23:17] + + * PPT limit[16:5] + + * reserved[4:3] + mode selection[2:0] + * [30:5] are valid only when [2:0] is a balanced core mode + * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). + * output: args[0] same layout, [31] reserved, [2:0] = arbitrated + * current efficiency mode. */ {1, 1, HSMP_SET_GET}, /* - * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0 - * input: args[0] = min df pstate[15:8] + max df pstate[7:0] + * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 + * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + + * reserved[30:16] + + * min DF P-state[15:8] + max DF P-state[7:0] + * DF P-state encoding: 0 = DFP0 (high performance), + * 1 = DFP1, 2 = DFP2 (low performance). + * [15:0] are reserved when getting (args[0] bit[31] = 1). + * output: args[0] = reserved[31:16] + min DF P-state[15:8] + + * max DF P-state[7:0] */ - {1, 0, HSMP_SET}, + {1, 1, HSMP_SET_GET}, /* * HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1 @@ -355,9 +394,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] */ {1, 1, HSMP_SET_GET}, - /* RESERVED(0x29-0x2f) */ - {0, 0, HSMP_RSVD}, - {0, 0, HSMP_RSVD}, + /* + * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 + * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + + * reserved[30:1] + + * enable PC6[0] (0 = disable, 1 = enable; + * reserved on GET) + * output: args[0] = reserved[31:1] + current PC6 control[0] + * (last value configured via HSMP or APML) + */ + {1, 1, HSMP_SET_GET}, + + /* + * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 + * Configures CC6 enable for all cores; changing the setting does + * not by itself transition cores in or out of CC6. + * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + + * reserved[30:1] + + * enable CC6[0] (0 = disable, 1 = enable; + * reserved on GET) + * output: args[0] = reserved[31:1] + current CC6 control[0] + * (last value configured via HSMP or APML) + */ + {1, 1, HSMP_SET_GET}, + + /* RESERVED(0x2B-0x2F) */ {0, 0, HSMP_RSVD}, {0, 0, HSMP_RSVD}, {0, 0, HSMP_RSVD}, @@ -385,6 +446,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] */ {0, 2, HSMP_GET}, + /* + * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 + * input: args[0] = reg space[23] + reg offset[22:12] + + * device LID[11:8] + DIMM address[7:0] + * output: args[0] = read data byte[3:0] + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 + * input: args[0] = apic id of core[15:0] + * output: args[0] = CCD power(mWatts)[31:0] + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 + * output: args[0] = thermal behaviour[31:0] + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 + * input: args[0] = SVI3 rail index[3:1] + read temperature[0] + * output: args[0] = SVI3 rail index[30:28] + + * rail temperature in degree C[27:0] + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 + * input: args[0] = HSMP command mask[0] + * output: status of HSMP command = args[0], args[1], args[2] + */ + {1, 3, HSMP_GET}, + + /* + * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 + * input: args[0] = op[31:30] + reserved[29:28] + + * apic id[27:16] + floor frequency MHz[15:0] + * op encoding: 00 = set per-core floor, + * 01 = set all-cores floor (apic id reserved), + * 10 = get per-core floor, + * 11 = get per-core effective floor. + * Floor frequency field is reserved on GET (bit[31] = 1). + * output: args[0] = floor frequency MHz[15:0] + * (effective for op 11, configured for op 10; + * reserved on SET) + */ + {1, 1, HSMP_SET_GET}, + + /* + * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 + * input: args[0] = write data[31:24] + reg space[23] + + * reg offset[22:12] + device LID[11:8] + + * DIMM address[7:0] + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 + * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + + * SDPS limit[30:0] + * output: args[0] = SDPS limit[30:0] + */ + {1, 1, HSMP_SET_GET}, + + /* + * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 + * input: args[0] = op[31:30] + priority sel[27:26] + + * priority val[21:20] + input[19:0] + * output: args[0] = supported priorities or priority val[1:0] + */ + {1, 1, HSMP_SET_GET}, + + /* + * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 + * input: args[0] = op[31] + sub-op[30:29] + params[28:0] + * output: args[0] = discovery bits or floating/global memory BW (Gbps) + * output: args[1] = reserved or config (drop adj, sampling delay, + * hysteresis) + */ + {1, 2, HSMP_SET_GET}, }; /* Metrics table (supported only with proto version 6) */ From 9185ad51dfd2bc8bde0c7e7d9f4493d8758d4fab Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 27 Jul 2026 19:45:39 +0530 Subject: [PATCH 111/152] platform/x86/amd/hsmp: Unify response_sz validation to an upper-bound check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As HSMP protocol versions evolve, existing message IDs sometimes gain additional response words on newer firmware. validate_message() currently enforces a strict equality (response_sz == table value) for HSMP_SET and HSMP_GET, so userspace compiled against an earlier descriptor table is rejected with -EINVAL when it asks for fewer response words than the in-kernel table now declares - even though that caller has no interest in the additional words. Only HSMP_SET_GET already used a relaxed upper-bound check. Replace the per-type branching with a single upper-bound check for all message types. Userspace can now request fewer response words than hardware provides, while requests that exceed the descriptor table (and therefore the hardware capability) are still rejected. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-3-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index a457831c9c6b..02425f7e6f14 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -197,20 +197,16 @@ static int validate_message(struct hsmp_message *msg) return -EINVAL; /* - * Some older HSMP SET messages are updated to add GET in the same message. - * In these messages, GET returns the current value and SET also returns - * the successfully set value. To support this GET and SET in same message - * while maintaining backward compatibility for the HSMP users, - * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. + * 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. */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { - if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } else { - /* only HSMP_SET or HSMP_GET messages go through this strict check */ - if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } + if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + return 0; } From 96f1ba765ab55d57ee2a2bf88e05c2ee699c7c5b Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 27 Jul 2026 19:45:40 +0530 Subject: [PATCH 112/152] platform/x86/amd/hsmp: Source metric-table size from firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver hard-codes the metric-table region size to sizeof(struct hsmp_metric_table). That is correct for HSMP protocol version 6 but mis-sizes the ioremap of the SMU DRAM region on newer platforms: Family 1Ah Model 50h-5Fh exposes a ~13 KB table under protocol version 7, and the table is expected to keep growing on future firmware. The same hard-coded value also forces hsmp_metric_tbl_read() to reject any read that follows the actual firmware layout. Pick up the table size from firmware instead. SMU on Family 1Ah Model 50h and later populates HSMP_GET_METRIC_TABLE_DRAM_ADDR's args[2] with the DRAM region size in bytes; older firmware leaves it 0. Bump the descriptor's response_sz to 3 so the field is read, and store the result in the new per-socket hsmp_socket.metric_tbl_size, which is then used both for the ioremap() of the region and as the expected size in hsmp_metric_tbl_read(). The size is stored per socket rather than per platform because hsmp_get_tbl_dram_base() runs once per socket and each socket maps its own region. A single platform-wide field would let the last socket's size be used to copy out of an earlier socket's smaller mapping. Bump DRIVER_VERSION to 2.6. Behaviour on existing protocol-version-6 hardware is unchanged. Reading a third response word is safe there: for this command SMU leaves args[2] as 0 rather than a stale value from an earlier mailbox transaction, so the fallback always applies, yielding the same value as the previous hard-coded one, and both the ioremap and the size check produce the same result as before. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/include/uapi/asm/amd_hsmp.h | 5 +++-- drivers/platform/x86/amd/hsmp/hsmp.c | 17 ++++++++++++++--- drivers/platform/x86/amd/hsmp/hsmp.h | 5 ++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h index eff35c3c6ce0..d1d3bc60cffc 100644 --- a/arch/x86/include/uapi/asm/amd_hsmp.h +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -367,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] {0, 0, HSMP_GET}, /* - * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 + * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 * output: args[0] = lower 32 bits of the address * output: args[1] = upper 32 bits of the address + * output: args[2] = DRAM region size in bytes */ - {0, 2, HSMP_GET}, + {0, 3, HSMP_GET}, /* * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0 diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 02425f7e6f14..3e9bdbcd9ea9 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -429,8 +429,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) return -ENOMEM; } - /* Do not support lseek(), also don't allow more than the size of metric table */ - if (size != sizeof(struct hsmp_metric_table)) { + if (size != sock->metric_tbl_size) { dev_err(sock->dev, "Wrong buffer size\n"); return -EINVAL; } @@ -484,6 +483,7 @@ void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) iounmap(sock->metric_tbl_addr); sock->metric_tbl_addr = NULL; } + sock->metric_tbl_size = 0; } } EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); @@ -493,6 +493,7 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; struct hsmp_message msg = { 0 }; phys_addr_t dram_addr; + size_t tbl_size; int ret; msg.sock_ind = sock_ind; @@ -524,11 +525,21 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) iounmap(sock->metric_tbl_addr); sock->metric_tbl_addr = NULL; } - sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); + sock->metric_tbl_size = 0; + + /* SMU returns table size from Family 1Ah Model 50h and forward */ + if (msg.args[2]) + tbl_size = msg.args[2]; + else + tbl_size = sizeof(struct hsmp_metric_table); + + sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; } + sock->metric_tbl_size = tbl_size; + return 0; } EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index cfd1a8cbd459..8dbff16a87b1 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -20,6 +20,7 @@ #include #include #include +#include #define HSMP_METRICS_TABLE_NAME "metrics_bin" @@ -29,7 +30,7 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" -#define DRIVER_VERSION "2.5" +#define DRIVER_VERSION "2.6" struct hsmp_mbaddr_info { u32 base_addr; @@ -43,6 +44,8 @@ struct hsmp_socket { struct bin_attribute hsmp_attr; struct hsmp_mbaddr_info mbinfo; void __iomem *metric_tbl_addr; + /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ + size_t metric_tbl_size; void __iomem *virt_base_addr; struct semaphore hsmp_sem; /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ From 5273183b6362cad9584bdfb5dddb2df30e4f5477 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 27 Jul 2026 19:45:41 +0530 Subject: [PATCH 113/152] platform/x86/amd/hsmp: Add IOCTL_GET_TELEMETRY_DATA for metric table reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metric table needs to be delivered to userspace as a single atomic snapshot, but the current sysfs metrics_bin path is a file read: userspace can read it in chunks and observe a torn snapshot if an SMU refresh happens between read() calls. The same path is also bounded by PAGE_SIZE, so the ~13 KB table used by HSMP protocol version 7 on Family 1Ah Model 50h-5Fh cannot be returned at all, regardless of how userspace reads it. Rather than extend sysfs to lift both restrictions, expose the metric table through the existing HSMP character device using a new ioctl that always copies the table in one shot. Add struct hsmp_telemetry_data and HSMP_IOCTL_GET_TELEMETRY_DATA to the UAPI header. Under the surrounding #pragma pack(4), placing the __u64 user pointer first gives a tight 16-byte layout that is identical for 32- and 64-bit callers, and the trailing __u16 reserved field is rejected with -EINVAL if non-zero so future kernels can repurpose it without breaking already-deployed userspace. The command is encoded with _IOW because the kernel only reads the request struct; the snapshot travels through the user pointer it carries. The requested size may be anything from one byte up to the size firmware reported for that socket's table. A short request returns the leading bytes of the snapshot, so userspace built against an older table layout keeps working on firmware that grew the table, mirroring the relaxed response_sz rule applied to HSMP messages earlier in this series. A request larger than the firmware table is rejected with -EINVAL rather than short-written, so a caller can never mistake a partial copy for a full one. Dispatch hsmp_ioctl() on the ioctl command: the existing message handler is factored out as hsmp_ioctl_msg() for HSMP_IOCTL_CMD, and HSMP_IOCTL_GET_TELEMETRY_DATA goes to a new hsmp_ioctl_get_telemetry() helper. /dev/hsmp is a singleton character device that outlives an individual socket unbind, so an ioctl issued on an already-open fd can run concurrently with socket teardown. hsmp_sock_rwsem is the driver's contract for that: the data plane takes it for read, and probe and remove take it for write to drain the data plane before freeing the socket array, unmapping the metric tables and destroying the per-socket mutexes. hsmp_ioctl_get_telemetry() takes it for read across the socket lookup, the checks on that socket's metric-table state and the table read itself, so none of that state can be torn down underneath it. Without this the handler would sleep in its kvmalloc() holding no lock at all, and could resume with a freed socket, locking a destroyed mutex and reading from an unmapped iomem region. The lock is dropped before the copy_to_user(), because faulting in the destination can block indefinitely on a userfaultfd-backed buffer and would otherwise leave a socket unbind waiting for the write lock. Since hsmp_metric_tbl_read() reached the mailbox through hsmp_send_message(), which takes hsmp_sock_rwsem itself, calling it with the lock already held would recursively take the read side and can deadlock against a queued writer. Split out hsmp_metric_tbl_read_locked(), which asserts the lock and uses hsmp_send_message_locked(), and leave hsmp_metric_tbl_read() as a wrapper that takes the read lock for the sysfs callers. This also brings the whole fill-and-copy under the rwsem for those callers, where the memcpy_fromio() previously ran outside it, and makes the lock order uniformly hsmp_sock_rwsem -> metric_read_lock -> hsmp_sem. The user-controlled socket index in HSMP_IOCTL_GET_TELEMETRY_DATA is clamped with array_index_nospec() before indexing hsmp_pdev.sock[], mitigating Spectre v1 (CVE-2017-5753). Include linux/nospec.h, which the file relied on getting transitively. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-5-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/arch/x86/amd_hsmp.rst | 35 ++++++- arch/x86/include/uapi/asm/amd_hsmp.h | 45 +++++++++ drivers/platform/x86/amd/hsmp/hsmp.c | 144 ++++++++++++++++++++++++++- 3 files changed, 220 insertions(+), 4 deletions(-) diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst index 8bb411f0d70d..fa1fc240e212 100644 --- a/Documentation/arch/x86/amd_hsmp.rst +++ b/Documentation/arch/x86/amd_hsmp.rst @@ -68,6 +68,13 @@ under per socket sysfs directory created at Note: lseek() is not supported as entire metrics table is read. +The sysfs metrics_bin path supports only HSMP protocol version 6 and, +because it is a file read, can return a torn snapshot if userspace +reads in pieces. The protocol version 7 metric table (~13 KB) also +exceeds PAGE_SIZE, so a read returns ``-EOPNOTSUPP`` there. For +atomic reads on any protocol version, use the +``HSMP_IOCTL_GET_TELEMETRY_DATA`` ioctl on /dev/hsmp (see below). + Metrics table definitions will be documented as part of Public PPR. The same is defined in the amd_hsmp.h header. @@ -167,7 +174,7 @@ Next thing, open the device file, as follows:: exit(1); } -The following IOCTL is defined: +The following IOCTLs are defined: ``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)`` The argument is a pointer to a:: @@ -180,6 +187,32 @@ The following IOCTL is defined: __u16 sock_ind; /* socket number */ }; +``ioctl(file, HSMP_IOCTL_GET_TELEMETRY_DATA, struct hsmp_telemetry_data *req)`` + Atomically fetch the firmware metric (telemetry) table for a socket. + The ioctl copies the table in one shot, so unlike the metrics_bin + sysfs path it cannot return a torn snapshot and is not bounded by + PAGE_SIZE. Required for HSMP protocol version 7+ (e.g. Family 1Ah + Model 50h-5Fh, whose table is ~13 KB). Argument:: + + struct hsmp_telemetry_data { + __u64 buf; /* User pointer to destination buffer */ + __u32 size; /* Size of @buf in bytes */ + __u16 sock_ind; /* Socket index */ + __u16 reserved; /* Reserved, must be zero */ + }; + + ``size`` must be non-zero and no larger than the table size firmware + reports for that socket; a larger value is rejected with ``-EINVAL`` + rather than short-written, and a smaller one returns the leading + ``size`` bytes of the snapshot. A non-zero ``reserved`` is also + rejected with ``-EINVAL``. + + The table layout depends on the protocol version, which userspace + reads from the ``protocol_version`` sysfs attribute. On version 6 + the table is ``struct hsmp_metric_table``, so callers pass + ``sizeof(struct hsmp_metric_table)``. Later version metrics table + layout is documented in the Public PPR. + The ioctl would return a non-zero on failure; you can read errno to see what happened. The transaction returns 0 on success. diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h index d1d3bc60cffc..eda336bfd3e9 100644 --- a/arch/x86/include/uapi/asm/amd_hsmp.h +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -612,6 +612,39 @@ struct hsmp_metric_table { __u32 gfxclk_frequency[8]; }; +/** + * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL + * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout + * stable between 32-bit and 64-bit callers) to the destination + * buffer that receives the metric table. + * @size: Input. Size in bytes of the buffer pointed to by @buf, and the + * number of bytes copied out on success. Must be non-zero and no + * larger than the metric table size firmware reports for this + * socket; a larger value is rejected with -EINVAL rather than + * short-written. A smaller value returns the leading @size bytes + * of the snapshot. The kernel does not write this field back. + * @sock_ind: Input. Socket index from which the metric table is read. + * @reserved: Reserved for future use. Callers should set this to zero; + * future kernels may begin interpreting the field, so passing + * a non-zero value today is not forwards compatible. + * + * Placing @buf first lets all fields fall on their natural alignment under + * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with + * the same wire layout on 32-bit and 64-bit userspace. + * + * The metric table layout depends on the HSMP protocol version reported by + * firmware, which userspace can read from the protocol_version sysfs + * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on + * that version pass sizeof(struct hsmp_metric_table). Later version metrics + * table layout is documented in the Public PPR. + */ +struct hsmp_telemetry_data { + __u64 buf; + __u32 size; + __u16 sock_ind; + __u16 reserved; +}; + /* Reset to default packing */ #pragma pack() @@ -619,4 +652,16 @@ struct hsmp_metric_table { #define HSMP_BASE_IOCTL_NR 0xF8 #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) +/* + * Fetch the firmware metric (telemetry) table for a given socket via the + * HSMP character device. This avoids the PAGE_SIZE limitation of the + * sysfs binary attribute path for tables larger than one page (such as the + * ~13 KB table used by HSMP protocol version 7). + * + * The direction is _IOW because the kernel only reads the request struct; + * the table itself is written to the buffer that @buf points at. + */ +#define HSMP_IOCTL_GET_TELEMETRY_DATA \ + _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) + #endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 3e9bdbcd9ea9..5e123a4ecea9 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -15,9 +15,12 @@ #include #include #include +#include #include #include +#include #include +#include #include "hsmp.h" @@ -347,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) return false; } -long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) { int __user *arguser = (int __user *)arg; struct hsmp_message msg = { 0 }; @@ -416,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; } -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size); + +/* + * Fetch the firmware metric (telemetry) table for the requested socket and + * copy it to the userspace buffer described by the request. + * + * The metric table size is variable across HSMP protocol versions and on + * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer + * size, which may be anything up to the size firmware reported for this + * socket's table. + */ +static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) +{ + void *kbuf __free(kvfree) = NULL; + void __user *arguser = (void __user *)arg; + struct hsmp_telemetry_data req; + struct hsmp_socket *sock; + void __user *user_buf; + size_t tbl_size; + unsigned int sock_ind; + int ret; + + /* Telemetry data is read-only; require read access on the fd. */ + if (!(fp->f_mode & FMODE_READ)) + return -EPERM; + + if (copy_from_user(&req, arguser, sizeof(req))) + return -EFAULT; + + /* + * Reserved fields must be zero so future kernels can safely + * repurpose them without breaking already-deployed userspace. + */ + if (req.reserved) + return -EINVAL; + + user_buf = u64_to_user_ptr(req.buf); + + /* + * /dev/hsmp is a singleton character device that outlives an individual + * socket unbind, so an ioctl on an already-open fd can run concurrently + * with socket teardown. Hold hsmp_sock_rwsem for read across the socket + * lookup, the checks on its metric-table state and the read itself: + * probe and remove take the same lock for write, so they cannot free the + * socket array, unmap the table or destroy the per-socket mutex while + * this runs. + * + * The lock is dropped before the copy_to_user() below. Faulting in the + * destination can block indefinitely on a userfaultfd-backed buffer, + * which would leave a socket unbind waiting for the write lock. + */ + scoped_guard(rwsem_read, &hsmp_sock_rwsem) { + if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) + return -ENODEV; + + /* + * Sanitize the user-controlled socket index against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENODEV, but a mispredicted branch can still let the + * CPU speculatively use sock_ind as an index into + * hsmp_pdev.sock[] and pull arbitrary kernel memory into the + * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns + * the bounds check into a data-flow clamp so the speculative + * load is in-range too. + */ + sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + if (!sock->metric_tbl_addr) + return -ENODEV; + + tbl_size = sock->metric_tbl_size; + if (!tbl_size) + return -ENODEV; + + /* + * A request shorter than the firmware table is served with the + * leading @size bytes of the snapshot, so userspace built + * against an older table layout keeps working on firmware that + * grew the table. Asking for more than firmware provides is + * rejected rather than short-written, so a caller can never + * mistake a partial copy for a full one. + */ + if (!req.size || req.size > tbl_size) + return -EINVAL; + + /* + * The bounce buffer is overwritten in full by memcpy_fromio() + * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid + * the zeroing cost of kvzalloc() on the ~13 KB allocation done + * on every ioctl call. + */ + kbuf = kvmalloc(tbl_size, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); + } + + if (ret < 0) + return ret; + + if (copy_to_user(user_buf, kbuf, req.size)) + return -EFAULT; + + return 0; +} + +long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HSMP_IOCTL_CMD: + return hsmp_ioctl_msg(fp, arg); + case HSMP_IOCTL_GET_TELEMETRY_DATA: + return hsmp_ioctl_get_telemetry(fp, arg); + default: + return -ENOTTY; + } +} + +/* + * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping + * and its metric_read_lock alive: probe and remove take the same lock for + * write while they bring sockets up and tear them down. + */ +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size) { struct hsmp_message msg = { 0 }; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!sock || !buf) return -EINVAL; @@ -445,13 +576,20 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) */ guard(mutex)(&sock->metric_read_lock); - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; memcpy_fromio(buf, sock->metric_tbl_addr, size); return size; } + +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +{ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_metric_tbl_read_locked(sock, buf, size); +} EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) From aca39607c1734ed976fdd65deb75b3555a5a0326 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 27 Jul 2026 19:45:42 +0530 Subject: [PATCH 114/152] platform/x86/amd/hsmp: Enable protocol version 7 metric tables on the ACPI driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI driver currently prepares the per-socket metric table only on HSMP_PROTO_VER6. With protocol version 7 in use on Family 1Ah Model 50h-5Fh, userspace cannot reach the larger ~13 KB table: hsmp_get_tbl_dram_base() is skipped, sock->metric_tbl_addr stays NULL, and the ioctl added earlier in this series has nothing to read. Widen the proto_ver gate in init_acpi() from '== HSMP_PROTO_VER6' to '>= HSMP_PROTO_VER6' so the DRAM region is mapped and sock->metric_tbl_size is populated on protocol version 7 (and any future compatible version), making the ioctl path functional. hsmp_metric_tbl_acpi_read() now returns -EOPNOTSUPP whenever the running protocol version is not VER6, because the sysfs binary attribute cannot carry a table larger than PAGE_SIZE. Version 7 userspace gets a clear, actionable error and a documented pointer to HSMP_IOCTL_GET_TELEMETRY_DATA; version 6 userspace sees no change. The non-ACPI plat.c path is intentionally left untouched: it covers Family 1Ah Model 0h-Fh hardware fixed at protocol version 6, where the existing metrics_bin remains the supported interface. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-6-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 24f54dc7254f..8257cd1da48e 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -277,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj struct device *dev = container_of(kobj, struct device, kobj); struct hsmp_socket *sock = dev_get_drvdata(dev); + /* + * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. + * It can therefore only carry the protocol version 6 metric table + * (struct hsmp_metric_table). The larger tables defined from protocol + * version 7 onwards do not fit; userspace on those systems must read + * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. + * Surface the unsupported case here as -EOPNOTSUPP rather than + * silently truncating the snapshot. + */ + if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) + return -EOPNOTSUPP; + return hsmp_metric_tbl_read(sock, buf, count); } static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, const struct bin_attribute *battr, int id) { - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) + /* + * Keep metrics_bin visible on protocol version 7 and later as well, + * 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. + */ + if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) return battr->attr.mode; return 0; @@ -539,7 +557,7 @@ static int init_acpi(struct device *dev) return ret; } - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { + if (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"); From bdd0f4e3333f6769aa740da38a67fa235062a62d Mon Sep 17 00:00:00 2001 From: Dave Carey Date: Tue, 28 Jul 2026 18:55:43 -0400 Subject: [PATCH 115/152] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated yb9-kbdock WMI driver that registers an input device reporting SW_TABLET_MODE to track the detachable Bluetooth keyboard. lenovo-ymc also loads on this machine and creates an input node with the SW_TABLET_MODE capability bit set. For input switches, the presence of the capability bit has semantic meaning: userspace (e.g. GNOME) reads the switch state at startup from every node advertising the capability and does not expect more than one such node. Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only other action taken in response to a YMC event, is guarded by a separate DMI table that excludes this machine; no other functionality is affected. Signed-off-by: Dave Carey Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260728225545.1333610-2-carvsdriver@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c index 015e046b0fce..df96107d2501 100644 --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -28,6 +28,22 @@ static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { + { + /* + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to + * avoid userspace seeing two input nodes that both advertise + * the SW_TABLET_MODE capability. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { { .matches = { @@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) struct input_dev *input_dev; int err; + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) + return -ENODEV; + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { if (force) dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); From c9b5c8ff065d0a26317215ec080fb06801887956 Mon Sep 17 00:00:00 2001 From: Dave Carey Date: Tue, 28 Jul 2026 18:55:44 -0400 Subject: [PATCH 116/152] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard that magnetically attaches to the bottom (secondary) screen in one of two positions. The Embedded Controller tracks the attachment state in a 2-bit field called BKBD and signals changes via WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI device, _UID "GMZN"). The device contains embedded BMOF data (WQDD, 20705 bytes) documenting both WMI interfaces used by this driver: LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status. The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer, so the notify callback receives BKBD without a separate query. LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}. Used for the initial state read on probe and after resume. BKBD encoding: 0 = keyboard detached 1 = keyboard docked on top half of bottom screen 2 = keyboard docked on bottom half of bottom screen 3 = reserved (not observed in practice) This driver registers two WMI drivers sharing a module-level BLOCKING_NOTIFIER_HEAD: - The event driver (LENOVO_BTKBD_EVENT) uses .notify_new() to receive a pre-parsed wmi_buffer and fires the notifier chain with the BKBD value extracted from the buffer. - The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev in its per-device private struct. At probe time it registers a notifier_block on the chain and reads the initial BKBD state via wmidev_query_block(). The WMI buffer is parsed as struct lenovo_feature_status { __le32 id; __le32 status; }, and the ID field is verified before the status is used. - SW_TABLET_MODE=1 is reported when the keyboard is detached; SW_TABLET_MODE=0 when docked in either position (keyboard present). - The raw BKBD value is exposed via read-only sysfs attribute "keyboard_position". - BKBD state is re-read via wmidev_query_block() on resume from suspend or hibernation. Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0. Acked-by: Hans de Goede Reviewed-by: Armin Wolf Signed-off-by: Dave Carey Link: https://patch.msgid.link/20260728225545.1333610-3-carvsdriver@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 ++ MAINTAINERS | 7 + drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/yb9-kbdock.c | 323 ++++++++++++++++++ 5 files changed, 364 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock new file mode 100644 index 000000000000..ebe713aefad1 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock @@ -0,0 +1,19 @@ +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock//keyboard_position +Date: June 2027 +KernelVersion: 7.3 +Contact: Dave Carey +Description: + Read-only attribute reporting the current keyboard dock position + as reported by the Embedded Controller on the Lenovo Yoga Book 9 + 14IAH10. + + Possible values: + + == ============================================================= + 0 keyboard is not docked to any screen (detached) + 1 keyboard docked on the top half of the bottom screen + 2 keyboard docked on the bottom half of the bottom screen + == ============================================================= + + SW_TABLET_MODE input events are also emitted: 0 when the keyboard + is docked (either position), 1 when detached. diff --git a/MAINTAINERS b/MAINTAINERS index 3820b286f09f..8abf559a138c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14710,6 +14710,13 @@ F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER +M: Dave Carey +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock +F: drivers/platform/x86/lenovo/yb9-kbdock.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 4443f40ef8aa..516594993073 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA To compile this driver as a module, choose M here: the module will be called lenovo-wmi-camera. +config LENOVO_YB9_KBDOCK + tristate "Lenovo Yoga Book 9 keyboard dock detection" + depends on ACPI_WMI + depends on DMI + depends on INPUT + help + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to + either screen; this driver reports SW_TABLET_MODE input events based + on the attachment state and exposes the raw position in sysfs. + + To compile this driver as a module, choose M here: the module will be + called lenovo-yb9-kbdock. + config LENOVO_YMC tristate "Lenovo Yoga Tablet Mode Control" depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 91a9370f11b3..12816d8d5e24 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c new file mode 100644 index 000000000000..70d81f3af09e --- /dev/null +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Yoga Book 9 keyboard-dock detection + * + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically + * attaches to the bottom screen in one of two positions. The EC tracks + * attachment state in a 2-bit field called BKBD and signals changes via WMI + * event 0xEB on the WM10 ACPI device (_UID "GMZN"). + * + * BKBD values: + * 0 = keyboard detached + * 1 = keyboard docked on the top half of the bottom screen + * 2 = keyboard docked on the bottom half of the bottom screen + * 3 = reserved / not observed + * + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): + * + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. + * The notify callback receives BKBD as an integer; no separate query needed. + * + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) + * WmiDataId(2) uint32 Status = BKBD value + * Used on probe and resume to read initial state. + * + * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI + * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev + * and registers a notifier_block to receive those events, eliminating the + * need for shared global state or a mutex. + * + * SW_TABLET_MODE=1 is reported when the keyboard is detached; + * SW_TABLET_MODE=0 when docked in either position (keyboard present). + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". + * + * Copyright (C) 2026 Dave Carey + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" + +/* BKBD encoding */ +#define BKBD_DETACHED 0 + +/* LENOVO_FEATURE_STATUS_DATA feature selector */ +#define YB9_FEATURE_STATUS_ID 0x00060000u + +/* + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. + * IDs is always 0x00060000; Status holds the BKBD value (0–3). + */ +struct lenovo_feature_status { + __le32 id; + __le32 status; +} __packed; + +/* ------------------------------------------------------------------ + * Notifier chain — event driver fires it, block driver listens + * ------------------------------------------------------------------ */ + +static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); + +static void devm_yb9_kbdock_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); +} + +static int devm_yb9_kbdock_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); +} + +/* ------------------------------------------------------------------ + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA + * (owns input_dev, sysfs, PM resume) + * ------------------------------------------------------------------ */ + +struct yb9_kbdock_data { + struct wmi_device *wdev; + struct input_dev *input_dev; + struct notifier_block nb; + spinlock_t lock; /* protects input_report_switch + input_sync */ +}; + +static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) +{ + struct wmi_buffer out; + int ret; + + ret = wmidev_query_block(d->wdev, 0, &out, + sizeof(struct lenovo_feature_status)); + if (ret) + return ret; + + struct lenovo_feature_status *fs __free(kfree) = out.data; + + if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) + return -EIO; + + *bkbd = le32_to_cpu(fs->status); + return 0; +} + +static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) +{ + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; + + spin_lock(&d->lock); + input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); + input_sync(d->input_dev); + spin_unlock(&d->lock); + dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); +} + +static int yb9_kbdock_sync(struct yb9_kbdock_data *d) +{ + u32 bkbd; + int ret; + + ret = yb9_kbdock_query(d, &bkbd); + if (ret) + return ret; + + yb9_kbdock_report(d, bkbd); + return 0; +} + +static int yb9_kbdock_nb_call(struct notifier_block *nb, + unsigned long bkbd, void *unused) +{ + struct yb9_kbdock_data *d = + container_of(nb, struct yb9_kbdock_data, nb); + + yb9_kbdock_report(d, bkbd); + return NOTIFY_DONE; +} + +static ssize_t keyboard_position_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct yb9_kbdock_data *d = dev_get_drvdata(dev); + u32 bkbd; + int ret; + + ret = yb9_kbdock_query(d, &bkbd); + if (ret) + return ret; + return sysfs_emit(buf, "%u\n", bkbd); +} +static DEVICE_ATTR_RO(keyboard_position); + +static const struct attribute * const yb9_kbdock_attrs[] = { + &dev_attr_keyboard_position.attr, + NULL, +}; +ATTRIBUTE_GROUPS(yb9_kbdock); + +static int yb9_kbdock_resume(struct device *dev) +{ + struct yb9_kbdock_data *d = dev_get_drvdata(dev); + + return yb9_kbdock_sync(d); +} +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); + +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) +{ + struct yb9_kbdock_data *d; + struct input_dev *input_dev; + int ret; + + d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->wdev = wdev; + spin_lock_init(&d->lock); + + input_dev = devm_input_allocate_device(&wdev->dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; + input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; + input_dev->id.bustype = BUS_HOST; + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); + + ret = input_register_device(input_dev); + if (ret) + return ret; + + d->input_dev = input_dev; + d->nb.notifier_call = yb9_kbdock_nb_call; + + ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); + if (ret) + return ret; + + dev_set_drvdata(&wdev->dev, d); + return yb9_kbdock_sync(d); +} + +static const struct wmi_device_id yb9_kbdock_block_id_table[] = { + { .guid_string = YB9_KBDOCK_QUERY_GUID }, + { } +}; + +static struct wmi_driver yb9_kbdock_block_driver = { + .driver = { + .name = "lenovo-yb9-kbdock", + .dev_groups = yb9_kbdock_groups, + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), + }, + .id_table = yb9_kbdock_block_id_table, + .no_singleton = true, + .probe = yb9_kbdock_block_probe, +}; + +/* ------------------------------------------------------------------ + * Event WMI driver — LENOVO_BTKBD_EVENT + * (fires the notifier chain on each WMI event) + * ------------------------------------------------------------------ */ + +static void yb9_kbdock_notify_new(struct wmi_device *wdev, + const struct wmi_buffer *data) +{ + /* + * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). + * Short-buffer guard is handled by .min_event_size below. + */ + u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); + + blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); +} + +static const struct wmi_device_id yb9_kbdock_event_id_table[] = { + { .guid_string = YB9_KBDOCK_EVENT_GUID }, + { } +}; +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); + +static struct wmi_driver yb9_kbdock_event_driver = { + .driver = { + .name = "lenovo-yb9-kbdock-event", + }, + .id_table = yb9_kbdock_event_id_table, + .no_singleton = true, + .notify_new = yb9_kbdock_notify_new, + .min_event_size = sizeof(__le32), +}; + +/* ------------------------------------------------------------------ + * Module init / exit + * ------------------------------------------------------------------ */ + +static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { + { + /* Lenovo Yoga Book 9 14IAH10 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), + }, + }, + { } +}; + +static int __init yb9_kbdock_init(void) +{ + int ret; + + if (!dmi_check_system(yb9_kbdock_dmi_table)) + return -ENODEV; + + ret = wmi_driver_register(&yb9_kbdock_event_driver); + if (ret) + return ret; + + ret = wmi_driver_register(&yb9_kbdock_block_driver); + if (ret) { + wmi_driver_unregister(&yb9_kbdock_event_driver); + return ret; + } + + return 0; +} +module_init(yb9_kbdock_init); + +static void __exit yb9_kbdock_exit(void) +{ + wmi_driver_unregister(&yb9_kbdock_block_driver); + wmi_driver_unregister(&yb9_kbdock_event_driver); +} +module_exit(yb9_kbdock_exit); + +MODULE_AUTHOR("Dave Carey "); +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); +MODULE_LICENSE("GPL"); From 31e1acf15df9a23ff3ead6dddf8ae1b7103d8b49 Mon Sep 17 00:00:00 2001 From: Riccardo Squarcialupi Date: Fri, 31 Jul 2026 14:54:31 +0200 Subject: [PATCH 117/152] platform/x86: samsung-galaxybook: Add SAMB430 device ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Samsung Galaxy Book6 Pro (NP944XJG-KG4IT) exposes its SCAI ACPI device with HID SAMB430, which is not in the driver's device ID table, so the driver never binds and none of its features are available. Add SAMB430 to galaxybook_device_ids[]. Tested on an NP944XJG-KG4IT by forcing the bind via driver_override, which is equivalent to an ID table match. All driver features probe successfully: keyboard backlight LED, battery charge control end threshold, platform profile (low-power/quiet/balanced/performance), firmware attributes (power_on_lid_open, usb_charging), and the camera lens cover input switch. One optional feature probe fails harmlessly on this model: "failed to execute CSFI; device responded with failure code 0xff". This does not affect any of the features listed above. Assisted-by: Claude:claude-opus-5 Signed-off-by: Riccardo Squarcialupi Link: https://patch.msgid.link/20260731125431.199902-1-rikysquarcia@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/samsung-galaxybook.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c index 6382af0b106c..99f72b204a51 100644 --- a/drivers/platform/x86/samsung-galaxybook.c +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { { "SAM0428" }, { "SAM0429" }, { "SAM0430" }, + { "SAMB430" }, {} }; MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); From 1b04f556e8428a3865a95627ab7a0acb5d9b93a5 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 28 Jul 2026 19:19:24 +0800 Subject: [PATCH 118/152] platform: arm64: qcom-hamoa-ec: reject incomplete responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qcom_ec_read() accepts short positive transfers, while both callers unconditionally consume every field in their fixed-size response. A short transfer can therefore make them use trailing stack bytes that were not returned by the device. The first response byte contains the number of payload bytes, excluding the byte count itself. A complete response of resp_len bytes must therefore report resp_len - 1 payload bytes. The existing check only rejects counts that do not fit in the response buffer and still accepts an incomplete payload. Require both the SMBus transfer length and the EC-provided payload count to match the expected response size. Fixes: 5c44f48e91de ("platform: arm64: Add driver for EC found on Qualcomm reference devices") Signed-off-by: Linmao Li Reviewed-by: Bryan O'Donoghue Reviewed-by: Anvesh Jain P Link: https://patch.msgid.link/20260728111924.4106898-1-lilinmao@kylinos.cn Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/qcom-hamoa-ec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c index 5ca7308c6077..4d2ad042a7f8 100644 --- a/drivers/platform/arm64/qcom-hamoa-ec.c +++ b/drivers/platform/arm64/qcom-hamoa-ec.c @@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) return ret; else if (ret == 0 || ret == 0xff) return -EOPNOTSUPP; + else if (ret != resp_len) + return -EIO; - if (resp[0] >= resp_len) + if (resp[0] != resp_len - 1) return -EINVAL; return 0; From 7a3db155e06275e0d302a243420739abe1f71b45 Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Mon, 17 Aug 2026 17:42:33 +0530 Subject: [PATCH 119/152] platform/x86: hp-wmi: Add OMEN board 8BAA thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 16-wf0xxx (board ID: 8BAA) has the same WMI interface as other OMEN boards and is compatible with the existing omen_v1_board_params. Add the DMI board name to hp_wmi_feature_boards[] table and map it to omen_v1_board_params. Without this entry, platform profile switching is unavailable, preventing fan RPM reporting and controlling. Tested on: HP OMEN 16-wf0xxx DMI Board Name: 8BAA It has been confirmed that the platform profile is registered successfully, and the fan RPMs are readable and controllable. Link: https://www.reddit.com/r/HPOmen/comments/1siukdu/guide_native_fan_control_on_hp_omen_16wfx0xxx/ Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260817121233.44636-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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 3c737ef3809c..5f4853d82de6 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -255,6 +255,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, + .driver_data = (void *)&omen_v1_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, .driver_data = (void *)&omen_v1_board_params, From edbad1e0e8c1ef33688615e809bd32f2f1aff140 Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Mon, 17 Aug 2026 20:22:06 +0530 Subject: [PATCH 120/152] platform/x86: hp-wmi: Add OMEN Transcend 16 8BB3 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN Transcend 16 (board ID: 8BB3) uses the existing OMEN v1 WMI interface 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 8BB3, including platform profile and fan control support. Tested on: HP OMEN Transcend 16-u0xxx DMI Board Name: 8BB3 Platform profile registration, fan RPM reporting, and PWM fan control have been verified on this board. Link: https://github.com/arfelious/omen-fan-control/commit/5d7a893432f1075ebb030a4eccdc929c35d68d97 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260817145206.148600-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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 5f4853d82de6..49321d7feb93 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -271,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, .driver_data = (void *)&victus_s_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, .driver_data = (void *)&victus_s_board_params, From 2fb7ed607f37c598708860928c0b362a98a4663d Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Tue, 18 Aug 2026 13:59:25 +0530 Subject: [PATCH 121/152] platform/x86: hp-wmi: Add OMEN board 8A43 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 16-n0xxx AMD (board ID: 8A43) 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 8A43. The board has been reported as working with this configuration in OmenCtl. Link: https://github.com/yunusemreyl/OmenCtl/commit/39d03b62028555d3014085f0d9cb3eb57a501871 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260818082925.14854-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(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 49321d7feb93..5551f8134cdb 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", + "8A15", "8A42", "8A43", "8BAD", "8C58", "8E41", From 5848eb9130ed0b3934f059fa06a53113637a43a4 Mon Sep 17 00:00:00 2001 From: Suryansh Singh Date: Tue, 18 Aug 2026 14:38:28 +0530 Subject: [PATCH 122/152] platform/x86: hp-wmi: Add OMEN board 8D88 thermal profile support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP OMEN 16 (board ID: 8D88) supports the existing OMEN thermal profile handling. Add the DMI board name to hp_wmi_feature_boards[] so that the existing thermal profile support is enabled for this board. This enables the existing fan control and platform profile handling for 8D88. The board has been reported as working with this configuration in OmenCtl. Link: https://github.com/yunusemreyl/OmenCtl/commit/e3cde3842bb2ffbd697592dc08a6043dc7cccfd0 Signed-off-by: Suryansh Singh Link: https://patch.msgid.link/20260818090828.27049-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(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 5551f8134cdb..b2773fc1aca4 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -331,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, .driver_data = (void *)&omen_v1_no_ec_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, + .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, From 28e5e682597abf1ef1422a93797c49c25cf0336b Mon Sep 17 00:00:00 2001 From: AbdElRahman Soliman Date: Sun, 16 Aug 2026 20:41:18 +0300 Subject: [PATCH 123/152] platform/x86: asus-armoury: add support for FX517ZR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DMI match and power-limit table entry for the ASUS TUF Dash F15 (2022), board FX517ZR, an Alder Lake + RTX 3070 laptop. AC and DC min/max values for ppt_pl1_spl, ppt_pl2_sppt, nv_dynamic_boost and nv_temp_target were referenced from ASUS Armoury Crate's manual performance-tuning mode on Windows for this exact model. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: AbdElRahman Soliman Link: https://patch.msgid.link/20260816174118.28012-1-abdelrahman7987@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(+) diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index bc899bec7943..46c308ba8b98 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -975,6 +975,32 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_max = 85, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), From 5ab078e3241da0beec2022254b5811a8a52cff84 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 14 Aug 2026 22:25:35 +0900 Subject: [PATCH 124/152] platform/x86: dell-wmi-sysman: Fix instance ID bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The get_instance_id() macro walks the per-type attribute array with 'i <= instances_count'. Each array is allocated with exactly instances_count entries, so the valid range is [0, instances_count) and the last iteration reads one element past the end. On a name miss that out-of-bounds attribute_name is handed to strcmp(), which reads on until it finds a NUL byte. Every kobject in these ksets is built from an entry that was populated, so a miss does not look reachable from sysfs today. The bound is wrong either way and the read is out of bounds. The matching macro in hp-bioscfg carried the same off-by-one and was corrected by commit 25150715e0b0 ("platform/x86: hp-bioscfg: Fix kernel panic in GET_INSTANCE_ID macro"). That macro takes a kobject pointer out of the out-of-bounds element and dereferences it, so it could fault. This one reads a char array. Use '<' to match the allocation. Fixes: e8a60aa7404b ("platform/x86: Introduce support for Systems Management Driver over WMI for Dell Systems") Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260814132535.4169956-1-sammiee5311@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h index 3bddedad5eba..eb48ced55823 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h @@ -107,7 +107,7 @@ enum { static int get_##type##_instance_id(struct kobject *kobj) \ { \ int i; \ - for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ return i; \ } \ From 329f10d8be193bf36af124e00b9dd6644cd71724 Mon Sep 17 00:00:00 2001 From: Hilgad Montelo Date: Thu, 13 Aug 2026 19:17:44 -0300 Subject: [PATCH 125/152] platform/x86: panasonic-laptop: Fix sentinel write past pcc->sinf[] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit acpi_pcc_retrieve_biosdata() rejects SINF packages only when pcc->num_sifr is strictly less than hkey->package.count, then unconditionally writes a trailing sentinel at pcc->sinf[hkey->package.count]. But pcc->sinf[] is allocated with exactly pcc->num_sifr elements (valid indices 0..num_sifr-1), so that write needs num_sifr strictly greater than package.count to stay in bounds -- num_sifr == package.count passes the existing check but still overflows by one element. This is exactly the case probe()'s existing num_sifr++ workaround ("Some DSDT-s have an off-by-one bug where the SINF package count is one higher than the SQTY reported value") is written to accommodate: when a DSDT's SINF package count equals SQTY+1, the workaround makes num_sifr equal to package.count, which is precisely the boundary that overflows here. Found via UBSan (array-index-out-of-bounds) on hardware where HKEY.SQTY returns 37 and HKEY.SINF()'s package has 38 elements: num_sifr becomes 38 after the += 1 workaround, the loop correctly fills indices 0..37, and the sentinel write then targets index 38, one past the end -- a silent 4-byte heap overflow on kernels without CONFIG_UBSAN. Tightening the rejection check to num_sifr <= package.count would avoid the overflow but breaks probe() entirely on exactly this hardware, since num_sifr == package.count is the case the off-by-one workaround exists to support. Nothing else in the driver reads this sentinel value back, so simply skip the write when there is no room for it instead. Fixes: a3d0dbd18ce9 ("platform/x86: panasonic-laptop: simplify allocation of sinf") Cc: stable@vger.kernel.org Signed-off-by: Hilgad Montelo Link: https://patch.msgid.link/20260813221744.25668-4-hilgad.montelo@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/panasonic-laptop.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 3effb11c4153..19d194ff37ca 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -358,7 +358,16 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc) } else pr_err("Invalid HKEY.SINF data\n"); } - pcc->sinf[hkey->package.count] = -1; + /* + * pcc->sinf[] has pcc->num_sifr elements (valid indices + * 0..num_sifr-1). On DSDTs where SINF's package count equals + * num_sifr exactly -- the off-by-one case probe()'s num_sifr++ + * already allocates a spare element for -- there is no room left + * for this trailing sentinel; nothing reads it back, so just skip + * the write rather than running one element past the flex array. + */ + if (hkey->package.count < pcc->num_sifr) + pcc->sinf[hkey->package.count] = -1; end: kfree(buffer.pointer); From dc03f05e419f3460342fb7564884f244622634b6 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:21 +0500 Subject: [PATCH 126/152] platform/x86: hp-bioscfg: fix off-by-one write in hp_get_string_from_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hp_get_string_from_buffer() clamps the converted string length against the destination buffer size with "size > dst_size", so when the converted length is exactly equal to dst_size, conv_dst_size is left at dst_size and the unconditional NUL terminator write dst[conv_dst_size] = 0; lands one byte past the destination buffer. This is the same shape of bug as the previously fixed off-by-one in hp_convert_hexstr_to_str(): the buffer is sized correctly for the content, but the terminator write is never checked against that size. Fix by changing the comparison to ">=" so conv_dst_size is always left with room for the terminator. All fixed-size destinations that reach this function (path[512], current_value[512], current_password/current_value[64], and the per-entry buffers in encodings[][512] and prerequisites[][512]) are affected. Fixes: a34fc329b189 ("platform/x86: hp-bioscfg: bioscfg") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-2-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(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 78019644ec35..309634c1cc20 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -85,7 +85,7 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_ * bytes. */ conv_dst_size = size; - if (size > dst_size) + if (size >= dst_size) conv_dst_size = dst_size - 1; /* From a7508c7959ff8d037327d377ed21a9c0eabe4674 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:22 +0500 Subject: [PATCH 127/152] platform/x86: hp-bioscfg: fix heap OOB read in sk_store() and kek_store() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sk_store() and kek_store() strip a trailing newline from the sysfs write before allocating the key buffer: length = count; if (buf[length - 1] == '\n') length--; bioscfg_drv.spm_data.signing_key = kmemdup(buf, length, GFP_KERNEL); but then pass the original "count" (not "length") as the copy size to hp_wmi_perform_query(), which memcpy()s that many bytes out of the "length"-sized allocation, reading one byte past it whenever the write ends in a newline, the normal case for a shell "echo" into sysfs. KASAN confirms this directly: BUG: KASAN: slab-out-of-bounds in hp_wmi_perform_query+0x1e9/0x460 [hp_bioscfg] Read of size 28 at addr ffff88813c8e2b80 by task python3/16022 ... sk_store+0xa7/0x240 [hp_bioscfg] kernfs_fop_write_iter+0x3e1/0x5d0 ... The buggy address is located 0 bytes inside of allocated 27-byte region [ffff88813c8e2b80, ffff88813c8e2b9b) Reproduced identically for kek_store, and at multiple write sizes (28, 57, 201 bytes), each time reading exactly one byte past a kmemdup() allocation one byte smaller than the write. Fix by passing "length" instead of "count" to hp_wmi_perform_query() in both functions. Fixes: b2715aa2e135 ("platform/x86: hp-bioscfg: spmobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-3-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index 2b00a14792e9..4d94e48c1a4c 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -238,7 +238,7 @@ static ssize_t sk_store(struct kobject *kobj, ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK, HPWMI_SECUREPLATFORM, (void *)bioscfg_drv.spm_data.signing_key, - count, 0); + length, 0); if (!ret) { bioscfg_drv.spm_data.mechanism = SIGNING_KEY; @@ -274,7 +274,7 @@ static ssize_t kek_store(struct kobject *kobj, ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK, HPWMI_SECUREPLATFORM, (void *)bioscfg_drv.spm_data.endorsement_key, - count, 0); + length, 0); if (!ret) { bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY; From 2b2ec354f905c14e3270e8ec3ab50f7d8ad73bab Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:23 +0500 Subject: [PATCH 128/152] platform/x86: hp-bioscfg: fix heap OOB read on empty password write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validate_password_input() computes length = strlen(buf) and then checks buf[length - 1] to strip a trailing newline, without checking that length is nonzero first. Writing an empty string (a bare '\n') to current_password or new_password gives length == 0, and buf[length - 1] reads buf[-1], one byte before the heap allocation holding the copied input. KASAN confirms this directly: BUG: KASAN: slab-out-of-bounds in store_password_instance.constprop.0+0x223/0x2a0 [hp_bioscfg] Read of size 1 at addr ffff88811bd8da9f by task sh/13740 ... store_password_instance.constprop.0+0x223/0x2a0 [hp_bioscfg] current_password_store+0x14/0x20 [hp_bioscfg] ... The buggy address is located 23 bytes to the right of allocated 8-byte region [ffff88811bd8da80, ffff88811bd8da88) Reproduced identically via new_password_store. Execution continues past the bad read (the garbage byte only affects whether "length" is decremented by one), so the write completes and returns success; this is a pure information read past the buffer, not a crash, but it is still an out-of-bounds access KASAN correctly flags. Fix by only checking buf[length - 1] when length is nonzero. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-4-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(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index ed5e2080f22b..010786ac000d 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -66,7 +66,7 @@ static int validate_password_input(int instance_id, const char *buf) struct password_data *password_data = &bioscfg_drv.password_data[instance_id]; length = strlen(buf); - if (buf[length - 1] == '\n') + if (length > 0 && buf[length - 1] == '\n') length--; if (length > MAX_PASSWD_SIZE) From e213939ed9e6e6badf7aa48c4c8dd9a9cdf00615 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 17:09:37 +0800 Subject: [PATCH 129/152] platform/x86: hp-bioscfg: fix password encoding bounds check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The password PSWD_ENCODINGS parser reads password_obj[elem + pos_values] while copying the supported password encodings from the ACPI package. The outer loop only guarantees that elem is within password_obj_count. The encoding count is bounded by MAX_ENCODINGS_SIZE, but that does not guarantee that the ACPI package contains enough entries for all elem + pos_values accesses. A malformed package can therefore declare a non-zero encoding count without providing enough string objects, causing the parser to read past the ACPI package array and pass an out-of-bounds string pointer and length to hp_convert_hexstr_to_str(). Add the same computed-index bounds check used by the other offset-based package parsing loops before reading password_obj[elem + pos_values]. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260708090937.740435-1-lgs201920130244@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 010786ac000d..0de0626a2803 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -351,6 +351,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor case PSWD_ENCODINGS: size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); for (pos_values = 0; pos_values < size; pos_values++) { + if (elem + pos_values >= password_obj_count) { + pr_err("Error elem-objects package is too small\n"); + return -EINVAL; + } + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, password_obj[elem + pos_values].string.length, &str_value, &value_len); From 2ea12a467a9cb12170417b30784fe26a243a75fe Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:27 +0500 Subject: [PATCH 130/152] platform/x86: hp-bioscfg: fix new_password_store() overwriting current_password MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit current_password_store() and new_password_store() both call store_password_instance() with is_current = true: static ssize_t new_password_store(...) { return store_password_instance(kobj, buf, count, true); } so a write to new_password is routed to current_password instead, and the new_password field is never written by either sysfs entry point. Fix by passing false from new_password_store(), matching what the is_current parameter is meant to select. Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-8-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(-) diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index 0de0626a2803..b0f41ee36125 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -123,7 +123,7 @@ static ssize_t new_password_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { - return store_password_instance(kobj, buf, count, true); + return store_password_instance(kobj, buf, count, false); } static struct kobj_attribute password_new_password = __ATTR_WO(new_password); From cb6b1b0fb236a9581cae213c2a9182e68cc3ffe5 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:28 +0500 Subject: [PATCH 131/152] platform/x86: hp-bioscfg: fix ORD_LIST_ELEMENTS never being parsed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI_TYPE_STRING case explicitly skips the string conversion for elem == ORD_LIST_ELEMENTS: if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) { ret = hp_convert_hexstr_to_str(..., &str_value, &value_len); if (ret) continue; } so by the time the ORD_LIST_ELEMENTS case in the eloc switch runs, str_value is NULL (it was freed and reset to NULL at the end of the previous iteration). That case then does: ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len); hp_convert_hexstr_to_str() rejects a NULL input with -EINVAL, which sends this function to exit_list, and exit_list unconditionally returns 0. The net effect is that any ordered-list attribute with elements present silently ends up with an empty elements list, with no error surfaced anywhere. Fix by converting the current element directly, order_obj[elem], the same way the PREREQUISITES case already handles its own array elements, instead of reusing the unrelated str_value/value_len left over from earlier processing. Fixes: 4b2672ec71a3 ("platform/x86: hp-bioscfg: order-list-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-9-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 863e486474ad..3e2f61ec6b33 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -261,7 +261,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord * Ordered list data is stored in hex and comma separated format * Convert the data and split it to show each element */ - ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len); + ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer, + order_obj[elem].string.length, + &tmpstr, &tmp_len); if (ret) goto exit_list; From 05c808362e808e196f75696b8a64f7aa8b2245ce Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 12 Aug 2026 16:18:29 +0500 Subject: [PATCH 132/152] platform/x86: hp-bioscfg: advance elem past consumed array elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The outer parsing loop in each attribute-type parser advances "elem" (the index into the ACPI package element array) by exactly one per iteration, but cases that consume multi-element arrays (PREREQUISITES, ENUM_POSSIBLE_VALUES, PSWD_ENCODINGS) read "size" consecutive elements without adjusting "elem" for the extra entries consumed beyond the first. The next outer iteration then re-reads a leftover element from the array just consumed instead of the next real property, and the type check fails on that stale element, aborting the parse with -EIO. This produces exactly the failure visible in dmesg on the test hardware, on every boot: Error expected type 2 for elem 13, but got type 1 instead hp_bioscfg: Returned error 0x3, "Invalid command value/Feature not supported" Fix by advancing "elem" by (size - 1) after each array-consuming loop, so the outer loop's own "elem++" lands on the correct next element. "eloc" is intentionally left alone: it indexes the logical property schema, not the physical element array, and each array case is still exactly one logical property regardless of how many physical elements it spans. The defect is identical across all five attribute-type parsers (enum, integer, string, ordered-list, password), which were copy-pasted from the same template when the driver was introduced. Fixes: 6b2770bfd6f9 ("platform/x86: hp-bioscfg: enum-attributes") Fixes: 6f2c06d5a467 ("platform/x86: hp-bioscfg: int-attributes") Fixes: e6c7b3e15559 ("platform/x86: hp-bioscfg: string-attributes") Fixes: 4b2672ec71a3 ("platform/x86: hp-bioscfg: order-list-attributes") Fixes: 8646a3b5ee3a ("platform/x86: hp-bioscfg: passwdobj-attributes") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260812111829.172273-10-meatuni001@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c | 4 ++++ drivers/platform/x86/hp/hp-bioscfg/int-attributes.c | 2 ++ drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c | 2 ++ drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c | 4 ++++ drivers/platform/x86/hp/hp-bioscfg/string-attributes.c | 2 ++ 5 files changed, 14 insertions(+) diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index 21077d17113b..446dd18d2cee 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -228,6 +228,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: @@ -281,6 +283,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum kfree(str_value); str_value = NULL; } + if (size) + elem += (size < MAX_VALUES_SIZE ? size : MAX_VALUES_SIZE) - 1; break; default: pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem); diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index f2fd966c9ca4..a27907066448 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -243,6 +243,8 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_ kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: 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 3e2f61ec6b33..5bf8d40bdf81 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -232,6 +232,8 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index b0f41ee36125..a9e178637416 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -321,6 +321,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: password_data->common.security_level = int_value; @@ -367,6 +369,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor str_value = NULL; } + if (size) + elem += size - 1; break; case PSWD_IS_SET: password_data->is_enabled = 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 f98c32dacbc7..f3dfba270f7e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -233,6 +233,8 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob kfree(str_value); str_value = NULL; } + if (size) + elem += size - 1; break; case SECURITY_LEVEL: From 3921bb8635ff2836622df1cdf3194d4f3c1835a4 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Wed, 12 Aug 2026 14:30:12 +0530 Subject: [PATCH 133/152] platform/x86/amd/hsmp: Reject negative power cap writes in hwmon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_hwmon_write() takes the user-supplied hwmon value as a signed long and assigns "val / MICROWATT_PER_MILLIWATT" to msg.args[0], which is a __u32. MICROWATT_PER_MILLIWATT is an unsigned long, so a negative write to power1_cap (e.g. "echo -1 > power1_cap") is first converted to a huge unsigned value by the division and then stored into the u32 argument. As a result a nonsensical, multi-gigawatt socket power limit is sent to the SMU via HSMP_SET_SOCKET_POWER_LIMIT instead of the write being rejected. Reject negative values with -EINVAL before the conversion. Tested with HSMP enabled: CAP=$(dirname $(grep -l amd_hsmp_hwmon \ /sys/class/hwmon/hwmon*/name | head -1))/power1_cap # negative write echo -1000000 > $CAP ; echo "ret=$?" # valid positive write must still work echo 400000000 > $CAP ; echo "ret=$?" Before: # echo -1000000 > $CAP ; echo "ret=$?" ret=0 <- accepted; bogus limit sent to SMU # echo 400000000 > $CAP ; echo "ret=$?" ret=0 After: # echo -1000000 > $CAP ; echo "ret=$?" bash: echo: write error: Invalid argument ret=1 <- rejected with -EINVAL # echo 400000000 > $CAP ; echo "ret=$?" ret=0 <- valid write still works Fixes: 92c025db52bb ("platform/x86/amd/hsmp: Report power via hwmon sensors") Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260812090012.140193-1-hemanth.selam@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hwmon.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c index 0cc9a742497f..c8314eee06f4 100644 --- a/drivers/platform/x86/amd/hsmp/hwmon.c +++ b/drivers/platform/x86/amd/hsmp/hwmon.c @@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, if (attr != hwmon_power_cap) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + msg.num_args = 1; msg.args[0] = val / MICROWATT_PER_MILLIWATT; msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; From a89f07db0cb95c54dac4a8406c79a04e44a73c3c Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 7 Aug 2026 23:40:02 +0900 Subject: [PATCH 134/152] platform/x86: ISST: Validate socket ID in clos_assoc ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isst_if_clos_assoc() validates the user-supplied socket_id with 'socket_id > topology_max_packages()', but isst_common.sst_inst[] is allocated with topology_max_packages() entries, so the valid index range is [0, topology_max_packages()). The '>' comparison lets socket_id == topology_max_packages() pass and index one entry past the array. In addition, isst_common.sst_inst[socket_id] is NULL for an in-range package that has no bound TPMI SST instance, and the pointer is used without a NULL check. Both the out-of-bounds entry and the NULL pointer are then dereferenced by map_partition_power_domain_id() and the following power_domain_info access. Reject socket_id >= topology_max_packages() and a NULL sst_inst, matching the checks already performed by get_instance(). Fixes: 12a7d2cb811d ("platform/x86: ISST: Add SST-CP support via TPMI") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260807144003.3498972-2-sammiee5311@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 24334ae70d82..b2965baeaa36 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -729,7 +729,7 @@ static long isst_if_clos_assoc(void __user *argp) if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc))) return -EFAULT; - if (clos_assoc.socket_id > topology_max_packages()) + if (clos_assoc.socket_id >= topology_max_packages()) return -EINVAL; cpu = clos_assoc.logical_cpu; @@ -747,6 +747,8 @@ static long isst_if_clos_assoc(void __user *argp) pkg_id = clos_assoc.socket_id; sst_inst = isst_common.sst_inst[pkg_id]; + if (!sst_inst) + return -EINVAL; punit_id = map_partition_power_domain_id(sst_inst, punit_id, &part); if (punit_id < 0) From 80e0d353c86a9a168ad6d213f494796294381538 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Fri, 7 Aug 2026 23:40:03 +0900 Subject: [PATCH 135/152] platform/x86: ISST: Validate level in perf mask ioctls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isst_if_get_perf_level_mask() and isst_if_get_base_freq_mask() use the user-provided level as an index into perf_levels[] via _read_pp_level_info() and _read_bf_level_info(), but neither helper validates it first. The adjacent level-info helpers reject levels above max_level before reading the same per-level register block. Add the same bounds checks to the mask helpers, and reject disabled SST-PP levels in isst_if_get_perf_level_mask() to match isst_if_get_perf_level_info(). This prevents out-of-bounds reads from the per-level offset table on invalid ioctl input. Fixes: ea009e4769fa3 ("platform/x86: ISST: Add SST-PP support via TPMI") Fixes: 06a61df83209 ("platform/x86: ISST: Add SST-BF support via TPMI") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260807144003.3498972-3-sammiee5311@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/intel/speed_select_if/isst_tpmi_core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index b2965baeaa36..f1b6109521c5 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -1263,6 +1263,12 @@ static int isst_if_get_perf_level_mask(void __user *argp) if (!power_domain_info) return -EINVAL; + if (cpumask.level > power_domain_info->max_level) + return -EINVAL; + + if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level))) + return -EINVAL; + _read_pp_level_info("mask", mask, cpumask.level, SST_PP_INFO_2_OFFSET, SST_PP_RSLVD_CORE_MASK_START, SST_PP_RSLVD_CORE_MASK_WIDTH, SST_MUL_FACTOR_NONE) @@ -1344,6 +1350,9 @@ static int isst_if_get_base_freq_mask(void __user *argp) if (!power_domain_info) return -EINVAL; + if (cpumask.level > power_domain_info->max_level) + return -EINVAL; + _read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET, P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH, SST_MUL_FACTOR_NONE) From 124e2dbabe460c2a6e7440f4ad8af560131295c9 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:09 -0700 Subject: [PATCH 136/152] platform/x86: ISST: Validate logical CPU id and clos id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate max CLOS ID and logical CPU ID for core power feature. Reject any clos level or logical CPU number greater than the supported maximum. These are used to calculate MMIO offset. Fixes: 12a7d2cb811d ("platform/x86: ISST: Add SST-CP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-2-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index f1b6109521c5..a5ab3be92afa 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -649,6 +649,8 @@ static long isst_if_core_power_state(void __user *argp) #define SST_CLOS_CONFIG_MAX_START 16 #define SST_CLOS_CONFIG_MAX_WIDTH 8 +#define SST_MAX_CLOS 3 + static long isst_if_clos_param(void __user *argp) { struct tpmi_per_power_domain_info *power_domain_info; @@ -657,6 +659,9 @@ static long isst_if_clos_param(void __user *argp) if (copy_from_user(&clos_param, argp, sizeof(clos_param))) return -EFAULT; + if (clos_param.clos > SST_MAX_CLOS) + return -EINVAL; + power_domain_info = get_instance(clos_param.socket_id, clos_param.power_domain_id); if (!power_domain_info) return -EINVAL; @@ -703,6 +708,8 @@ static long isst_if_clos_param(void __user *argp) #define SST_CLOS_ASSOC_CPUS_PER_REG 16 #define SST_CLOS_ASSOC_BITS_PER_CPU 4 +#define SST_CLOS_ASSOC_MAX_LOGICAL_CPU 63 + static long isst_if_clos_assoc(void __user *argp) { struct isst_if_clos_assoc_cmds assoc_cmds; @@ -729,9 +736,15 @@ static long isst_if_clos_assoc(void __user *argp) if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc))) return -EFAULT; + if (clos_assoc.clos > SST_MAX_CLOS) + return -EINVAL; + if (clos_assoc.socket_id >= topology_max_packages()) return -EINVAL; + if (clos_assoc.logical_cpu > SST_CLOS_ASSOC_MAX_LOGICAL_CPU) + return -EINVAL; + cpu = clos_assoc.logical_cpu; clos = clos_assoc.clos; From e45d6b8472861d3bac86bb37f8556a7c5aca3266 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:10 -0700 Subject: [PATCH 137/152] platform/x86: ISST: Validate max level for set feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate the level before setting, so that it fails early instead of failing later when checking the bit mask for allowed levels. Fixes: ea009e4769fa3 ("platform/x86: ISST: Add SST-PP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-3-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index a5ab3be92afa..7be50d4167be 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -947,6 +947,9 @@ static int isst_if_set_perf_level(void __user *argp) if (!power_domain_info) return -EINVAL; + if (perf_level.level > power_domain_info->max_level) + return -EINVAL; + if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; From 1700b4f804555467b7eff58dff7acc11d508b3a1 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:11 -0700 Subject: [PATCH 138/152] platform/x86: ISST: Validate parameter for core power state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow only 0 or 1 for core_power enable and priority_type parameters. Fixes: 12a7d2cb811d ("platform/x86: ISST: Add SST-CP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-4-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/intel/speed_select_if/isst_tpmi_core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 7be50d4167be..8f63694da897 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -599,6 +599,9 @@ static bool disable_dynamic_sst_features(void) #define SST_CP_PRIORITY_TYPE_START 1 #define SST_CP_PRIORITY_TYPE_WIDTH 1 +#define SST_CP_MAX_ENABLE 1 +#define SST_CP_MAX_PRIORITY_TYPE 1 + static long isst_if_core_power_state(void __user *argp) { struct tpmi_per_power_domain_info *power_domain_info; @@ -618,6 +621,10 @@ static long isst_if_core_power_state(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (core_power.enable > SST_CP_MAX_ENABLE || + core_power.priority_type > SST_CP_MAX_PRIORITY_TYPE) + return -EINVAL; + _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET, SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE) _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET, From 574b59bb4b6bfcfd1f639d02f1041b314d43a2e6 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:12 -0700 Subject: [PATCH 139/152] platform/x86: ISST: Validate parameter for frequency and priority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate range for frequency and proportional priority while setting CLOS parameters. Fixes: 12a7d2cb811d ("platform/x86: ISST: Add SST-CP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-5-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 8f63694da897..ee2e1ec1b103 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -658,6 +658,9 @@ static long isst_if_core_power_state(void __user *argp) #define SST_MAX_CLOS 3 +#define SST_MAX_FREQ 0xff +#define SST_CLOS_MAX_PRIORITY 0x0f + static long isst_if_clos_param(void __user *argp) { struct tpmi_per_power_domain_info *power_domain_info; @@ -677,6 +680,15 @@ static long isst_if_clos_param(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (!in_range(clos_param.min_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1)) + return -EINVAL; + + if (!in_range(clos_param.max_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1)) + return -EINVAL; + + if (!in_range(clos_param.prop_prio, 0, SST_CLOS_MAX_PRIORITY + 1)) + return -EINVAL; + _write_cp_info("clos.min_freq", clos_param.min_freq_mhz, (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE), SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH, From 9b9026943b19d06ebf520b1f4786621947cf43c8 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:13 -0700 Subject: [PATCH 140/152] platform/x86: ISST: Use PP level enable mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add check for enabled levels only when reading MMIO. Some levels can be disabled by BIOS. If the level is not enabled, return an error. Reset the enable and allowed level masks if there is a failure to add a perf level. Fixes: ea009e4769fa3 ("platform/x86: ISST: Add SST-PP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-6-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index ee2e1ec1b103..f1b35714a4e9 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -336,8 +336,11 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, int i; pd_info->perf_levels = devm_kcalloc(dev, levels, sizeof(struct perf_level), GFP_KERNEL); - if (!pd_info->perf_levels) + if (!pd_info->perf_levels) { + pd_info->pp_header.allowed_level_mask = 0; + pd_info->pp_header.level_en_mask = 0; return 0; + } pd_info->ratio_unit = pd_info->pp_header.ratio_unit; pd_info->avx_levels = SST_MAX_AVX_LEVELS; @@ -911,7 +914,7 @@ static int isst_if_get_perf_level(void __user *argp) SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE) perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1)); - level_mask = perf_level.level_mask; + level_mask = perf_level.level_mask & power_domain_info->pp_header.level_en_mask; perf_level.sst_bf_support = 0; for_each_set_bit(level, &level_mask, BITS_PER_BYTE) { /* @@ -1349,6 +1352,9 @@ static int isst_if_get_base_freq_info(void __user *argp) if (base_freq.level > power_domain_info->max_level) return -EINVAL; + if (!(power_domain_info->pp_header.level_en_mask & BIT(base_freq.level))) + return -EINVAL; + _read_bf_level_info("p1_high", base_freq.high_base_freq_mhz, base_freq.level, SST_BF_INFO_0_OFFSET, SST_BF_P1_HIGH_START, SST_BF_P1_HIGH_WIDTH, SST_MUL_FACTOR_FREQ) @@ -1388,6 +1394,9 @@ static int isst_if_get_base_freq_mask(void __user *argp) if (cpumask.level > power_domain_info->max_level) return -EINVAL; + if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level))) + return -EINVAL; + _read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET, P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH, SST_MUL_FACTOR_NONE) @@ -1483,6 +1492,9 @@ static int isst_if_get_turbo_freq_info(void __user *argp) if (turbo_freq.level > power_domain_info->max_level) return -EINVAL; + if (!(power_domain_info->pp_header.level_en_mask & BIT(turbo_freq.level))) + return -EINVAL; + turbo_freq.max_buckets = TRL_MAX_BUCKETS; turbo_freq.max_trl_levels = TRL_MAX_LEVELS; turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS; From 0f377f2b47646abe6ec3616ae6a8670d9ff7eb86 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:15:14 -0700 Subject: [PATCH 141/152] platform/x86: ISST: Just allow 2 bits for SST feature enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently only 2 features SST-TF and SST-BF are supported, so only allow bit 0 and bit 1. Fixes: ea009e4769fa3 ("platform/x86: ISST: Add SST-PP support via TPMI") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811221514.3905817-7-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index f1b35714a4e9..92d420e706f6 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -882,6 +882,7 @@ static long isst_if_clos_assoc(void __user *argp) #define SST_PP_FEATURE_STATE_START 8 #define SST_PP_FEATURE_STATE_WIDTH 8 +#define SST_PP_FEATURE_STATE_VALID_MASK GENMASK(1, 0) #define SST_BF_FEATURE_SUPPORTED_START 12 #define SST_BF_FEATURE_SUPPORTED_WIDTH 1 @@ -1035,6 +1036,9 @@ static int isst_if_set_perf_feature(void __user *argp) if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN)) return -EPERM; + if (perf_feature.feature & ~SST_PP_FEATURE_STATE_VALID_MASK) + return -EINVAL; + _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET, SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE) From f9a647cb8d90c09633a49a1e766e140e78012444 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:21:33 -0700 Subject: [PATCH 142/152] platform/x86: ISST: Return error during profile addition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If sst_add_perf_profiles() fails for memory allocation, it continues to allow SST-CP (core-power) feature. But in practice this is not very useful as to achieve some frequencies via SST-CP, an SST-PP (perf-profile) level change is required. Fixes: 0ab147bb840f ("platform/x86: ISST: Parse SST MMIO and update instance") Cc: HyeongJun An Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811222134.3912626-2-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/intel/speed_select_if/isst_tpmi_core.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 92d420e706f6..a50e3aa8c4d2 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -339,7 +339,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, if (!pd_info->perf_levels) { pd_info->pp_header.allowed_level_mask = 0; pd_info->pp_header.level_en_mask = 0; - return 0; + return -ENOMEM; } pd_info->ratio_unit = pd_info->pp_header.ratio_unit; @@ -370,7 +370,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev, static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info) { struct device *dev = &auxdev->dev; - int i, mask, levels; + int i, ret, mask, levels; *((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base); pd_info->sst_header.cp_offset *= 8; @@ -402,8 +402,12 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai levels = i; mask <<= 1; } + + ret = sst_add_perf_profiles(auxdev, pd_info, levels + 1); + if (ret) + return ret; + pd_info->max_level = levels; - sst_add_perf_profiles(auxdev, pd_info, levels + 1); return 0; } From 3de2776e9d7073765c10c2326c2bda5926811ea6 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 11 Aug 2026 15:21:34 -0700 Subject: [PATCH 143/152] platform/x86: ISST: Add a NULL check for sst_inst[] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To be consistent with other places, add a NULL check for failed socket loading by checking isst_common.sst_inst[]. Fixes: d805456c712f ("platform/x86: ISST: Enumerate TPMI SST and create framework") Cc: stable@vger.kernel.org Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260811222134.3912626-3-srinivas.pandruvada@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index a50e3aa8c4d2..66dd974720be 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -1433,6 +1433,8 @@ static int isst_if_get_tpmi_instance_count(void __user *argp) return -EINVAL; sst_inst = isst_common.sst_inst[tpmi_inst.socket_id]; + if (!sst_inst) + return -EINVAL; tpmi_inst.count = isst_instance_count(sst_inst); From abca989604f60fe29d7170431f819e28ec7d868a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 10 Aug 2026 22:41:07 +0200 Subject: [PATCH 144/152] platform/x86: think-lmi: Free system certificate signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multi-certificate support also allows the system authentication object to store ->signature and ->save_signature, which leak when the driver is removed. Free the signatures to avoid leaking memory. Fixes: 5dcb5ef12590 ("platform/x86: think-lmi: Multi-certificate support") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260810204106.165895-2-thorsten.blum@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index e215e86e3db7..e3e683bf3469 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -1456,6 +1456,10 @@ static void tlmi_release_attr(void) /* Free up any saved signatures */ kfree(tlmi_priv.pwd_admin->signature); kfree(tlmi_priv.pwd_admin->save_signature); + if (tlmi_priv.pwd_system) { + kfree(tlmi_priv.pwd_system->signature); + kfree(tlmi_priv.pwd_system->save_signature); + } /* Authentication structures */ list_for_each_entry_safe(pos, n, &tlmi_priv.authentication_kset->list, entry) From 46b14c6f11f558362391e308f4f184ee867ef58f Mon Sep 17 00:00:00 2001 From: Nikolay Kulikov Date: Mon, 10 Aug 2026 22:10:02 +0300 Subject: [PATCH 145/152] mlxbf-bootctl: fix the build error with FIELD_PREP() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rsh_log_store() calls the FIELD_PREP() macro without including the required header file, resulting a build error: CC drivers/platform/mellanox/mlxbf-bootctl.o drivers/platform/mellanox/mlxbf-bootctl.c: In function ‘rsh_log_store’: drivers/platform/mellanox/mlxbf-bootctl.c:429:16: error: implicit declaration of function ‘FIELD_PREP’ [-Wimplicit-function-declaration] 429 | data = FIELD_PREP(MLXBF_RSH_LOG_TYPE_MASK, MLXBF_RSH_LOG_TYPE_MSG); | ^~~~~~~~~~ Fix this by including the file. Fixes: e9d1b2d0f7d0 ("mlxbf-bootctl: Add sysfs file for BlueField boot log") Signed-off-by: Nikolay Kulikov Link: https://patch.msgid.link/20260810-mellanox_fix_implicit_declaration-v1-1-352e647b8f28@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/mellanox/mlxbf-bootctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c index f67c7f56ab2b..9ddc7fa1a973 100644 --- a/drivers/platform/mellanox/mlxbf-bootctl.c +++ b/drivers/platform/mellanox/mlxbf-bootctl.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include From 4f3183f5ae9b8ddfe338d79a96146a05342bbe50 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 10 Aug 2026 14:05:57 +0200 Subject: [PATCH 146/152] platform/x86: think-lmi: Fix certificate thumbprint sysfs output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cert_thumbprint() already returns the accumulated output length, but certificate_thumbprint_show() adds that value to count again, making the next line use the wrong offset. Errors returned by cert_thumbprint() are also ignored and their negative values added to count. Assign the total length to count instead and propagate errors correctly. Fixes: b49f72e7f96d ("platform/x86: think-lmi: Certificate authentication support") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260810120556.149416-2-thorsten.blum@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index e3e683bf3469..ca266657e16c 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -745,6 +745,8 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att return -EOPNOTSUPP; for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) { + ssize_t ret; + if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) { /* Format: 'SVC | SMC, Thumbtype' */ wmistr = kasprintf(GFP_KERNEL, "%s,%s", @@ -756,8 +758,12 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att } if (!wmistr) return -ENOMEM; - count += cert_thumbprint(buf, wmistr, count); + + ret = cert_thumbprint(buf, wmistr, count); kfree(wmistr); + if (ret < 0) + return ret; + count = ret; } return count; From 0b30e6e583b2988864ffa7aeb2905bc2d0c7d575 Mon Sep 17 00:00:00 2001 From: Yaroslav Dudkov Date: Fri, 7 Aug 2026 18:05:53 +0000 Subject: [PATCH 147/152] platform/x86: msi-ec: Add MSI Katana GF76 11UEK EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the firmware string '17L1EMS1.107' to the ALLOWED_FW_13 array. This enables Embedded Controller support, including battery charge thresholds, for the MSI Katana GF76 11UEK (MS-17L1) laptop. Tested on MSI Katana GF76 11UEK with EC firmware 17L1EMS1.107 and BIOS E17L1IMS.312. Signed-off-by: Yaroslav Dudkov Link: https://patch.msgid.link/20260807180553.869371-1-aroslavdudkov622@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(+) diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 667df9509798..566dfc73cb67 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { static const char * const ALLOWED_FW_13[] __initconst = { "1594EMS1.109", // MSI Prestige 16 Studio A13VE + "17L1EMS1.107", // MSI Katana GF76 11UEK NULL }; From 8d840dad456f88374d8a3b03851452a280d6ee50 Mon Sep 17 00:00:00 2001 From: Andrew Onyshchuk Date: Thu, 6 Aug 2026 08:44:17 -0700 Subject: [PATCH 148/152] platform/x86: thinkpad_acpi: Fix fan speed reporting on Edge E330 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ThinkPad Edge E330 with H3 firmware uses the non-standard EC fan register block. Without a matching quirk, thinkpad_acpi reads the legacy tachometer registers and reports 0 RPM. Add the H3 BIOS family to TPACPI_FAN_NS so the driver reads the fan period from EC register 0x95 using the existing non-standard reporting path. Tested on a ThinkPad Edge E330 with BIOS H3ET77WW and EC H3EC35WW. Signed-off-by: Andrew Onyshchuk Reviewed-by: Mark Pearson Link: https://patch.msgid.link/20260806154417.618575-1-andryk.rv@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(+) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index efc7784f11d1..60a2312f7d6b 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -8845,6 +8845,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_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 */ TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ From 1b3c0028dc060d760791638b770396c8998f7306 Mon Sep 17 00:00:00 2001 From: Jeff Hagadorn Date: Wed, 5 Aug 2026 11:31:02 -0700 Subject: [PATCH 149/152] platform/x86: oxpec: Add support for OneXPlayer X2 Mini Pro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OneXPlayer X2 Mini Pro is a new Strix Halo handheld. It ships the same system board as the OneXPlayer APEX and uses the same registers as the OneXPlayer Fly devices. Add a quirk for it to the oxpec driver. Signed-off-by: Jeff Hagadorn Reviewed-by: Antheas Kapenekakis Link: https://patch.msgid.link/20260805183102.38408-1-jeff@aletheia.io Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/oxpec.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 99c0dfcf393b..34bb17fca148 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_x1, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), + }, + .driver_data = (void *)oxp_fly, + }, {}, }; From 08ce055a42446b2796e1b12e645eefa673b74af9 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 30 Jul 2026 14:05:41 -0700 Subject: [PATCH 150/152] MAINTAINERS: update Intel PMC Core maintainer contact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Intel PMC Core Driver maintainer entry with the current maintainer email contact. Signed-off-by: David E. Box Signed-off-by: Xi Pardee Link: https://patch.msgid.link/20260730210546.3936101-1-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 8abf559a138c..285043264d9f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13302,7 +13302,7 @@ F: drivers/platform/x86/intel/punit_ipc.c INTEL PMC CORE DRIVER M: Rajneesh Bhardwaj -M: David E Box +M: Xi Pardee L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-platform-intel-pmc From e0d6312578e1fd03738fc2ac8ac21c8bd84e965e Mon Sep 17 00:00:00 2001 From: Musaev Ibragim Date: Wed, 15 Jul 2026 00:45:36 +0600 Subject: [PATCH 151/152] platform/x86: redmi-wmi: report EC state change events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Redmibook EC/firmware fully handles the keyboard backlight cycle, the OEM preset power mode (Fn+K) and the Fn lock toggle by itself, and sends a WMI event carrying the resulting state in the third payload byte. These events are currently swallowed with KE_IGNORE, so userspace never learns that the state changed and cannot give the user any feedback (OSD), even though the WMI event is the only notification channel for these EC-driven changes. Report them as key presses instead: - keyboard backlight cycle -> KEY_KBDILLUMTOGGLE - OEM preset power mode -> KEY_PERFORMANCE - Fn lock toggle -> KEY_FN_ESC Desktops that only look at the keycode get the usual hotkey behaviour; since sparse-keymap emits MSC_SCAN with the raw payload before the key event, an OSD daemon can additionally recover the exact new state from byte 2 (e.g. backlight Off/Low/High/Auto is 0x00/0x05/0x0a/0x80). Note that the power mode event must keep being read from the WMI device in any case: on the TM2209 the ACPI event handler (EV20) applies the mode change as a side effect of building the event payload for _WED. Tested on Redmi Book Pro 15 2023 (TM2209). Signed-off-by: Musaev Ibragim Link: https://patch.msgid.link/178405473606.25865.4048095379503614221@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/redmi-wmi.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c index 58898630eda6..cc82ef502002 100644 --- a/drivers/platform/x86/redmi-wmi.c +++ b/drivers/platform/x86/redmi-wmi.c @@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, - /* Keyboard backlight */ - {KE_IGNORE, 0x00000501, {}}, - {KE_IGNORE, 0x00800501, {}}, - {KE_IGNORE, 0x00050501, {}}, - {KE_IGNORE, 0x000a0501, {}}, + /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ + {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, + {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, /* Xiaomi G Command Center */ {KE_KEY, 0x00010a01, {KEY_VENDOR}}, - /* OEM preset power mode */ - {KE_IGNORE, 0x00011601, {}}, - {KE_IGNORE, 0x00021601, {}}, - {KE_IGNORE, 0x00031601, {}}, - {KE_IGNORE, 0x00041601, {}}, + /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ + {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, + {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, - /* Fn Lock state */ - {KE_IGNORE, 0x00000701, {}}, - {KE_IGNORE, 0x00010701, {}}, + /* Fn Lock state: 1=on 0=off */ + {KE_KEY, 0x00000701, {KEY_FN_ESC}}, + {KE_KEY, 0x00010701, {KEY_FN_ESC}}, /* Fn+`/1/2/3/4 */ {KE_KEY, 0x00011101, {KEY_F13}}, From 54745d563114b74f6fecebce68cd020d06c1772b Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 18 Aug 2026 17:16:35 +0200 Subject: [PATCH 152/152] platform/x86: think-lmi: Fix current password length check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit current_password_store() checks the password length before removing the trailing newline, which can reject valid passwords that are exactly ->maxlen bytes long. It also passes ->maxlen to strscpy(), which truncates passwords without a newline. Use strchrnul() to measure the password length up to the newline, then copy that many bytes and add a trailing NUL terminator using strscpy(). Fixes: a40cd7ef22fb ("platform/x86: think-lmi: Add WMI interface support on Lenovo platforms") Cc: stable@vger.kernel.org Reviewed-by: Mark Pearson Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260818151635.37094-2-thorsten.blum@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/think-lmi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index ca266657e16c..a0e3fa766e37 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -438,14 +438,13 @@ static ssize_t current_password_store(struct kobject *kobj, struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); size_t pwdlen; - pwdlen = strlen(buf); + /* Strip newline; setting password won't work if one is present. */ + pwdlen = strchrnul(buf, '\n') - buf; /* pwdlen == 0 is allowed to clear the password */ if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) return -EINVAL; - strscpy(setting->password, buf, setting->maxlen); - /* Strip out CR if one is present, setting password won't work if it is present */ - strreplace(setting->password, '\n', '\0'); + strscpy(setting->password, buf, pwdlen + 1); return count; }