platform-drivers-x86 for v7.3-1

Highlights:
 
 - amd/halo: Add Halo RGB LED driver
 
 - amd/hsmp:
   - Properly serialize probe, remove, and data paths
   - Add support for protocol v7 used by Family 1AH Model 80H
   - Fix error checking corner cases (largely from AI review)
   - Reject negative power cap
 
 - amd/pmc:
   - Improve behavior on platforms that do not support STB
   - Add T14 Gen2 AMD (20XL) to s2idle quirk list
 
 - amd/pmf:
   - Add ioctl interface to retrieve device metrics
   - Add support for new metrics tables used by Family 1AH Model 80H
 
 - arm64: qcom-hamoa-ec: Reject short responses
 
 - asus-nb-wmi: Support ProArt key on ASUS ProArt PX13
 
 - asus-armoury:
   - Gate PPT writes behind active fan curve
   - Add power limits for more models
 
 - dell-wmi-base: Fix handling of ultra performance key
 
 - dell-wmi-sysman: Don't hex dump attribute security buffer
 
 - hp-bioscfg:
   - Various fixes
   - Improve reduced ACPI packages support (necessary for HP EliteBook 840 G2)
 
 - lg-laptop:
   - Fix LED resource handling
   - Add support for events used in newer models
   - Fix keyboard backlight support on LG Gram 16T90SP
 
 - hp-wmi:
   - Generalize thermal params to board params
   - Manage CPU and GPU PWM independently
   - Add GPU MUX switch support
   - Add Victus 15-fb0xxx support
   - Add OMEN MAX 16-ak0xxx, OMEN 16-n0xxx, OMEN 16-wd0xxx, OMEN
     16-wf0xxx, and OMEN board ID 8D88 support
   - Add OMEN Transcend 16-u0xxx support
 
 - huawei: Add support for Fn-lock ACPI interface found on newer Huawei
           laptops such as MateBook 14 2024
 
 - ISST:
   - Improve input validation (many fixes)
   - Disallow SST-CP (core-power) feature if perf profile add fails
 
 - lenovo/yb9-kbdock: Add driver for Yoga Book 9 14IAH10
 
 - lenovo/ymc:
   - Extend hinge switch query to support Yoga 9 2-in-1 14IPH11
   - Prevent loading on Yoga Book 9 14IAH10 to avoid duplicated input
     nodes
 
 - msi-ec: Add MSI Raider A18 HX A9WJG and MSI Katana GF76 11UEK support
 
 - msi-wmi: Add MSI Claw M-Center keys support
 
 - oxpec: Add support for OneXPlayer X2 Mini Pro
 
 - redmi-wmi: Report kbd backlight cycle, OEM preset power mode, and FnLock
              toggle events to userspace
 
 - samsung-galaxybook: Add Samsung Galaxy Book6 Pro support
 
 - thinkpad_acpi: Add USB-C Security support
 
 - uniwill-laptop:
   - Add keyboard backlight, AC auto boot, and USB powershare support
   - Add MACHENIKE L16 Pro, AiStone X4SP4NAL, and Avell A60 MUV support
   - Make lightbar max brightness configurable and add support for
     LAPQC71A/B
 
 - Major refactoring efforts:
   - Stop setting acpi_device_name/class() and pnp.device_class to
     faciliate their eventual removal
 
 - Many rollback/remove path fixes (presumably mostly found by AI)
 
 - Miscellaneous cleanups / refactoring / improvements
 
 The following is an automated shortlog grouped by driver:
 
 acer-wmi:
  -  reject missing gaming WMI results
 
 amd/hsmp:
  -  ACPI HSMP refcounted sockets and coordinated release
  -  Add HSMP messages for Family 1Ah, Model 50h-5Fh
  -  Add IOCTL_GET_TELEMETRY_DATA for metric table reads
  -  Clear mdev.this_device on deregister
  -  Enable protocol version 7 metric tables on the ACPI driver
  -  Gate the data plane on a fully initialized socket
  -  Map the metric table with ioremap() and unmap it explicitly
  -  Pass struct device explicitly to ACPI mailbox parsers
  -  Reject negative power cap writes in hwmon
  -  Serialize ACPI HSMP probe and remove with an rwsem
  -  Serialize per-socket metric table reads with a mutex
  -  Serialize the data plane against socket teardown
  -  Source metric-table size from firmware
  -  Unify response_sz validation to an upper-bound check
  -  Validate ACPI UID before parsing socket index
  -  Validate _DSD mailbox sub-package element count
 
 amd:
  -  Introduce Halo Box RGB LED driver
 
 amd/pmc:
  -  Add T14 Gen2 AMD (20XL) to s2idle quirk list
  -  Do not fail probe when STB init fails
  -  Fix LPS0 and debugfs leaks when STB init fails
  -  Fix msg_port restoration in amd_stb_debugfs_open_v2()
  -  Only expose stb_read after telemetry buffer is mapped
  -  Propagate SMU errors and validate S2D address
  -  Restore msg_port on amd_stb_s2d_init() error paths
 
 amd/pmf:
  -  Add 1AH_M80H device IDs and extended SMU mailbox registers
  -  Add 1AH_M80H metrics table and NPU metrics support
  -  Add missing newline in dev_err message
  -  Add util layer and userspace character device interface
  -  Implement util layer ioctl handler
  -  Introduce AMD PMF testing tool for driver metrics and features
  -  Move debug helper functions to UAPI header
  -  Move metrics code to dedicated file
  -  Refactor NPU metrics for platform extensibility
  -  store BIOS output values for user-space metrics via util IOCTL
  -  Store commonly used enums in the header file
  -  Use per-SoC smu_regs struct for SMU mailbox registers
  -  Use upper/lower_32_bits() in amd_pmf_set_dram_addr()
 
 arm64: qcom-hamoa-ec:
  -  reject incomplete responses
 
 asus-armoury:
  -  Add power limits for ROG Strix SCAR 16 (G635LX)
  -  Add power limits quirk for FA401KM
  -  Add power limits quirk for FA608WV
  -  add support for FX517ZR
  -  add support for HN7306EA and HN7306EAC
  -  fix Use-After-Free and memory leak in driver init
  -  gate PPT writes behind active fan curve
  -  use cleanup.h to manage tunables
 
 asus-laptop:
  -  Stop setting acpi_device_name/class()
 
 asus-nb-wmi:
  -  map ProArt key (0x8b) to KEY_PROG3
 
 asus-wireless:
  -  Fail probe when there is no ACPI match
 
 asus-wmi:
  -  fix resource leaks on probe failure
 
 dell-ddv:
  -  Use no_free_ptr() to simplify error handling
 
 dell-privacy:
  -  Fix race condition
 
 dell-smbios:
  -  Pass device to callbacks
 
 dell-smbios-wmi:
  -  Fix chardev resource management
  -  Replace global list with single item
 
 dell-wmi-base:
  -  Fix handling of ultra performance key
  -  Fix resource leak on module load failure
 
 dell-wmi-sysman:
  -  Don't hex dump attribute security buffer
  -  Fix instance ID bounds
 
 Documentation/ABI:
  -  add testing entry for AMD PMF character device interface
 
 eeepc-laptop:
  -  Stop setting acpi_device_name/class()
 
 fujitsu-laptop:
  -  Stop setting acpi_device_name/class()
 
 fujitsu-tablet:
  -  Stop setting acpi_device_name/class()
 
 hp-bioscfg:
  -  accept reduced ACPI packages from older HP BIOS
  -  advance elem past consumed array elements
  -  bound ordered-list parsing by the package count
  -  fix heap OOB read in sk_store() and kek_store()
  -  fix heap OOB read on empty password write
  -  fix new_password_store() overwriting current_password
  -  fix off-by-one write in hp_get_string_from_buffer()
  -  fix ORD_LIST_ELEMENTS never being parsed
  -  fix password encoding bounds check
  -  pass validated element count to package parsers
  -  warn on element type mismatch instead of failing
 
 hp-wmi:
  -  Add dual-channel PWM fan control
  -  Add GPU MUX switch support
  -  Add OMEN board 8A43 thermal profile support
  -  Add OMEN board 8BA9 thermal profile support
  -  Add OMEN board 8BAA thermal profile support
  -  Add OMEN board 8D88 thermal profile support
  -  Add OMEN Transcend 16 8BB3 support
  -  Add support for OMEN MAX 16-ak0xxx (8DD6)
  -  Add Victus 15-fb0xxx support
  -  Drive fan control from board data
  -  Introduce board-specific feature data
 
 huawei-wmi:
  -  add ACPI fallback for Fn-lock on newer models
 
 ideapad-laptop:
  -  Fix driver unregistration order
 
 int1092:
  -  Fix info leak in parse_package()
  -  Fix potential memory leak in sar_probe()
 
 intel/pmc:
  -  initialize empty PMT read result
 
 ishtp_eclite:
  -  Fix ACPI device reference leak in probe error path
 
 ISST:
  -  Add a NULL check for sst_inst[]
  -  Just allow 2 bits for SST feature enable
  -  Return error during profile addition
  -  Use PP level enable mask
  -  Validate level in perf mask ioctls
  -  Validate logical CPU id and clos id
  -  Validate max level for set feature
  -  Validate parameter for core power state
  -  Validate parameter for frequency and priority
  -  Validate socket ID in clos_assoc ioctl
 
 lenovo:
  -  Add Yoga Book 9 keyboard dock detection driver
 
 lenovo: lenovo-ymc:
  -  Suppress probe on Yoga Book 9 14IAH10
 
 lenovo/ymc:
  -  Only match lower byte in WMI lid switch query response
 
 lg-laptop:
  -  Add support for additional events
  -  Add support for native ACPI events
  -  Fix keyboard backlight support on LG Gram 16T90SP
  -  Fix LED resource handling
  -  Improve WMAB control method support
 
 MAINTAINERS:
  -  update Intel PMC Core maintainer contact
 
 mlxbf-bootctl:
  -  fix the build error with FIELD_PREP()
 
 mlxbf-pmc:
  -  Check ACPI_COMPANION() against NULL
 
 msi-ec:
  -  Add MSI Katana GF76 11UEK EC firmware
  -  Add MSI Raider A18 HX A9WJG EC firmware
 
 msi-wmi:
  -  Add MSI Claw M-Center keys
  -  Reformat msi_wmi_notify()
 
 oxpec:
  -  Add support for OneXPlayer X2 Mini Pro
 
 panasonic-laptop:
  -  Fix sentinel write past pcc->sinf[]
  -  Stop setting acpi_device_name/class()
 
 power: supply: surface_{battery,charger}:
  -  Consistently define ssam_device_ids using named initializers
 
 redmi-wmi:
  -  report EC state change events
 
 samsung-galaxybook:
  -  Add SAMB430 device ID
 
 sony-laptop:
  -  Stop setting acpi_device_class()
 
 sonypi:
  -  Stop setting acpi_device_name/class()
 
 surface: acpi-notify:
  -  Check ACPI companion before use
 
 surface: aggregator:
  -  Consistently define ssam_device_ids using named initializers
 
 surface: surfacepro3_button:
  -  Stop setting acpi_device_name()
 
 think-lmi:
  -  Fix certificate thumbprint sysfs output
  -  Fix current password length check
  -  Free system certificate signatures
 
 thinkpad_acpi:
  -  Add USB-C Security (USCS) support
  -  Fix fan speed reporting on Edge E330
  -  Fix USB-C Security probe failure on unsupported platforms
  -  Stop setting acpi_device_class()
 
 topstar-laptop:
  -  Stop setting acpi_device_name/class()
 
 toshiba_acpi:
  -  Do not use uninitialized device_class
 
 toshiba_bluetooth:
  -  Use more common error handling code in toshiba_bt_rfkill_probe()
 
 toshiba_haps:
  -  Do not use uninitialized device_class
 
 uniwill-laptop:
  -  Add 2 new feature defines for TUXEDO devices
  -  Add AC auto boot support
  -  Add Avell A60 MUV support
  -  Add keyboard backlight support
  -  Add lightbar support for LAPQC71A/B
  -  Add support for the AiStone X4SP4NAL
  -  Add support for the MACHENIKE L16 Pro
  -  Add support for USB powershare
  -  Handle screen-related events
  -  Remove single color keyboard detection
  -  Split uniwill_kbd_led_init()
 
 x86/platform/olpc: xo15:
  -  Stop setting acpi_device_name/class()
 
 xo15-ebook:
  -  Stop setting acpi_device_name/class()
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCaow0EgAKCRBZrE9hU+XO
 MRvYAP412gjYokIoj6ncE2OD897gX2d5BwUVKkraQHoevxLm8wEAikY+iYbdOv3a
 fqhPeCDZSRBn6yEBHewY7cdotNDn2go=
 =d/Uf
 -----END PGP SIGNATURE-----

Merge tag 'platform-drivers-x86-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform driver updates from Ilpo Järvinen
 "Highlights:

  Major refactoring effort: stop setting acpi_device_name/class() and
  pnp.device_class to facilitate their eventual removal

  Many rollback/remove path fixes (presumably mostly found by AI)

  Miscellaneous cleanups / refactoring / improvements

  amd/halo:
   - Add Halo RGB LED driver

  amd/hsmp:
   - Properly serialize probe, remove, and data paths
   - Add support for protocol v7 used by Family 1AH Model 80H
   - Fix error checking corner cases (largely from AI review)
   - Reject negative power cap

  amd/pmc:
   - Improve behavior on platforms that do not support STB
   - Add T14 Gen2 AMD (20XL) to s2idle quirk list

  amd/pmf:
   - Add ioctl interface to retrieve device metrics
   - Add support for new metrics tables used by Family 1AH Model 80H

  qcom-hamoa-ec (arm64):
   - Reject short responses

  asus-nb-wmi:
   - Support ProArt key on ASUS ProArt PX13

  asus-armoury:
   - Gate PPT writes behind active fan curve
   - Add power limits for more models

  dell-wmi-base:
   - Fix handling of ultra performance key

  dell-wmi-sysman:
   - Don't hex dump attribute security buffer

  hp-bioscfg:
   - Various fixes
   - Improve reduced ACPI packages support (necessary for HP EliteBook 840 G2)

  lg-laptop:
   - Fix LED resource handling
   - Add support for events used in newer models
   - Fix keyboard backlight support on LG Gram 16T90SP

  hp-wmi:
   - Generalize thermal params to board params
   - Manage CPU and GPU PWM independently
   - Add GPU MUX switch support
   - Add Victus 15-fb0xxx support
   - Add OMEN MAX 16-ak0xxx, OMEN 16-n0xxx, OMEN 16-wd0xxx, OMEN
     16-wf0xxx, and OMEN board ID 8D88 support
   - Add OMEN Transcend 16-u0xxx support

  huawei:
   - Add support for Fn-lock ACPI interface found on newer Huawei
     laptops such as MateBook 14 2024

  ISST:
   - Improve input validation (many fixes)
   - Disallow SST-CP (core-power) feature if perf profile add fails

  lenovo/yb9-kbdock:
   - Add driver for Yoga Book 9 14IAH10

  lenovo/ymc:
   - Extend hinge switch query to support Yoga 9 2-in-1 14IPH11
   - Prevent loading on Yoga Book 9 14IAH10 to avoid duplicated input
    nodes

  msi-ec:
   - Add MSI Raider A18 HX A9WJG and MSI Katana GF76 11UEK support

  msi-wmi:
   - Add MSI Claw M-Center keys support

  oxpec:
   - Add support for OneXPlayer X2 Mini Pro

  redmi-wmi:
   - Report kbd backlight cycle, OEM preset power mode, and FnLock
     toggle events to userspace

  samsung-galaxybook:
   - Add Samsung Galaxy Book6 Pro support

  thinkpad_acpi:
   - Add USB-C Security support

  uniwill-laptop:
   - Add keyboard backlight, AC auto boot, and USB powershare support
   - Add MACHENIKE L16 Pro, AiStone X4SP4NAL, and Avell A60 MUV support
   - Make lightbar max brightness configurable and add support for
     LAPQC71A/B"

* tag 'platform-drivers-x86-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (152 commits)
  platform/x86: think-lmi: Fix current password length check
  platform/x86: redmi-wmi: report EC state change events
  MAINTAINERS: update Intel PMC Core maintainer contact
  platform/x86: oxpec: Add support for OneXPlayer X2 Mini Pro
  platform/x86: thinkpad_acpi: Fix fan speed reporting on Edge E330
  platform/x86: msi-ec: Add MSI Katana GF76 11UEK EC firmware
  platform/x86: think-lmi: Fix certificate thumbprint sysfs output
  mlxbf-bootctl: fix the build error with FIELD_PREP()
  platform/x86: think-lmi: Free system certificate signatures
  platform/x86: ISST: Add a NULL check for sst_inst[]
  platform/x86: ISST: Return error during profile addition
  platform/x86: ISST: Just allow 2 bits for SST feature enable
  platform/x86: ISST: Use PP level enable mask
  platform/x86: ISST: Validate parameter for frequency and priority
  platform/x86: ISST: Validate parameter for core power state
  platform/x86: ISST: Validate max level for set feature
  platform/x86: ISST: Validate logical CPU id and clos id
  platform/x86: ISST: Validate level in perf mask ioctls
  platform/x86: ISST: Validate socket ID in clos_assoc ioctl
  platform/x86/amd/hsmp: Reject negative power cap writes in hwmon
  ...
This commit is contained in:
Linus Torvalds 2026-08-24 10:16:35 -07:00
commit 5b05bb3f6c
100 changed files with 5355 additions and 1051 deletions

View File

@ -0,0 +1,73 @@
What: /dev/amdpmf_interface
Date: June 2026
KernelVersion: 7.2
Contact: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
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.

View File

@ -0,0 +1,19 @@
What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
Date: June 2027
KernelVersion: 7.3
Contact: Dave Carey <carvsdriver@gmail.com>
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.

View File

@ -78,3 +78,28 @@ 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 <W_Armin@gmx.de>
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. 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 <W_Armin@gmx.de>
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.

View File

@ -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
------

View File

@ -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
----------------
@ -85,6 +98,20 @@ 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.
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
==========

View File

@ -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.

View File

@ -1145,6 +1145,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) <superm1@kernel.org>
R: Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>
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 <naveenkrishna.chatradhi@amd.com>
R: Carlos Bilbao <carlos.bilbao@kernel.org>
@ -1263,6 +1270,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 <kenneth.feng@amd.com>
@ -13350,7 +13358,7 @@ F: drivers/platform/x86/intel/punit_ipc.c
INTEL PMC CORE DRIVER
M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com>
M: David E Box <david.e.box@intel.com>
M: Xi Pardee <xi.pardee@linux.intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-platform-intel-pmc
@ -14783,6 +14791,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 <carvsdriver@gmail.com>
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 <hansg@kernel.org>
L: linux-input@vger.kernel.org

View File

@ -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
@ -328,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
@ -355,9 +395,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 +447,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) */
@ -467,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()
@ -474,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_*/

View File

@ -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))

View File

@ -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;
}

View File

@ -7,6 +7,7 @@
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/amd-pmf.h>
#include <linux/amd-pmf-io.h>
#include <linux/cleanup.h>
#include <linux/io-64-nonatomic-lo-hi.h>
@ -137,20 +138,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");
}

View File

@ -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;

View File

@ -10,6 +10,7 @@
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/if_ether.h>
#include <linux/iopoll.h>

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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/

View File

@ -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 <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/container_of.h>
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include <linux/byteorder/generic.h>
#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) <superm1@kernel.org>");
MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>");
MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver");
MODULE_LICENSE("GPL");

View File

@ -15,12 +15,18 @@
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/ioport.h>
#include <linux/kref.h>
#include <linux/kstrtox.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/topology.h>
#include <linux/uuid.h>
@ -38,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;
@ -77,6 +94,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);
}
@ -104,7 +123,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;
@ -113,10 +132,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;
}
@ -139,7 +158,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;
}
@ -148,12 +167,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;
@ -183,14 +208,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;
}
@ -198,10 +223,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;
}
@ -215,7 +240,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);
@ -223,12 +247,27 @@ 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);
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,
@ -238,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;
@ -459,11 +516,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;
@ -491,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");
@ -576,6 +642,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;
@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev)
return -ENOMEM;
if (!hsmp_pdev->is_probed) {
/*
* 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->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;
@ -619,14 +766,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);
/*
* We register only one misc_device even on multi-socket system.
* So, deregister should happen only once.
* 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().
*/
if (hsmp_pdev->is_probed) {
hsmp_misc_deregister();
hsmp_pdev->is_probed = false;
}
guard(rwsem_write)(&hsmp_sock_rwsem);
/*
* 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.
*/
sock->dev = NULL;
kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
}
static struct platform_driver amd_hsmp_driver = {

View File

@ -10,10 +10,17 @@
#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/nospec.h>
#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>
#include "hsmp.h"
@ -40,6 +47,17 @@
static struct hsmp_plat_device hsmp_pdev;
/*
* 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");
/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
@ -182,29 +200,33 @@ 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;
}
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);
@ -223,6 +245,20 @@ 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.
*
* 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;
ret = down_interruptible(&sock->hsmp_sem);
if (ret < 0)
return ret;
@ -233,6 +269,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)
@ -273,7 +322,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;
@ -301,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 };
@ -370,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;
@ -383,8 +560,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;
}
@ -392,27 +568,77 @@ 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;
ret = hsmp_send_message(&msg);
/*
* 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_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)
{
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;
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;
}
sock->metric_tbl_size = 0;
}
}
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];
struct hsmp_message msg = { 0 };
phys_addr_t dram_addr;
size_t tbl_size;
int ret;
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
ret = hsmp_send_message(&msg);
ret = hsmp_send_message_locked(&msg);
if (ret)
return ret;
@ -425,12 +651,33 @@ 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));
/*
* 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_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");
@ -444,7 +691,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];
@ -463,6 +710,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;
@ -474,6 +729,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");

View File

@ -15,9 +15,12 @@
#include <linux/hwmon.h>
#include <linux/kconfig.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
@ -27,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;
@ -41,8 +44,12 @@ 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 */
struct mutex metric_read_lock;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct device *dev;
u16 sock_ind;
@ -54,7 +61,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);
@ -63,6 +69,9 @@ 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);
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)
@ -71,4 +80,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);
/*
* 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 */

View File

@ -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;

View File

@ -13,12 +13,14 @@
#include <linux/acpi.h>
#include <linux/build_bug.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
#include <linux/sysfs.h>
#include <asm/amd/node.h>
@ -201,6 +203,25 @@ 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
* the per-socket mutexes need an explicit mutex_destroy(), neither of which
* devres covers.
*
* 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)
{
int ret;
@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
if (!hsmp_pdev->sock)
return -ENOMEM;
ret = init_platform_device(&pdev->dev);
hsmp_init_metric_read_locks(hsmp_pdev);
ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL);
if (ret)
return ret;
/*
* 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;

View File

@ -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,
@ -289,15 +294,12 @@ 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;
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;
@ -306,27 +308,52 @@ 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);
if (size != S2D_TELEMETRY_BYTES_MAX)
return -EIO;
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);
/* Clear msg_port for other SMU operation */
dev->msg_port = MSG_PORT_PMC;
if (!stb_phys_addr) {
dev_err(dev->dev, "S2D phys addr query returned invalid address\n");
ret = -ENXIO;
goto out;
}
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;
/*
* 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 */
dev->msg_port = MSG_PORT_PMC;
return ret;
}

View File

@ -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,

View File

@ -939,9 +939,16 @@ 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_pci_dev_put;
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);

View File

@ -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.

View File

@ -7,4 +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

View File

@ -11,13 +11,13 @@
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/dev_printk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
@ -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
@ -48,7 +55,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)");
@ -61,7 +68,15 @@ 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 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)
{
@ -131,37 +146,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);
@ -176,13 +160,19 @@ 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[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, 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 +198,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 +207,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[0], 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 +229,14 @@ 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[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:
@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
return rc;
}
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) },
{ }
/* 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, 0, 0 },
};
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
{
u64 phys_addr;
u32 hi, low;
/* 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,
},
};
/* 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 const struct pci_device_id pmf_pci_ids[] = {
{ 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) },
{ PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) },
{ }
};
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
{
@ -536,6 +433,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},
@ -543,6 +453,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);
@ -624,6 +535,17 @@ 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;
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);
@ -632,7 +554,11 @@ 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)
dev_warn(dev->dev, "failed to register util interface: %d\n", err);
dev_info(dev->dev, "registered PMF device successfully\n");
@ -643,6 +569,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);

View File

@ -0,0 +1,330 @@
// 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 <Shyam-sundar.S-k@amd.com>
* Patil Rajesh Reddy <Patil.Reddy@amd.com>
*/
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/io.h>
#include <linux/ktime.h>
#include <linux/limits.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/string.h>
#include <linux/units.h>
#include <linux/wordpart.h>
#include <linux/workqueue.h>
#include "pmf.h"
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);
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\n", 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 = 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);
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:
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;
guard(mutex)(&dev->metrics_mutex);
ret = is_npu_metrics_supported(dev);
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:
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;
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;
}
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");

View File

@ -14,10 +14,13 @@
#include <linux/acpi.h>
#include <linux/amd-pmf-io.h>
#include <linux/circ_buf.h>
#include <linux/compiler_attributes.h>
#include <linux/compiler_types.h>
#include <linux/input.h>
#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/types.h>
#define POLICY_BUF_MAX_SZ 0x4b000
#define POLICY_SIGN_COOKIE 0x31535024
@ -28,6 +31,11 @@
#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
#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS
struct cookie_header {
u32 sign;
@ -70,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
@ -130,6 +142,14 @@ struct cookie_header {
#define GET_CMD true
#define METRICS_TABLE_ID 7
#define BIOS_OUTPUT_MAX 10
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);
@ -242,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 */
@ -391,6 +604,19 @@ 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[3];
};
struct amd_pmf_arg_data {
u32 lo;
u32 hi;
u32 size;
};
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@ -442,6 +668,14 @@ 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];
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 {
@ -680,14 +914,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;
@ -719,20 +945,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,
@ -871,6 +1083,10 @@ 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);
/* 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);
@ -923,9 +1139,19 @@ 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);
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 */

View File

@ -10,6 +10,7 @@
*/
#include <acpi/button.h>
#include <linux/amd-pmf.h>
#include <linux/amd-pmf-io.h>
#include <linux/cleanup.h>
#include <linux/power_supply.h>
@ -17,61 +18,7 @@
#include "pmf.h"
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *platform_type_as_str(u16 platform_type)
{
switch (platform_type) {
case CLAMSHELL:
return "CLAMSHELL";
case FLAT:
return "FLAT";
case TENT:
return "TENT";
case STAND:
return "STAND";
case TABLET:
return "TABLET";
case BOOK:
return "BOOK";
case PRESENTATION:
return "PRESENTATION";
case PULL_FWD:
return "PULL_FWD";
default:
return "UNKNOWN";
}
}
static const char *laptop_placement_as_str(u16 device_state)
{
switch (device_state) {
case ON_TABLE:
return "ON_TABLE";
case ON_LAP_MOTION:
return "ON_LAP_MOTION";
case IN_BAG:
return "IN_BAG";
case OUT_OF_BAG:
return "OUT_OF_BAG";
default:
return "UNKNOWN";
}
}
static const char *ta_slider_as_str(unsigned int state)
{
switch (state) {
case TA_BEST_PERFORMANCE:
return "PERFORMANCE";
case TA_BETTER_PERFORMANCE:
return "BALANCED";
case 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)
u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
{
switch (index) {
case 0 ... 1:
@ -82,13 +29,15 @@ 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)
{
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);
@ -102,9 +51,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));
@ -287,14 +237,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");

View File

@ -8,7 +8,9 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/array_size.h>
#include <linux/debugfs.h>
#include <linux/dev_printk.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#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)

View File

@ -0,0 +1,153 @@
// 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 <Shyam-sundar.S-k@amd.com>
* Sanket Goswami <Sanket.Goswami@amd.com>
*/
#include <linux/amd-pmf.h>
#include <linux/minmax.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include "pmf.h"
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)
{
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)
{
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;
}

View File

@ -16,6 +16,7 @@
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/err.h>
@ -93,6 +94,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 +219,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.
@ -989,10 +1008,11 @@ 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;
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;
@ -1000,24 +1020,25 @@ 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;
/* 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 */
@ -1060,13 +1081,10 @@ 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]);
if (!dc_rog_tunables) {
kfree(ac_rog_tunables);
goto err_nomem;
}
dc_rog_tunables = kzalloc_obj(*dc_rog_tunables);
if (!dc_rog_tunables)
return -ENOMEM;
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables;
dc_rog_tunables->power_limits = dc_limits;
/* Set initial DC values */
@ -1106,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] = no_free_ptr(ac_rog_tunables);
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(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)
@ -1127,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)

View File

@ -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"),
@ -754,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"),
@ -909,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"),
@ -2079,6 +2171,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"),
@ -2322,6 +2443,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"),

View File

@ -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();

View File

@ -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 */

View File

@ -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;

View File

@ -4073,6 +4073,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)
{
@ -5244,20 +5268,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:

View File

@ -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);

View File

@ -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;

View File

@ -6,21 +6,20 @@
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/rwsem.h>
#include <linux/uaccess.h>
#include <linux/wmi.h>
#include <uapi/linux/wmi.h>
#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,21 +31,16 @@ 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;
struct device *child;
u64 req_buf_size;
struct miscdevice char_dev;
};
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)
{
@ -80,50 +74,36 @@ 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;
mutex_lock(&call_mutex);
priv = get_first_smbios_priv();
if (!priv) {
ret = -ENODEV;
goto out_wmi_call;
}
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;
guard(rwsem_read)(&chardev_lock);
return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size,
sizeof(priv->req_buf_size));
if (!chardev_priv)
return -ENODEV;
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,
@ -167,22 +147,22 @@ 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;
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)(&chardev_lock);
return ret;
if (!chardev_priv)
return -ENODEV;
guard(mutex)(&chardev_priv->call_lock);
return dell_smbios_wmi_do_ioctl(chardev_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,
@ -195,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;
@ -254,31 +253,20 @@ 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;
ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
if (ret)
return ret;
mutex_lock(&list_mutex);
list_add_tail(&priv->list, &wmi_list);
mutex_unlock(&list_mutex);
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);
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);
}
static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
@ -316,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)

View File

@ -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);

View File

@ -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;
}
@ -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);

View File

@ -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,

View File

@ -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));
}

View File

@ -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);

View File

@ -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; \
} \

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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;
/*
@ -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 */
@ -731,26 +736,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;

View File

@ -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) \
@ -401,6 +404,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 +415,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 +426,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 +438,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 +447,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);

View File

@ -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 */
@ -227,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:
@ -280,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);
@ -300,10 +305,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 +319,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);

View File

@ -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:
@ -275,10 +277,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 +290,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);

View File

@ -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:
@ -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:
@ -261,7 +263,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;
@ -298,10 +302,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 +315,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);

View File

@ -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)
@ -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);
@ -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;
@ -351,6 +353,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);
@ -362,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;
@ -385,10 +394,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 +407,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,

View File

@ -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;

View File

@ -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:
@ -263,10 +265,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 +279,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,

View File

@ -14,6 +14,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/dmi.h>
@ -23,6 +25,7 @@
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/kstrtox.h>
#include <linux/limits.h>
#include <linux/minmax.h>
#include <linux/module.h>
@ -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
@ -133,11 +142,52 @@ 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_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;
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.
@ -157,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",
@ -187,87 +237,111 @@ 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, "8A3D") },
.driver_data = (void *)&victus_s_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, "8BAA") },
.driver_data = (void *)&omen_v1_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_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, "8BB3") },
.driver_data = (void *)&omen_v1_no_ec_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_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,
},
{
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
.driver_data = (void *)&omen_v1_legacy_thermal_params,
.driver_data = (void *)&omen_v1_legacy_board_params,
},
{},
};
@ -332,6 +406,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 {
@ -500,9 +575,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;
};
@ -829,24 +904,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)
@ -863,7 +934,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)
@ -1124,12 +1197,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,
@ -1138,6 +1333,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);
@ -1789,6 +1985,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,
@ -1874,7 +2102,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 +2117,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 +2155,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 +2426,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 +2458,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);
@ -2400,7 +2636,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;
@ -2412,16 +2648,16 @@ 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));
ret = hp_wmi_fan_speed_set(priv);
if (ret < 0)
return ret;
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
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;
@ -2445,12 +2681,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)
@ -2474,8 +2710,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)
@ -2484,10 +2720,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);
@ -2514,14 +2750,15 @@ 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) {
if (!is_victus_s_thermal_profile())
int rpm;
if (!hp_wmi_fan_control_supported())
return -EOPNOTSUPP;
/* PWM input is invalid when not in manual mode */
if (priv->mode != PWM_MODE_MANUAL)
@ -2530,7 +2767,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) {
@ -2538,16 +2778,20 @@ 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);
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:
@ -2563,7 +2807,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
};
@ -2604,14 +2848,14 @@ 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 */
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,
@ -2650,10 +2894,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;
}
@ -2693,24 +2935,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 +2988,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;

View File

@ -6,6 +6,7 @@
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@ -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.

View File

@ -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_kcalloc(&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 = {

View File

@ -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);

View File

@ -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;

View File

@ -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)
return 0;
if (!pd_info->perf_levels) {
pd_info->pp_header.allowed_level_mask = 0;
pd_info->pp_header.level_en_mask = 0;
return -ENOMEM;
}
pd_info->ratio_unit = pd_info->pp_header.ratio_unit;
pd_info->avx_levels = SST_MAX_AVX_LEVELS;
@ -367,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;
@ -399,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;
}
@ -599,6 +606,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 +628,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,
@ -649,6 +663,11 @@ 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
#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;
@ -657,6 +676,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;
@ -665,6 +687,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,
@ -703,6 +734,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,7 +762,13 @@ 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.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;
@ -747,6 +786,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)
@ -845,6 +886,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
@ -877,7 +919,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) {
/*
@ -932,6 +974,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;
@ -995,6 +1040,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)
@ -1261,6 +1309,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)
@ -1306,6 +1360,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)
@ -1342,6 +1399,12 @@ 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;
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)
@ -1370,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);
@ -1437,6 +1502,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;

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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;
}
@ -745,6 +744,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 +757,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;
@ -1456,6 +1461,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)

View File

@ -38,6 +38,7 @@
#include <linux/backlight.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@ -67,6 +68,7 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_choices.h>
#include <linux/string_helpers.h>
#include <linux/sysfs.h>
#include <linux/types.h>
@ -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 */
@ -309,6 +312,7 @@ struct tp_acpi_drv_struct {
struct ibm_struct {
char *name;
acpi_device_class device_class;
int (*read) (struct seq_file *);
int (*write) (char *);
@ -374,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;
@ -838,9 +844,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 +3874,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 +3954,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);
}
@ -8840,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 */
@ -11285,6 +11291,114 @@ 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;
if (!acpi_has_method(hkey_handle, "USCS"))
return -ENODEV;
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 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&dprc_attr_group,
&auxmac_attr_group,
&hwdd_attr_group,
&usbc_security_attr_group,
NULL,
};
@ -11499,6 +11614,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 +12081,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)

View File

@ -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 <carvsdriver@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/compiler_attributes.h>
#include <linux/dev_printk.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/wmi.h>
#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 (03).
*/
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 <carvsdriver@gmail.com>");
MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
MODULE_LICENSE("GPL");

View File

@ -8,6 +8,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
@ -20,10 +22,28 @@
#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");
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 = {
@ -85,7 +105,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);
@ -101,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");

View File

@ -8,8 +8,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/compiler_attributes.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/dmi.h>
@ -17,10 +20,12 @@
#include <linux/input/sparse-keymap.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <acpi/battery.h>
@ -57,13 +62,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
@ -78,42 +88,95 @@ 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)
#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);
struct lg_wmab_buffer_result {
__le32 value;
__le32 status;
} __packed;
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;
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}
};
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];
@ -154,30 +217,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)
@ -211,72 +341,11 @@ 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;
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) {
led_classdev_notify_brightness_hw_changed(
&kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent));
} 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)
{
unsigned long value;
union acpi_object *r;
int ret;
ret = kstrtoul(buffer, 10, &value);
@ -285,10 +354,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;
}
@ -296,22 +365,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,
@ -361,41 +422,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,
@ -403,40 +453,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,
@ -453,14 +493,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;
}
@ -471,8 +515,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);
@ -485,19 +530,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;
@ -563,10 +602,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)
@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0);
static void kbd_backlight_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
u32 val;
union acpi_object *r;
/* 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;
r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val);
kfree(r);
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;
}
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)
{
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 (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC)
return LED_OFF;
}
switch (r->buffer.pointer[0] & 0x27) {
case 0x24:
val = LED_FULL;
break;
case 0x22:
val = LED_HALF;
break;
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 KBD_LED_BRIGHTNESS_HALF:
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)
@ -629,26 +668,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)
{
@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev)
if (year >= 2019)
battery_limit_use_wmbb = 1;
/* LEDs are optional */
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);
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;
/* LEDs are optional */
led_classdev_register(&pf_device->dev, &kbd_backlight);
led_classdev_register(&pf_device->dev, &tpad_led);
wmi_input_setup();
battery_hook_register(&battery_hook);
return 0;
@ -884,11 +1068,7 @@ 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);
pf_device = NULL;
platform_driver_unregister(&pf_driver);

View File

@ -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
};
@ -1130,6 +1131,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 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = {
&CONF11,
&CONF12,
&CONF13,
&CONF14,
NULL
};

View File

@ -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 }
};
@ -172,44 +187,62 @@ 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;
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;
}
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)

View File

@ -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,
},
{},
};

View File

@ -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"
@ -360,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);
@ -1017,8 +1024,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) {

View File

@ -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}},

View File

@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = {
{ "SAM0428" },
{ "SAM0429" },
{ "SAM0430" },
{ "SAMB430" },
{}
};
MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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,

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -66,15 +66,6 @@ enum sfh_dev_mode {
SFH_MODE_TABLET = 3,
};
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]
@ -83,6 +74,7 @@ enum laptop_placement {
* @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;
@ -91,6 +83,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);

View File

@ -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 */

View File

@ -0,0 +1,204 @@
/* 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 <linux/bits.h>
#include <linux/ioctl.h>
#include <linux/types.h>
/**
* 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)
/**
* 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,
};
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;
/* 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];
__u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX];
};
#endif /* _UAPI_LINUX_AMD_PMF_H */

View File

@ -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=<dir> - Build output directory"
@echo " PREFIX - Installation prefix (default: /usr)"
@echo " DESTDIR - Destination directory for install"

View File

@ -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 <Shyam-sundar.S-k@amd.com>
* Sanket Goswami <Sanket.Goswami@amd.com>
*/
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/amd-pmf.h>
#include <linux/kernel.h>
#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;
}