platform-drivers-x86 for v6.13-1

Highlights:
  - alienware-wmi:	WMAX thermal interface support
  - amd/hsmp:		Split ACPI and platform device based drivers
  - amd/x3d_vcache:	X3D frequency/cache mode switching support
  - asus-wmi:		Thermal policy fixes
  - intel/pmt:		Disable C1 auto-demotion in suspend to allow
 			entering the deepest C-states
  - intel-hid:		Fix volume buttons on Thinkpad X12 Detachable
 			Tablet Gen 1
  - intel_scu_ipc:	Replace "workaround" with 32-bit IO
  - panasonic-laptop:	Correct *_show() function error handling
  - p2sb:		Gemini Lake P2SB devfn correction
  - think-lmi:		Admin/System certificate authentication support
  - wmi:			Disable WMI devices for shutdown, refactoring
 			continues
  - x86-android-tablets:	Vexia EDU ATLA 10 tablet support
  - platform/surface:	Surface Pro 9 5G (Arm/QCOM) support
  - Miscellaneous cleanups / refactoring / improvements
 
 Expected conflicts:
  - hsmp driver split into two vs constifying bin_attribute [1]
 
 [1] https://lore.kernel.org/all/20241107212645.41252436@canb.auug.org.au/
 
 The following is an automated shortlog grouped by driver:
 
 alienware-wmi:
  -  added force module parameters
  -  added platform profile support
  -  Adds support to Alienware x17 R2
  -  alienware_wmax_command() is now input size agnostic
  -  create_thermal_profile() no longer brute-forces IDs
  -  extends the list of supported models
  -  fixed indentation and clean up
  -  Fix spelling mistake "requieres" -> "requires"
  -  order alienware_quirks[] alphabetically
  -  WMAX interface documentation
 
 amd: amd_3d_vcache:
  -  Add AMD 3D V-Cache optimizer driver
  -  Add sysfs ABI documentation
 
 amd/hsmp:
  -  Add new error code and error logs
  -  Change generic plat_dev name to hsmp_pdev
  -  Change the error type
  -  Convert amd_hsmp_rdwr() to a function pointer
  -  Create hsmp/ directory
  -  Create separate ACPI, plat and common drivers
  -  Create wrapper function init_acpi()
  -  Make hsmp_pdev static instead of global
  -  mark hsmp_msg_desc_table[] as maybe_unused
  -  Move ACPI code to acpi.c
  -  Move platform device specific code to plat.c
  -  Move structure and macros to header file
  -  Use dev_groups in the driver structure
  -  Use name space while exporting module symbols
 
 amd/pmf:
  -  Switch to platform_get_resource() and devm_ioremap_resource()
  -  Use dev_err_probe() to simplify error handling
 
 asus-laptop:
  -  prefer strscpy() over strcpy()
 
 asus-wmi:
  -  Fix inconsistent use of thermal policies
  -  Use platform_profile_cycle()
 
 classmate-laptop:
  -  Replace snprintf in show functions with sysfs_emit
 
 compal-laptop:
  -  use sysfs_emit() instead of sprintf()
 
 dell-dcdbase:
  -  Replace snprintf in show functions with sysfs_emit
 
 Documentation: alienware-wmi:
  -  Describe THERMAL_INFORMATION operation 0x02
 
 eeepc-laptop:
  -  use sysfs_emit() instead of sprintf()
 
 hp: hp-bioscfg:
  -  remove redundant if statement
 
 intel:
  -  Add 'intel' prefix to the modules automatically
 
 intel-hid:
  -  fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1
 
 intel/pmc:
  -  Disable C1 auto-demotion during suspend
  -  Refactor platform resume functions to use cnl_resume()
 
 intel/pmt:
  -  allow user offset for PMT callbacks
  -  Correct the typo 'ACCCESS_LOCAL'
 
 intel_scu_ipc:
  -  Convert to check for errors first
  -  Don't use "proxy" headers
  -  Replace workaround by 32-bit IO
  -  Save a copy of the entire struct intel_scu_ipc_data
  -  Simplify code with cleanup helpers
  -  Unify the flow in pwr_reg_rdwr()
 
 intel/vsec:
  -  Remove a useless mutex
 
 MAINTAINERS:
  -  adjust file entry in INTEL TPMI DRIVER
  -  Change AMD PMF driver status to "Supported"
  -  Update ISHTP ECLITE maintainer entry
 
 p2sb:
  -  Cache correct PCI bar for P2SB on Gemini Lake
 
 panasonic-laptop:
  -  Return errno correctly in show callback
 
 surface: aggregator_registry:
  -  Add Surface Pro 9 5G
 
 Switch back to struct platform_driver::
  - remove()
 
 think-lmi:
  -  Add certificate as mechanism
  -  Allow empty admin password
  -  improve check if BIOS account security enabled
  -  Multi-certificate support
 
 wmi:
  -  Implement proper shutdown handling
  -  Introduce to_wmi_driver()
  -  Remove wmi_block_list
  -  Replace dev_to_wdev() with to_wmi_device()
 
 x86: acer-wmi:
  -  remove unused macros
 
 x86-android-tablets:
  -  Add get_i2c_adap_by_handle() helper
  -  Add support for getting i2c_adapter by PCI parent devname()
  -  Add support for Vexia EDU ATLA 10 tablet
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCZz3IswAKCRBZrE9hU+XO
 McdKAQCY9gIuqtHpYK0QIQYMoZOWhpiCzfZ96DDHqt4Wknh6NgD/YY6eESyDokyB
 4BkujKwqo3cdGNPjIBy41jnNjekNsw8=
 =QEd2
 -----END PGP SIGNATURE-----

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

Pull x86 platform driver updates from Ilpo Järvinen:

 - alienware WMAX thermal interface support

 - Split ACPI and platform device based amd/hsmp drivers

 - AMD X3D frequency/cache mode switching support

 - asus thermal policy fixes

 - Disable C1 auto-demotion in suspend to allow entering the deepest
   C-states

 - Fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1

 - Replace intel_scu_ipc "workaround" with 32-bit IO

 - Correct *_show() function error handling in panasonic-laptop

 - Gemini Lake P2SB devfn correction

 - think-lmi Admin/System certificate authentication support

 - Disable WMI devices for shutdown, refactoring continues

 - Vexia EDU ATLA 10 tablet support

 - Surface Pro 9 5G (Arm/QCOM) support

 - Misc cleanups / refactoring / improvements

* tag 'platform-drivers-x86-v6.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (69 commits)
  platform/x86: p2sb: Cache correct PCI bar for P2SB on Gemini Lake
  platform/x86: panasonic-laptop: Return errno correctly in show callback
  Documentation: alienware-wmi: Describe THERMAL_INFORMATION operation 0x02
  alienware-wmi: create_thermal_profile() no longer brute-forces IDs
  alienware-wmi: Adds support to Alienware x17 R2
  alienware-wmi: extends the list of supported models
  alienware-wmi: order alienware_quirks[] alphabetically
  platform/x86/intel/pmt: allow user offset for PMT callbacks
  platform/x86/amd/hsmp: Change the error type
  platform/x86/amd/hsmp: Add new error code and error logs
  platform/x86/amd: amd_3d_vcache: Add sysfs ABI documentation
  platform/x86/amd: amd_3d_vcache: Add AMD 3D V-Cache optimizer driver
  intel-hid: fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1
  platform/x86/amd/hsmp: mark hsmp_msg_desc_table[] as maybe_unused
  platform/x86: asus-wmi: Use platform_profile_cycle()
  platform/x86: asus-wmi: Fix inconsistent use of thermal policies
  platform/x86: hp: hp-bioscfg: remove redundant if statement
  MAINTAINERS: Update ISHTP ECLITE maintainer entry
  platform/x86: x86-android-tablets: Add support for Vexia EDU ATLA 10 tablet
  platform/x86: x86-android-tablets: Add support for getting i2c_adapter by PCI parent devname()
  ...
This commit is contained in:
Linus Torvalds 2024-11-20 14:07:55 -08:00
commit fcb3ad4366
92 changed files with 3194 additions and 1517 deletions

View File

@ -0,0 +1,12 @@
What: /sys/bus/platform/drivers/amd_x3d_vcache/AMDI0101:00/amd_x3d_mode
Date: November 2024
KernelVersion: 6.13
Contact: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
Description: (RW) AMD 3D V-Cache optimizer allows users to switch CPU core
rankings dynamically.
This file switches between these two modes:
- "frequency" cores within the faster CCD are prioritized before
those in the slower CCD.
- "cache" cores within the larger L3 CCD are prioritized before
those in the smaller L3 CCD.

View File

@ -193,7 +193,7 @@ Description:
mechanism:
The means of authentication. This attribute is mandatory.
Only supported type currently is "password".
Supported types are "password" or "certificate".
max_password_length:
A file that can be read to obtain the
@ -303,6 +303,7 @@ Description:
being configured allowing anyone to make changes.
After any of these operations the system must reboot for the changes to
take effect.
Admin and System certificates are supported from 2025 systems onward.
certificate_thumbprint:
Read only attribute used to display the MD5, SHA1 and SHA256 thumbprints

View File

@ -4,8 +4,9 @@
AMD HSMP interface
============================================
Newer Fam19h EPYC server line of processors from AMD support system
management functionality via HSMP (Host System Management Port).
Newer Fam19h(model 0x00-0x1f, 0x30-0x3f, 0x90-0x9f, 0xa0-0xaf),
Fam1Ah(model 0x00-0x1f) EPYC server line of processors from AMD support
system management functionality via HSMP (Host System Management Port).
The Host System Management Port (HSMP) is an interface to provide
OS-level software with access to system management functions via a
@ -16,14 +17,25 @@ More details on the interface can be found in chapter
Eg: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/55898_B1_pub_0_50.zip
HSMP interface is supported on EPYC server CPU models only.
HSMP interface is supported on EPYC line of server CPUs and MI300A (APU).
HSMP device
============================================
amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice
/dev/hsmp to let user space programs run hsmp mailbox commands.
amd_hsmp driver under drivers/platforms/x86/amd/hsmp/ has separate driver files
for ACPI object based probing, platform device based probing and for the common
code for these two drivers.
Kconfig option CONFIG_AMD_HSMP_PLAT compiles plat.c and creates amd_hsmp.ko.
Kconfig option CONFIG_AMD_HSMP_ACPI compiles acpi.c and creates hsmp_acpi.ko.
Selecting any of these two configs automatically selects CONFIG_AMD_HSMP. This
compiles common code hsmp.c and creates hsmp_common.ko module.
Both the ACPI and plat drivers create the miscdevice /dev/hsmp to let
user space programs run hsmp mailbox commands.
The ACPI object format supported by the driver is defined below.
$ ls -al /dev/hsmp
crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp
@ -59,6 +71,51 @@ Note: lseek() is not supported as entire metrics table is read.
Metrics table definitions will be documented as part of Public PPR.
The same is defined in the amd_hsmp.h header.
ACPI device object format
=========================
The ACPI object format expected from the amd_hsmp driver
for socket with ID00 is given below::
Device(HSMP)
{
Name(_HID, "AMDI0097")
Name(_UID, "ID00")
Name(HSE0, 0x00000001)
Name(RBF0, ResourceTemplate()
{
Memory32Fixed(ReadWrite, 0xxxxxxx, 0x00100000)
})
Method(_CRS, 0, NotSerialized)
{
Return(RBF0)
}
Method(_STA, 0, NotSerialized)
{
If(LEqual(HSE0, One))
{
Return(0x0F)
}
Else
{
Return(Zero)
}
}
Name(_DSD, Package(2)
{
Buffer(0x10)
{
0x9D, 0x61, 0x4D, 0xB7, 0x07, 0x57, 0xBD, 0x48,
0xA6, 0x9F, 0x4E, 0xA2, 0x87, 0x1F, 0xC2, 0xF6
},
Package(3)
{
Package(2) {"MsgIdOffset", 0x00010934},
Package(2) {"MsgRspOffset", 0x00010980},
Package(2) {"MsgArgOffset", 0x000109E0}
}
})
}
An example
==========

View File

@ -0,0 +1,397 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
==============================================
Dell AWCC WMI interface driver (alienware-wmi)
==============================================
Introduction
============
The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
models. Throughout these models, two implementations have been identified. The
first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
and deep sleep control. The second one used by newer systems deals primarily
with thermal, overclocking, and GPIO control.
It is suspected that the latter is used by Alienware Command Center (AWCC) to
manage manufacturer predefined thermal profiles. The alienware-wmi driver
exposes Thermal_Information and Thermal_Control methods through the Platform
Profile API to mimic AWCC's behavior.
This newer interface, named AWCCMethodFunction has been reverse engineered, as
Dell has not provided any official documentation. We will try to describe to the
best of our ability its discovered inner workings.
.. note::
The following method description may be incomplete and some operations have
different implementations between devices.
WMI interface description
-------------------------
The WMI interface description can be decoded from the embedded binary MOF (bmof)
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("WMI Function"), guid("{A70591CE-A997-11DA-B012-B622A1EF5492}")]
class AWCCWmiMethodFunction {
[key, read] string InstanceName;
[read] boolean Active;
[WmiMethodId(13), Implemented, read, write, Description("Return Overclocking Report.")] void Return_OverclockingReport([out] uint32 argr);
[WmiMethodId(14), Implemented, read, write, Description("Set OCUIBIOS Control.")] void Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(15), Implemented, read, write, Description("Clear OC FailSafe Flag.")] void Clear_OCFailSafeFlag([out] uint32 argr);
[WmiMethodId(19), Implemented, read, write, Description("Get Fan Sensors.")] void GetFanSensors([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(20), Implemented, read, write, Description("Thermal Information.")] void Thermal_Information([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(21), Implemented, read, write, Description("Thermal Control.")] void Thermal_Control([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(23), Implemented, read, write, Description("MemoryOCControl.")] void MemoryOCControl([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(26), Implemented, read, write, Description("System Information.")] void SystemInformation([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(28), Implemented, read, write, Description("Power Information.")] void PowerInformation([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(32), Implemented, read, write, Description("FW Update GPIO toggle.")] void FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(33), Implemented, read, write, Description("Read Total of GPIOs.")] void ReadTotalofGPIOs([out] uint32 argr);
[WmiMethodId(34), Implemented, read, write, Description("Read GPIO pin Status.")] void ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(35), Implemented, read, write, Description("Read Chassis Color.")] void ReadChassisColor([out] uint32 argr);
[WmiMethodId(36), Implemented, read, write, Description("Read Platform Properties.")] void ReadPlatformProperties([out] uint32 argr);
[WmiMethodId(37), Implemented, read, write, Description("Game Shift Status.")] void GameShiftStatus([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(128), Implemented, read, write, Description("Caldera SW installation.")] void CalderaSWInstallation([out] uint32 argr);
[WmiMethodId(129), Implemented, read, write, Description("Caldera SW is released.")] void CalderaSWReleased([out] uint32 argr);
[WmiMethodId(130), Implemented, read, write, Description("Caldera Connection Status.")] void CalderaConnectionStatus([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(131), Implemented, read, write, Description("Surprise Unplugged Flag Status.")] void SurpriseUnpluggedFlagStatus([out] uint32 argr);
[WmiMethodId(132), Implemented, read, write, Description("Clear Surprise Unplugged Flag.")] void ClearSurpriseUnpluggedFlag([out] uint32 argr);
[WmiMethodId(133), Implemented, read, write, Description("Cancel Undock Request.")] void CancelUndockRequest([out] uint32 argr);
[WmiMethodId(135), Implemented, read, write, Description("Devices in Caldera.")] void DevicesInCaldera([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(136), Implemented, read, write, Description("Notify BIOS for SW ready to disconnect Caldera.")] void NotifyBIOSForSWReadyToDisconnectCaldera([out] uint32 argr);
[WmiMethodId(160), Implemented, read, write, Description("Tobii SW installation.")] void TobiiSWinstallation([out] uint32 argr);
[WmiMethodId(161), Implemented, read, write, Description("Tobii SW Released.")] void TobiiSWReleased([out] uint32 argr);
[WmiMethodId(162), Implemented, read, write, Description("Tobii Camera Power Reset.")] void TobiiCameraPowerReset([out] uint32 argr);
[WmiMethodId(163), Implemented, read, write, Description("Tobii Camera Power On.")] void TobiiCameraPowerOn([out] uint32 argr);
[WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
};
Some of these methods get quite intricate so we will describe them using
pseudo-code that vaguely resembles the original ASL code.
Methods not described in the following document have unknown behavior.
Argument Structure
------------------
All input arguments have type **uint32** and their structure is very similar
between methods. Usually, the first byte corresponds to a specific *operation*
the method performs, and the subsequent bytes correspond to *arguments* passed
to this *operation*. For example, if an operation has code 0x01 and requires an
ID 0xA0, the argument you would pass to the method is 0xA001.
Thermal Methods
===============
WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
::
if BYTE_0(arg2) == 0x01:
argr = 1
if BYTE_0(arg2) == 0x02:
argr = SYSTEM_DESCRIPTION
if BYTE_0(arg2) == 0x03:
if BYTE_1(arg2) == 0x00:
argr = FAN_ID_0
if BYTE_1(arg2) == 0x01:
argr = FAN_ID_1
if BYTE_1(arg2) == 0x02:
argr = FAN_ID_2
if BYTE_1(arg2) == 0x03:
argr = FAN_ID_3
if BYTE_1(arg2) == 0x04:
argr = SENSOR_ID_CPU | 0x0100
if BYTE_1(arg2) == 0x05:
argr = SENSOR_ID_GPU | 0x0100
if BYTE_1(arg2) == 0x06:
argr = THERMAL_MODE_QUIET_ID
if BYTE_1(arg2) == 0x07:
argr = THERMAL_MODE_BALANCED_ID
if BYTE_1(arg2) == 0x08:
argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
if BYTE_1(arg2) == 0x09:
argr = THERMAL_MODE_PERFORMANCE_ID
if BYTE_1(arg2) == 0x0A:
argr = THERMAL_MODE_LOW_POWER_ID
if BYTE_1(arg2) == 0x0B:
argr = THERMAL_MODE_GMODE_ID
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x04:
if is_valid_sensor(BYTE_1(arg2)):
argr = SENSOR_TEMP_C
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x05:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_RPM()
if BYTE_0(arg2) == 0x06:
skip
if BYTE_0(arg2) == 0x07:
argr = 0
If BYTE_0(arg2) == 0x08:
if is_valid_fan(BYTE_1(arg2)):
argr = 0
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x09:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_UNKNOWN_STAT_0()
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x0A:
argr = THERMAL_MODE_BALANCED_ID
if BYTE_0(arg2) == 0x0B:
argr = CURRENT_THERMAL_MODE()
if BYTE_0(arg2) == 0x0C:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_UNKNOWN_STAT_1()
else:
argr = 0xFFFFFFFF
Operation 0x02 returns a *system description* buffer with the following
structure:
::
out[0] -> Number of fans
out[1] -> Number of sensors
out[2] -> 0x00
out[3] -> Number of thermal modes
Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
codes in order, but different models may have different number of fans and
thermal profiles. These are the known ranges:
* Fan IDs: from 2 up to 4
* Sensor IDs: 2
* Thermal profile codes: from 1 up to 7
In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
::
if BYTE_0(arg2) == 0x01:
if is_valid_thermal_profile(BYTE_1(arg2)):
SET_THERMAL_PROFILE(BYTE_1(arg2))
argr = 0
if BYTE_0(arg2) == 0x02:
if is_valid_fan(BYTE_1(arg2)):
SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
argr = 0
else:
argr = 0xFFFFFFFF
.. note::
While you can manually change the fan speed multiplier with this method,
Dell's BIOS tends to overwrite this changes anyway.
These are the known thermal profile codes:
::
CUSTOM 0x00
BALANCED_USTT 0xA0
BALANCED_PERFORMANCE_USTT 0xA1
COOL_USTT 0xA2
QUIET_USTT 0xA3
PERFORMANCE_USTT 0xA4
LOW_POWER_USTT 0xA5
QUIET 0x96
BALANCED 0x97
BALANCED_PERFORMANCE 0x98
PERFORMANCE 0x99
GMODE 0xAB
Usually if a model doesn't support the first four profiles they will support
the User Selectable Thermal Tables (USTT) profiles and vice-versa.
GMODE replaces PERFORMANCE in G-Series laptops.
WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
::
if BYTE_0(arg2) == 0x1:
TOGGLE_GAME_SHIFT()
argr = GET_GAME_SHIFT_STATUS()
if BYTE_0(arg2) == 0x2:
argr = GET_GAME_SHIFT_STATUS()
Game Shift Status does not change the fan speed profile but it could be some
sort of CPU/GPU power profile. Benchmarks have not been done.
This method is only present on Dell's G-Series laptops and it's implementation
implies GMODE thermal profile is available, even if operation 0x03 of
Thermal_Information does not list it.
G-key on Dell's G-Series laptops also changes Game Shift status, so both are
directly related.
WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------
::
if BYTE_0(arg2) == 0x1:
if is_valid_fan(BYTE_1(arg2)):
argr = 1
else:
argr = 0
if BYTE_0(arg2) == 0x2:
if is_valid_fan(BYTE_1(arg2)):
if BYTE_2(arg2) == 0:
argr == SENSOR_ID
else
argr == 0xFFFFFFFF
else:
argr = 0
Overclocking Methods
====================
.. warning::
These methods have not been tested and are only partially reverse
engineered.
WMI method Return_OverclockingReport([out] uint32 argr)
-------------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method Clear_OCFailSafeFlag([out] uint32 argr)
--------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
AWCC supports memory overclocking, but this method is very intricate and has
not been deciphered yet.
GPIO methods
============
These methods are probably related to some kind of firmware update system,
through a GPIO device.
.. warning::
These methods have not been tested and are only partially reverse
engineered.
WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
------------------------------------------------------------------
::
if BYTE_0(arg2) == 0:
if BYTE_1(arg2) == 1:
SET_PIN_A_HIGH()
else:
SET_PIN_A_LOW()
if BYTE_0(arg2) == 1:
if BYTE_1(arg2) == 1:
SET_PIN_B_HIGH()
else:
SET_PIN_B_LOW()
else:
argr = 1
WMI method ReadTotalofGPIOs([out] uint32 argr)
----------------------------------------------
::
argr = 0x02
WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
------------------------------------------------------------------
::
if BYTE_0(arg2) == 0:
argr = PIN_A_STATUS
if BYTE_0(arg2) == 1:
argr = PIN_B_STATUS
Other information Methods
=========================
WMI method ReadChassisColor([out] uint32 argr)
----------------------------------------------
::
argr = CHASSIS_COLOR_ID
Acknowledgements
================
Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
and testing available thermal profile codes.

View File

@ -64,6 +64,7 @@ to matching WMI devices using a struct wmi_device_id table:
.id_table = foo_id_table,
.probe = foo_probe,
.remove = foo_remove, /* optional, devres is preferred */
.shutdown = foo_shutdown, /* optional, called during shutdown */
.notify = foo_notify, /* optional, for event handling */
.no_notify_data = true, /* optional, enables events containing no additional data */
.no_singleton = true, /* required for new WMI drivers */
@ -79,6 +80,10 @@ to unregister interfaces to other kernel subsystems and release resources, devre
This simplifies error handling during probe and often allows to omit this callback entirely, see
Documentation/driver-api/driver-model/devres.rst for details.
The shutdown() callback is called during shutdown, reboot or kexec. Its sole purpose is to disable
the WMI device and put it in a well-known state for the WMI driver to pick up later after reboot
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
Please note that new WMI drivers are required to be able to be instantiated multiple times,
and are forbidden from using any deprecated GUID-based WMI functions. This means that the
WMI driver should be prepared for the scenario that multiple matching WMI devices are present
@ -123,7 +128,7 @@ ACPI object is being done by the WMI subsystem, not the driver.
The WMI driver core will take care that the notify() callback will only be called after
the probe() callback has been called, and that no events are being received by the driver
right before and after calling its remove() callback.
right before and after calling its remove() or shutdown() callback.
However WMI driver developers should be aware that multiple WMI events can be received concurrently,
so any locking (if necessary) needs to be provided by the WMI driver itself.

View File

@ -786,6 +786,7 @@ F: drivers/perf/alibaba_uncore_drw_pmu.c
ALIENWARE WMI DRIVER
L: Dell.Client.Kernel@dell.com
S: Maintained
F: Documentation/wmi/devices/alienware-wmi.rst
F: drivers/platform/x86/dell/alienware-wmi.c
ALLEGRO DVT VIDEO IP CORE DRIVER
@ -965,6 +966,14 @@ Q: https://patchwork.kernel.org/project/linux-rdma/list/
F: drivers/infiniband/hw/efa/
F: include/uapi/rdma/efa-abi.h
AMD 3D V-CACHE PERFORMANCE OPTIMIZER DRIVER
M: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
R: Mario Limonciello <mario.limonciello@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Supported
F: Documentation/ABI/testing/sysfs-bus-platform-drivers-amd_x3d_vcache
F: drivers/platform/x86/amd/x3d_vcache.c
AMD ADDRESS TRANSLATION LIBRARY (ATL)
M: Yazen Ghannam <Yazen.Ghannam@amd.com>
L: linux-edac@vger.kernel.org
@ -1074,7 +1083,7 @@ S: Maintained
F: Documentation/arch/x86/amd_hsmp.rst
F: arch/x86/include/asm/amd_hsmp.h
F: arch/x86/include/uapi/asm/amd_hsmp.h
F: drivers/platform/x86/amd/hsmp.c
F: drivers/platform/x86/amd/hsmp/
AMD IOMMU (AMD-VI)
M: Joerg Roedel <joro@8bytes.org>
@ -1124,7 +1133,7 @@ F: drivers/platform/x86/amd/pmc/
AMD PMF DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
S: Supported
F: Documentation/ABI/testing/sysfs-amd-pmf
F: drivers/platform/x86/amd/pmf/
@ -11538,7 +11547,7 @@ F: Documentation/admin-guide/media/ipu6-isys.rst
F: drivers/media/pci/intel/ipu6/
INTEL ISHTP ECLITE DRIVER
M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com>
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/x86/intel/ishtp_eclite.c
@ -11771,7 +11780,7 @@ M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/debugfs-tpmi
F: drivers/platform/x86/intel/tpmi.c
F: drivers/platform/x86/intel/vsec_tpmi.c
F: include/linux/intel_tpmi.h
INTEL UNCORE FREQUENCY CONTROL

View File

@ -88,7 +88,8 @@ struct hsmp_msg_desc {
*
* Not supported messages would return -ENOMSG.
*/
static const struct hsmp_msg_desc hsmp_msg_desc_table[] = {
static const struct hsmp_msg_desc hsmp_msg_desc_table[]
__attribute__((unused)) = {
/* RESERVED */
{0, 0, HSMP_RSVD},

View File

@ -371,7 +371,7 @@ static const struct software_node *ssam_node_group_sp8[] = {
NULL,
};
/* Devices for Surface Pro 9 and 10 */
/* Devices for Surface Pro 9 (Intel/x86) and 10 */
static const struct software_node *ssam_node_group_sp9[] = {
&ssam_node_root,
&ssam_node_hub_kip,
@ -390,6 +390,21 @@ static const struct software_node *ssam_node_group_sp9[] = {
NULL,
};
/* Devices for Surface Pro 9 5G (ARM/QCOM) */
static const struct software_node *ssam_node_group_sp9_5g[] = {
&ssam_node_root,
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_tmp_sensors,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
&ssam_node_hid_kip_touchpad,
&ssam_node_hid_kip_fwupd,
&ssam_node_hid_sam_sensors,
&ssam_node_kip_tablet_switch,
NULL,
};
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
@ -462,6 +477,8 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Pro 9 5G (ARM/QCOM) */
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },

View File

@ -258,11 +258,6 @@ enum interface_flags {
ACER_WMID_v2,
};
#define ACER_DEFAULT_WIRELESS 0
#define ACER_DEFAULT_BLUETOOTH 0
#define ACER_DEFAULT_MAILLED 0
#define ACER_DEFAULT_THREEG 0
static int max_brightness = 0xF;
static int mailled = -1;
@ -2641,7 +2636,7 @@ static struct platform_driver acer_platform_driver = {
.pm = &acer_pm,
},
.probe = acer_platform_probe,
.remove_new = acer_platform_remove,
.remove = acer_platform_remove,
.shutdown = acer_platform_shutdown,
};

View File

@ -110,7 +110,7 @@ static struct platform_driver adv_swbutton_driver = {
.acpi_match_table = button_device_ids,
},
.probe = adv_swbutton_probe,
.remove_new = adv_swbutton_remove,
.remove = adv_swbutton_remove,
};
module_platform_driver(adv_swbutton_driver);

View File

@ -3,21 +3,21 @@
# AMD x86 Platform Specific Drivers
#
source "drivers/platform/x86/amd/hsmp/Kconfig"
source "drivers/platform/x86/amd/pmf/Kconfig"
source "drivers/platform/x86/amd/pmc/Kconfig"
config AMD_HSMP
tristate "AMD HSMP Driver"
depends on AMD_NB && X86_64 && ACPI
config AMD_3D_VCACHE
tristate "AMD 3D V-Cache Performance Optimizer Driver"
depends on X86_64 && ACPI
help
The driver provides a way for user space tools to monitor and manage
system management functionality on EPYC server CPUs from AMD.
Host System Management Port (HSMP) interface is a mailbox interface
between the x86 core and the System Management Unit (SMU) firmware.
The driver provides a sysfs interface, enabling the setting of a bias
that alters CPU core reordering. This bias prefers cores with higher
frequencies or larger L3 caches on processors supporting AMD 3D V-Cache
technology.
If you choose to compile this driver as a module the module will be
called amd_hsmp.
called amd_3d_vcache.
config AMD_WBRF
bool "AMD Wifi RF Band mitigations (WBRF)"

View File

@ -4,8 +4,9 @@
# AMD x86 Platform-Specific Drivers
#
obj-$(CONFIG_AMD_3D_VCACHE) += amd_3d_vcache.o
amd_3d_vcache-objs := x3d_vcache.o
obj-$(CONFIG_AMD_PMC) += pmc/
amd_hsmp-y := hsmp.o
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
obj-$(CONFIG_AMD_HSMP) += hsmp/
obj-$(CONFIG_AMD_PMF) += pmf/
obj-$(CONFIG_AMD_WBRF) += wbrf.o

View File

@ -1,988 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD HSMP Platform Driver
* Copyright (c) 2022, AMD.
* All Rights Reserved.
*
* This file provides a device implementation for HSMP interface
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_hsmp.h>
#include <asm/amd_nb.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
#include <linux/acpi.h>
#define DRIVER_NAME "amd_hsmp"
#define DRIVER_VERSION "2.2"
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
/* HSMP Status / Error codes */
#define HSMP_STATUS_NOT_READY 0x00
#define HSMP_STATUS_OK 0x01
#define HSMP_ERR_INVALID_MSG 0xFE
#define HSMP_ERR_INVALID_INPUT 0xFF
/* Timeout in millsec */
#define HSMP_MSG_TIMEOUT 100
#define HSMP_SHORT_SLEEP 1
#define HSMP_WR true
#define HSMP_RD false
/*
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
* register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
* Below are required SMN address for HSMP Mailbox register offsets in SMU address space
*/
#define SMN_HSMP_BASE 0x3B00000
#define SMN_HSMP_MSG_ID 0x0010534
#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
#define SMN_HSMP_MSG_RESP 0x0010980
#define SMN_HSMP_MSG_DATA 0x00109E0
#define HSMP_INDEX_REG 0xc4
#define HSMP_DATA_REG 0xc8
#define HSMP_CDEV_NAME "hsmp_cdev"
#define HSMP_DEVNODE_NAME "hsmp"
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
#define HSMP_ATTR_GRP_NAME_SIZE 10
/* These are the strings specified in ACPI table */
#define MSG_IDOFF_STR "MsgIdOffset"
#define MSG_ARGOFF_STR "MsgArgOffset"
#define MSG_RESPOFF_STR "MsgRspOffset"
#define MAX_AMD_SOCKETS 8
struct hsmp_mbaddr_info {
u32 base_addr;
u32 msg_id_off;
u32 msg_resp_off;
u32 msg_arg_off;
u32 size;
};
struct hsmp_socket {
struct bin_attribute hsmp_attr;
struct hsmp_mbaddr_info mbinfo;
void __iomem *metric_tbl_addr;
void __iomem *virt_base_addr;
struct semaphore hsmp_sem;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct pci_dev *root;
struct device *dev;
u16 sock_ind;
};
struct hsmp_plat_device {
struct miscdevice hsmp_device;
struct hsmp_socket *sock;
u32 proto_ver;
u16 num_sockets;
bool is_acpi_device;
bool is_probed;
};
static struct hsmp_plat_device plat_dev;
static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
int ret;
if (!sock->root)
return -ENODEV;
ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
sock->mbinfo.base_addr + offset);
if (ret)
return ret;
ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
: pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
return ret;
}
static void amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
if (write)
iowrite32(*value, sock->virt_base_addr + offset);
else
*value = ioread32(sock->virt_base_addr + offset);
}
static int amd_hsmp_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
if (plat_dev.is_acpi_device)
amd_hsmp_acpi_rdwr(sock, offset, value, write);
else
return amd_hsmp_pci_rdwr(sock, offset, value, write);
return 0;
}
/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
*
* The caller is expected to zero out any unused arguments.
* If a response is expected, the number of response words should be greater than 0.
*
* Returns 0 for success and populates the requested number of arguments.
* Returns a negative error code for failure.
*/
static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
{
struct hsmp_mbaddr_info *mbinfo;
unsigned long timeout, short_sleep;
u32 mbox_status;
u32 index;
int ret;
mbinfo = &sock->mbinfo;
/* Clear the status register */
mbox_status = HSMP_STATUS_NOT_READY;
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
if (ret) {
pr_err("Error %d clearing mailbox status register\n", ret);
return ret;
}
index = 0;
/* Write any message arguments */
while (index < msg->num_args) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_WR);
if (ret) {
pr_err("Error %d writing message argument %d\n", ret, index);
return ret;
}
index++;
}
/* Write the message ID which starts the operation */
ret = amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
if (ret) {
pr_err("Error %d writing message ID %u\n", ret, msg->msg_id);
return ret;
}
/*
* Depending on when the trigger write completes relative to the SMU
* firmware 1 ms cycle, the operation may take from tens of us to 1 ms
* to complete. Some operations may take more. Therefore we will try
* a few short duration sleeps and switch to long sleeps if we don't
* succeed quickly.
*/
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
while (time_before(jiffies, timeout)) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
if (ret) {
pr_err("Error %d reading mailbox status\n", ret);
return ret;
}
if (mbox_status != HSMP_STATUS_NOT_READY)
break;
if (time_before(jiffies, short_sleep))
usleep_range(50, 100);
else
usleep_range(1000, 2000);
}
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
return -ETIMEDOUT;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
return -ENOMSG;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
return -EINVAL;
} else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
pr_err("Message ID %u unknown failure (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EIO;
}
/*
* SMU has responded OK. Read response data.
* SMU reads the input arguments from eight 32 bit registers starting
* from SMN_HSMP_MSG_DATA and writes the response data to the same
* SMN_HSMP_MSG_DATA address.
* We copy the response data if any, back to the args[].
*/
index = 0;
while (index < msg->response_sz) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_RD);
if (ret) {
pr_err("Error %d reading response %u for message ID:%u\n",
ret, index, msg->msg_id);
break;
}
index++;
}
return ret;
}
static int validate_message(struct hsmp_message *msg)
{
/* msg_id against valid range of message IDs */
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
/* msg_id is a reserved message ID */
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
return -ENOMSG;
/* num_args and response_sz against the HSMP spec */
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
return -EINVAL;
return 0;
}
int hsmp_send_message(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
int ret;
if (!msg)
return -EINVAL;
ret = validate_message(msg);
if (ret)
return ret;
if (!plat_dev.sock || msg->sock_ind >= plat_dev.num_sockets)
return -ENODEV;
sock = &plat_dev.sock[msg->sock_ind];
/*
* The time taken by smu operation to complete is between
* 10us to 1ms. Sometime it may take more time.
* In SMP system timeout of 100 millisecs should
* be enough for the previous thread to finish the operation
*/
ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
if (ret < 0)
return ret;
ret = __hsmp_send_message(sock, msg);
up(&sock->hsmp_sem);
return ret;
}
EXPORT_SYMBOL_GPL(hsmp_send_message);
static int hsmp_test(u16 sock_ind, u32 value)
{
struct hsmp_message msg = { 0 };
int ret;
/*
* Test the hsmp port by performing TEST command. The test message
* takes one argument and returns the value of that argument + 1.
*/
msg.msg_id = HSMP_TEST;
msg.num_args = 1;
msg.response_sz = 1;
msg.args[0] = value;
msg.sock_ind = sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
/* Check the response value */
if (msg.args[0] != (value + 1)) {
dev_err(plat_dev.sock[sock_ind].dev,
"Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
sock_ind, (value + 1), msg.args[0]);
return -EBADE;
}
return ret;
}
static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
int ret;
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
return -EFAULT;
/*
* Check msg_id is within the range of supported msg ids
* i.e within the array bounds of hsmp_msg_desc_table
*/
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
case FMODE_WRITE:
/*
* Device is opened in O_WRONLY mode
* Execute only set/configure commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
return -EINVAL;
break;
case FMODE_READ:
/*
* Device is opened in O_RDONLY mode
* Execute only get/monitor commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
return -EINVAL;
break;
case FMODE_READ | FMODE_WRITE:
/*
* Device is opened in O_RDWR mode
* Execute both get/monitor and set/configure commands
*/
break;
default:
return -EINVAL;
}
ret = hsmp_send_message(&msg);
if (ret)
return ret;
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
/* Copy results back to user for get/monitor commands */
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
return -EFAULT;
}
return 0;
}
static const struct file_operations hsmp_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = hsmp_ioctl,
.compat_ioctl = hsmp_ioctl,
};
/* This is the UUID used for HSMP */
static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
0xa6, 0x9f, 0x4e, 0xa2,
0x87, 0x1f, 0xc2, 0xf6);
static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
{
if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
return false;
}
static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
{
char *uid;
/*
* UID (ID00, ID01..IDXX) is used for differentiating sockets,
* read it and strip the "ID" part of it and convert the remaining
* bytes to integer.
*/
uid = acpi_device_uid(ACPI_COMPANION(dev));
return kstrtou16(uid + 2, 10, sock_ind);
}
static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
{
struct hsmp_socket *sock = data;
struct resource r;
switch (res->type) {
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
if (!acpi_dev_resource_memory(res, &r))
return AE_ERROR;
if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
return AE_ERROR;
sock->mbinfo.base_addr = r.start;
sock->mbinfo.size = resource_size(&r);
break;
case ACPI_RESOURCE_TYPE_END_TAG:
break;
default:
return AE_ERROR;
}
return AE_OK;
}
static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *guid, *mailbox_package;
union acpi_object *dsd;
acpi_status status;
int ret = 0;
int j;
status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->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",
acpi_format_exception(status));
return -ENODEV;
}
dsd = buf.pointer;
/* HSMP _DSD property should contain 2 objects.
* 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
* 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
* This mailbox object contains 3 more acpi objects of type
* ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
* these packages inturn contain 2 acpi objects of type
* ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
*/
if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
ret = -EINVAL;
goto free_buf;
}
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");
ret = -EINVAL;
goto free_buf;
}
for (j = 0; j < mailbox_package->package.count; j++) {
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 ||
msgint->type != ACPI_TYPE_INTEGER) {
ret = -EINVAL;
goto free_buf;
}
if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_id_off = msgint->integer.value;
} else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_resp_off = msgint->integer.value;
} else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_arg_off = msgint->integer.value;
} else {
ret = -ENOENT;
goto free_buf;
}
}
if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
!sock->mbinfo.msg_arg_off)
ret = -EINVAL;
free_buf:
ACPI_FREE(buf.pointer);
return ret;
}
static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
{
acpi_status status;
status = acpi_walk_resources(ACPI_HANDLE(sock->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",
acpi_format_exception(status));
return -EINVAL;
}
if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
return -EINVAL;
/* The mapped region should be un cached */
sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
sock->mbinfo.size);
if (!sock->virt_base_addr) {
dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
return -ENOMEM;
}
return 0;
}
/* Parse the ACPI table to read the data */
static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
{
struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
int ret;
sock->sock_ind = sock_ind;
sock->dev = dev;
plat_dev.is_acpi_device = true;
sema_init(&sock->hsmp_sem, 1);
/* Read MP1 base address from CRS method */
ret = hsmp_read_acpi_crs(sock);
if (ret)
return ret;
/* Read mailbox offsets from DSD table */
return hsmp_read_acpi_dsd(sock);
}
static ssize_t hsmp_metric_tbl_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct hsmp_socket *sock = bin_attr->private;
struct hsmp_message msg = { 0 };
int ret;
if (!sock)
return -EINVAL;
/* Do not support lseek(), reads entire metric table */
if (count < bin_attr->size) {
dev_err(sock->dev, "Wrong buffer size\n");
return -EINVAL;
}
msg.msg_id = HSMP_GET_METRIC_TABLE;
msg.sock_ind = sock->sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
memcpy_fromio(buf, sock->metric_tbl_addr, bin_attr->size);
return bin_attr->size;
}
static int hsmp_get_tbl_dram_base(u16 sock_ind)
{
struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
struct hsmp_message msg = { 0 };
phys_addr_t dram_addr;
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);
if (ret)
return ret;
/*
* calculate the metric table DRAM address from lower and upper 32 bits
* sent from SMU and ioremap it to virtual address.
*/
dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
if (!dram_addr) {
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));
if (!sock->metric_tbl_addr) {
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
return -ENOMEM;
}
return 0;
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
struct bin_attribute *battr, int id)
{
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
return battr->attr.mode;
else
return 0;
}
static int hsmp_init_metric_tbl_bin_attr(struct bin_attribute **hattrs, u16 sock_ind)
{
struct bin_attribute *hattr = &plat_dev.sock[sock_ind].hsmp_attr;
sysfs_bin_attr_init(hattr);
hattr->attr.name = HSMP_METRICS_TABLE_NAME;
hattr->attr.mode = 0444;
hattr->read = hsmp_metric_tbl_read;
hattr->size = sizeof(struct hsmp_metric_table);
hattr->private = &plat_dev.sock[sock_ind];
hattrs[0] = hattr;
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
return hsmp_get_tbl_dram_base(sock_ind);
else
return 0;
}
/* One bin sysfs for metrics table */
#define NUM_HSMP_ATTRS 1
static int hsmp_create_attr_list(struct attribute_group *attr_grp,
struct device *dev, u16 sock_ind)
{
struct bin_attribute **hsmp_bin_attrs;
/* Null terminated list of attributes */
hsmp_bin_attrs = devm_kcalloc(dev, NUM_HSMP_ATTRS + 1,
sizeof(*hsmp_bin_attrs),
GFP_KERNEL);
if (!hsmp_bin_attrs)
return -ENOMEM;
attr_grp->bin_attrs = hsmp_bin_attrs;
return hsmp_init_metric_tbl_bin_attr(hsmp_bin_attrs, sock_ind);
}
static int hsmp_create_non_acpi_sysfs_if(struct device *dev)
{
const struct attribute_group **hsmp_attr_grps;
struct attribute_group *attr_grp;
u16 i;
hsmp_attr_grps = devm_kcalloc(dev, plat_dev.num_sockets + 1,
sizeof(*hsmp_attr_grps),
GFP_KERNEL);
if (!hsmp_attr_grps)
return -ENOMEM;
/* Create a sysfs directory for each socket */
for (i = 0; i < plat_dev.num_sockets; i++) {
attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group),
GFP_KERNEL);
if (!attr_grp)
return -ENOMEM;
snprintf(plat_dev.sock[i].name, HSMP_ATTR_GRP_NAME_SIZE, "socket%u", (u8)i);
attr_grp->name = plat_dev.sock[i].name;
attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
hsmp_attr_grps[i] = attr_grp;
hsmp_create_attr_list(attr_grp, dev, i);
}
return device_add_groups(dev, hsmp_attr_grps);
}
static int hsmp_create_acpi_sysfs_if(struct device *dev)
{
struct attribute_group *attr_grp;
u16 sock_ind;
int ret;
attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group), GFP_KERNEL);
if (!attr_grp)
return -ENOMEM;
attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
ret = hsmp_get_uid(dev, &sock_ind);
if (ret)
return ret;
ret = hsmp_create_attr_list(attr_grp, dev, sock_ind);
if (ret)
return ret;
return devm_device_add_group(dev, attr_grp);
}
static int hsmp_cache_proto_ver(u16 sock_ind)
{
struct hsmp_message msg = { 0 };
int ret;
msg.msg_id = HSMP_GET_PROTO_VER;
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
ret = hsmp_send_message(&msg);
if (!ret)
plat_dev.proto_ver = msg.args[0];
return ret;
}
static inline bool is_f1a_m0h(void)
{
if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
return true;
return false;
}
static int init_platform_device(struct device *dev)
{
struct hsmp_socket *sock;
int ret, i;
for (i = 0; i < plat_dev.num_sockets; i++) {
if (!node_to_amd_nb(i))
return -ENODEV;
sock = &plat_dev.sock[i];
sock->root = node_to_amd_nb(i)->root;
sock->sock_ind = i;
sock->dev = dev;
sock->mbinfo.base_addr = SMN_HSMP_BASE;
/*
* This is a transitional change from non-ACPI to ACPI, only
* family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
*/
if (is_f1a_m0h())
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
else
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
sema_init(&sock->hsmp_sem, 1);
/* Test the hsmp interface on each socket */
ret = hsmp_test(i, 0xDEADBEEF);
if (ret) {
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
return ret;
}
}
return 0;
}
static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
{ACPI_HSMP_DEVICE_HID, 0},
{}
};
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
static int hsmp_pltdrv_probe(struct platform_device *pdev)
{
struct acpi_device *adev;
u16 sock_ind = 0;
int ret;
/*
* On ACPI supported BIOS, there is an ACPI HSMP device added for
* each socket, so the per socket probing, but the memory allocated for
* sockets should be contiguous to access it as an array,
* Hence allocate memory for all the sockets at once instead of allocating
* on each probe.
*/
if (!plat_dev.is_probed) {
plat_dev.sock = devm_kcalloc(&pdev->dev, plat_dev.num_sockets,
sizeof(*plat_dev.sock),
GFP_KERNEL);
if (!plat_dev.sock)
return -ENOMEM;
}
adev = ACPI_COMPANION(&pdev->dev);
if (adev && !acpi_match_device_ids(adev, amd_hsmp_acpi_ids)) {
ret = hsmp_get_uid(&pdev->dev, &sock_ind);
if (ret)
return ret;
if (sock_ind >= plat_dev.num_sockets)
return -EINVAL;
ret = hsmp_parse_acpi_table(&pdev->dev, sock_ind);
if (ret) {
dev_err(&pdev->dev, "Failed to parse ACPI table\n");
return ret;
}
/* Test the hsmp interface */
ret = hsmp_test(sock_ind, 0xDEADBEEF);
if (ret) {
dev_err(&pdev->dev, "HSMP test message failed on Fam:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
dev_err(&pdev->dev, "Is HSMP disabled in BIOS ?\n");
return ret;
}
} else {
ret = init_platform_device(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
return ret;
}
}
ret = hsmp_cache_proto_ver(sock_ind);
if (ret) {
dev_err(&pdev->dev, "Failed to read HSMP protocol version\n");
return ret;
}
if (plat_dev.is_acpi_device)
ret = hsmp_create_acpi_sysfs_if(&pdev->dev);
else
ret = hsmp_create_non_acpi_sysfs_if(&pdev->dev);
if (ret)
dev_err(&pdev->dev, "Failed to create HSMP sysfs interface\n");
if (!plat_dev.is_probed) {
plat_dev.hsmp_device.name = HSMP_CDEV_NAME;
plat_dev.hsmp_device.minor = MISC_DYNAMIC_MINOR;
plat_dev.hsmp_device.fops = &hsmp_fops;
plat_dev.hsmp_device.parent = &pdev->dev;
plat_dev.hsmp_device.nodename = HSMP_DEVNODE_NAME;
plat_dev.hsmp_device.mode = 0644;
ret = misc_register(&plat_dev.hsmp_device);
if (ret)
return ret;
plat_dev.is_probed = true;
}
return 0;
}
static void hsmp_pltdrv_remove(struct platform_device *pdev)
{
/*
* We register only one misc_device even on multi socket system.
* So, deregister should happen only once.
*/
if (plat_dev.is_probed) {
misc_deregister(&plat_dev.hsmp_device);
plat_dev.is_probed = false;
}
}
static struct platform_driver amd_hsmp_driver = {
.probe = hsmp_pltdrv_probe,
.remove_new = hsmp_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = amd_hsmp_acpi_ids,
},
};
static struct platform_device *amd_hsmp_platdev;
static int hsmp_plat_dev_register(void)
{
int ret;
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
if (!amd_hsmp_platdev)
return -ENOMEM;
ret = platform_device_add(amd_hsmp_platdev);
if (ret)
platform_device_put(amd_hsmp_platdev);
return ret;
}
/*
* This check is only needed for backward compatibility of previous platforms.
* All new platforms are expected to support ACPI based probing.
*/
static bool legacy_hsmp_support(void)
{
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
return false;
switch (boot_cpu_data.x86) {
case 0x19:
switch (boot_cpu_data.x86_model) {
case 0x00 ... 0x1F:
case 0x30 ... 0x3F:
case 0x90 ... 0x9F:
case 0xA0 ... 0xAF:
return true;
default:
return false;
}
case 0x1A:
switch (boot_cpu_data.x86_model) {
case 0x00 ... 0x1F:
return true;
default:
return false;
}
default:
return false;
}
return false;
}
static int __init hsmp_plt_init(void)
{
int ret = -ENODEV;
/*
* amd_nb_num() returns number of SMN/DF interfaces present in the system
* if we have N SMN/DF interfaces that ideally means N sockets
*/
plat_dev.num_sockets = amd_nb_num();
if (plat_dev.num_sockets == 0 || plat_dev.num_sockets > MAX_AMD_SOCKETS)
return ret;
ret = platform_driver_register(&amd_hsmp_driver);
if (ret)
return ret;
if (!plat_dev.is_acpi_device) {
if (legacy_hsmp_support()) {
/* Not ACPI device, but supports HSMP, register a plat_dev */
ret = hsmp_plat_dev_register();
} else {
/* Not ACPI, Does not support HSMP */
pr_info("HSMP is not supported on Family:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
ret = -ENODEV;
}
if (ret)
platform_driver_unregister(&amd_hsmp_driver);
}
return ret;
}
static void __exit hsmp_plt_exit(void)
{
platform_device_unregister(amd_hsmp_platdev);
platform_driver_unregister(&amd_hsmp_driver);
}
device_initcall(hsmp_plt_init);
module_exit(hsmp_plt_exit);
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,47 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# AMD HSMP Driver
#
config AMD_HSMP
tristate
menu "AMD HSMP Driver"
depends on AMD_NB || COMPILE_TEST
config AMD_HSMP_ACPI
tristate "AMD HSMP ACPI device driver"
depends on ACPI
select AMD_HSMP
help
Host System Management Port (HSMP) interface is a mailbox interface
between the x86 core and the System Management Unit (SMU) firmware.
The driver provides a way for user space tools to monitor and manage
system management functionality on EPYC and MI300A server CPUs
from AMD.
This option supports ACPI based probing.
You may enable this, if your platform BIOS provides an ACPI object
as described in amd_hsmp.rst document.
If you choose to compile this driver as a module the module will be
called hsmp_acpi.
config AMD_HSMP_PLAT
tristate "AMD HSMP platform device driver"
select AMD_HSMP
help
Host System Management Port (HSMP) interface is a mailbox interface
between the x86 core and the System Management Unit (SMU) firmware.
The driver provides a way for user space tools to monitor and manage
system management functionality on EPYC and MI300A server CPUs
from AMD.
This option supports platform device based probing.
You may enable this, if your platform BIOS does not provide
HSMP ACPI object.
If you choose to compile this driver as a module the module will be
called amd_hsmp.
endmenu

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for drivers/platform/x86/amd/hsmp
# AMD HSMP Driver
#
obj-$(CONFIG_AMD_HSMP) += hsmp_common.o
hsmp_common-objs := hsmp.o
obj-$(CONFIG_AMD_HSMP_PLAT) += amd_hsmp.o
amd_hsmp-objs := plat.o
obj-$(CONFIG_AMD_HSMP_ACPI) += hsmp_acpi.o
hsmp_acpi-objs := acpi.o

View File

@ -0,0 +1,378 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD HSMP Platform Driver
* Copyright (c) 2024, AMD.
* All Rights Reserved.
*
* This file provides an ACPI based driver implementation for HSMP interface.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_hsmp.h>
#include <asm/amd_nb.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/ioport.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include <linux/uuid.h>
#include <uapi/asm-generic/errno-base.h>
#include "hsmp.h"
#define DRIVER_NAME "amd_hsmp"
#define DRIVER_VERSION "2.3"
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
/* These are the strings specified in ACPI table */
#define MSG_IDOFF_STR "MsgIdOffset"
#define MSG_ARGOFF_STR "MsgArgOffset"
#define MSG_RESPOFF_STR "MsgRspOffset"
static struct hsmp_plat_device *hsmp_pdev;
static int amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
if (write)
iowrite32(*value, sock->virt_base_addr + offset);
else
*value = ioread32(sock->virt_base_addr + offset);
return 0;
}
/* This is the UUID used for HSMP */
static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
0xa6, 0x9f, 0x4e, 0xa2,
0x87, 0x1f, 0xc2, 0xf6);
static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
{
if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
return false;
}
static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
{
char *uid;
/*
* UID (ID00, ID01..IDXX) is used for differentiating sockets,
* read it and strip the "ID" part of it and convert the remaining
* bytes to integer.
*/
uid = acpi_device_uid(ACPI_COMPANION(dev));
return kstrtou16(uid + 2, 10, sock_ind);
}
static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
{
struct hsmp_socket *sock = data;
struct resource r;
switch (res->type) {
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
if (!acpi_dev_resource_memory(res, &r))
return AE_ERROR;
if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
return AE_ERROR;
sock->mbinfo.base_addr = r.start;
sock->mbinfo.size = resource_size(&r);
break;
case ACPI_RESOURCE_TYPE_END_TAG:
break;
default:
return AE_ERROR;
}
return AE_OK;
}
static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *guid, *mailbox_package;
union acpi_object *dsd;
acpi_status status;
int ret = 0;
int j;
status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->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",
acpi_format_exception(status));
return -ENODEV;
}
dsd = buf.pointer;
/* HSMP _DSD property should contain 2 objects.
* 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
* 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
* This mailbox object contains 3 more acpi objects of type
* ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
* these packages inturn contain 2 acpi objects of type
* ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
*/
if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
ret = -EINVAL;
goto free_buf;
}
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");
ret = -EINVAL;
goto free_buf;
}
for (j = 0; j < mailbox_package->package.count; j++) {
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 ||
msgint->type != ACPI_TYPE_INTEGER) {
ret = -EINVAL;
goto free_buf;
}
if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_id_off = msgint->integer.value;
} else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_resp_off = msgint->integer.value;
} else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
msgstr->string.length)) {
sock->mbinfo.msg_arg_off = msgint->integer.value;
} else {
ret = -ENOENT;
goto free_buf;
}
}
if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
!sock->mbinfo.msg_arg_off)
ret = -EINVAL;
free_buf:
ACPI_FREE(buf.pointer);
return ret;
}
static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
{
acpi_status status;
status = acpi_walk_resources(ACPI_HANDLE(sock->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",
acpi_format_exception(status));
return -EINVAL;
}
if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
return -EINVAL;
/* The mapped region should be un-cached */
sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
sock->mbinfo.size);
if (!sock->virt_base_addr) {
dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
return -ENOMEM;
}
return 0;
}
/* Parse the ACPI table to read the data */
static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
{
struct hsmp_socket *sock = &hsmp_pdev->sock[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);
dev_set_drvdata(dev, sock);
/* Read MP1 base address from CRS method */
ret = hsmp_read_acpi_crs(sock);
if (ret)
return ret;
/* Read mailbox offsets from DSD table */
return hsmp_read_acpi_dsd(sock);
}
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct hsmp_socket *sock = dev_get_drvdata(dev);
return hsmp_metric_tbl_read(sock, buf, count);
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
struct bin_attribute *battr, int id)
{
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
return battr->attr.mode;
return 0;
}
static int init_acpi(struct device *dev)
{
u16 sock_ind;
int ret;
ret = hsmp_get_uid(dev, &sock_ind);
if (ret)
return ret;
if (sock_ind >= hsmp_pdev->num_sockets)
return -EINVAL;
ret = hsmp_parse_acpi_table(dev, sock_ind);
if (ret) {
dev_err(dev, "Failed to parse ACPI table\n");
return ret;
}
/* Test the hsmp interface */
ret = hsmp_test(sock_ind, 0xDEADBEEF);
if (ret) {
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
return ret;
}
ret = hsmp_cache_proto_ver(sock_ind);
if (ret) {
dev_err(dev, "Failed to read HSMP protocol version\n");
return ret;
}
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(sock_ind);
if (ret)
dev_err(dev, "Failed to init metric table\n");
}
return ret;
}
static struct bin_attribute hsmp_metric_tbl_attr = {
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
.read = hsmp_metric_tbl_acpi_read,
.size = sizeof(struct hsmp_metric_table),
};
static struct bin_attribute *hsmp_attr_list[] = {
&hsmp_metric_tbl_attr,
NULL
};
static struct attribute_group hsmp_attr_grp = {
.bin_attrs = hsmp_attr_list,
.is_bin_visible = hsmp_is_sock_attr_visible,
};
static const struct attribute_group *hsmp_groups[] = {
&hsmp_attr_grp,
NULL
};
static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
{ACPI_HSMP_DEVICE_HID, 0},
{}
};
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
static int hsmp_acpi_probe(struct platform_device *pdev)
{
int ret;
hsmp_pdev = get_hsmp_pdev();
if (!hsmp_pdev)
return -ENOMEM;
if (!hsmp_pdev->is_probed) {
hsmp_pdev->num_sockets = amd_nb_num();
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
return -ENODEV;
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
sizeof(*hsmp_pdev->sock),
GFP_KERNEL);
if (!hsmp_pdev->sock)
return -ENOMEM;
}
ret = init_acpi(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
return ret;
}
if (!hsmp_pdev->is_probed) {
ret = hsmp_misc_register(&pdev->dev);
if (ret)
return ret;
hsmp_pdev->is_probed = true;
}
return 0;
}
static void hsmp_acpi_remove(struct platform_device *pdev)
{
/*
* We register only one misc_device even on multi-socket system.
* So, deregister should happen only once.
*/
if (hsmp_pdev->is_probed) {
hsmp_misc_deregister();
hsmp_pdev->is_probed = false;
}
}
static struct platform_driver amd_hsmp_driver = {
.probe = hsmp_acpi_probe,
.remove = hsmp_acpi_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = amd_hsmp_acpi_ids,
.dev_groups = hsmp_groups,
},
};
module_platform_driver(amd_hsmp_driver);
MODULE_IMPORT_NS(AMD_HSMP);
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD HSMP Platform Driver
* Copyright (c) 2022, AMD.
* All Rights Reserved.
*
* This file provides a device implementation for HSMP interface
*/
#include <asm/amd_hsmp.h>
#include <asm/amd_nb.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
#include "hsmp.h"
/* HSMP Status / Error codes */
#define HSMP_STATUS_NOT_READY 0x00
#define HSMP_STATUS_OK 0x01
#define HSMP_ERR_INVALID_MSG 0xFE
#define HSMP_ERR_INVALID_INPUT 0xFF
#define HSMP_ERR_PREREQ_NOT_SATISFIED 0xFD
#define HSMP_ERR_SMU_BUSY 0xFC
/* Timeout in millsec */
#define HSMP_MSG_TIMEOUT 100
#define HSMP_SHORT_SLEEP 1
#define HSMP_WR true
#define HSMP_RD false
#define DRIVER_VERSION "2.3"
static struct hsmp_plat_device hsmp_pdev;
/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
*
* The caller is expected to zero out any unused arguments.
* If a response is expected, the number of response words should be greater than 0.
*
* Returns 0 for success and populates the requested number of arguments.
* Returns a negative error code for failure.
*/
static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
{
struct hsmp_mbaddr_info *mbinfo;
unsigned long timeout, short_sleep;
u32 mbox_status;
u32 index;
int ret;
mbinfo = &sock->mbinfo;
/* Clear the status register */
mbox_status = HSMP_STATUS_NOT_READY;
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
if (ret) {
dev_err(sock->dev, "Error %d clearing mailbox status register\n", ret);
return ret;
}
index = 0;
/* Write any message arguments */
while (index < msg->num_args) {
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_WR);
if (ret) {
dev_err(sock->dev, "Error %d writing message argument %d\n", ret, index);
return ret;
}
index++;
}
/* Write the message ID which starts the operation */
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
if (ret) {
dev_err(sock->dev, "Error %d writing message ID %u\n", ret, msg->msg_id);
return ret;
}
/*
* Depending on when the trigger write completes relative to the SMU
* firmware 1 ms cycle, the operation may take from tens of us to 1 ms
* to complete. Some operations may take more. Therefore we will try
* a few short duration sleeps and switch to long sleeps if we don't
* succeed quickly.
*/
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
while (time_before(jiffies, timeout)) {
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
if (ret) {
dev_err(sock->dev, "Error %d reading mailbox status\n", ret);
return ret;
}
if (mbox_status != HSMP_STATUS_NOT_READY)
break;
if (time_before(jiffies, short_sleep))
usleep_range(50, 100);
else
usleep_range(1000, 2000);
}
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -ETIMEDOUT;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
dev_err(sock->dev, "Message ID 0x%X failure : Invalid message (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -ENOMSG;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
dev_err(sock->dev, "Message ID 0x%X failure : Invalid arguments (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EINVAL;
} else if (unlikely(mbox_status == HSMP_ERR_PREREQ_NOT_SATISFIED)) {
dev_err(sock->dev, "Message ID 0x%X failure : Prerequisite not satisfied (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EREMOTEIO;
} else if (unlikely(mbox_status == HSMP_ERR_SMU_BUSY)) {
dev_err(sock->dev, "Message ID 0x%X failure : SMU BUSY (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EBUSY;
} else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
dev_err(sock->dev, "Message ID 0x%X unknown failure (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EIO;
}
/*
* SMU has responded OK. Read response data.
* SMU reads the input arguments from eight 32 bit registers starting
* from SMN_HSMP_MSG_DATA and writes the response data to the same
* SMN_HSMP_MSG_DATA address.
* We copy the response data if any, back to the args[].
*/
index = 0;
while (index < msg->response_sz) {
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_RD);
if (ret) {
dev_err(sock->dev, "Error %d reading response %u for message ID:%u\n",
ret, index, msg->msg_id);
break;
}
index++;
}
return ret;
}
static int validate_message(struct hsmp_message *msg)
{
/* msg_id against valid range of message IDs */
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
/* msg_id is a reserved message ID */
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
return -ENOMSG;
/* num_args and response_sz against the HSMP spec */
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
return -EINVAL;
return 0;
}
int hsmp_send_message(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
int ret;
if (!msg)
return -EINVAL;
ret = validate_message(msg);
if (ret)
return ret;
if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets)
return -ENODEV;
sock = &hsmp_pdev.sock[msg->sock_ind];
/*
* The time taken by smu operation to complete is between
* 10us to 1ms. Sometime it may take more time.
* In SMP system timeout of 100 millisecs should
* be enough for the previous thread to finish the operation
*/
ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
if (ret < 0)
return ret;
ret = __hsmp_send_message(sock, msg);
up(&sock->hsmp_sem);
return ret;
}
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, AMD_HSMP);
int hsmp_test(u16 sock_ind, u32 value)
{
struct hsmp_message msg = { 0 };
int ret;
/*
* Test the hsmp port by performing TEST command. The test message
* takes one argument and returns the value of that argument + 1.
*/
msg.msg_id = HSMP_TEST;
msg.num_args = 1;
msg.response_sz = 1;
msg.args[0] = value;
msg.sock_ind = sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
/* Check the response value */
if (msg.args[0] != (value + 1)) {
dev_err(hsmp_pdev.sock[sock_ind].dev,
"Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
sock_ind, (value + 1), msg.args[0]);
return -EBADE;
}
return ret;
}
EXPORT_SYMBOL_NS_GPL(hsmp_test, AMD_HSMP);
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
int ret;
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
return -EFAULT;
/*
* Check msg_id is within the range of supported msg ids
* i.e within the array bounds of hsmp_msg_desc_table
*/
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
case FMODE_WRITE:
/*
* Device is opened in O_WRONLY mode
* Execute only set/configure commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
return -EPERM;
break;
case FMODE_READ:
/*
* Device is opened in O_RDONLY mode
* Execute only get/monitor commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
return -EPERM;
break;
case FMODE_READ | FMODE_WRITE:
/*
* Device is opened in O_RDWR mode
* Execute both get/monitor and set/configure commands
*/
break;
default:
return -EPERM;
}
ret = hsmp_send_message(&msg);
if (ret)
return ret;
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
/* Copy results back to user for get/monitor commands */
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
return -EFAULT;
}
return 0;
}
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
{
struct hsmp_message msg = { 0 };
int ret;
if (!sock || !buf)
return -EINVAL;
/* Do not support lseek(), also don't allow more than the size of metric table */
if (size != sizeof(struct hsmp_metric_table)) {
dev_err(sock->dev, "Wrong buffer size\n");
return -EINVAL;
}
msg.msg_id = HSMP_GET_METRIC_TABLE;
msg.sock_ind = sock->sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
memcpy_fromio(buf, sock->metric_tbl_addr, size);
return size;
}
EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, 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;
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);
if (ret)
return ret;
/*
* calculate the metric table DRAM address from lower and upper 32 bits
* sent from SMU and ioremap it to virtual address.
*/
dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
if (!dram_addr) {
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));
if (!sock->metric_tbl_addr) {
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, AMD_HSMP);
int hsmp_cache_proto_ver(u16 sock_ind)
{
struct hsmp_message msg = { 0 };
int ret;
msg.msg_id = HSMP_GET_PROTO_VER;
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
ret = hsmp_send_message(&msg);
if (!ret)
hsmp_pdev.proto_ver = msg.args[0];
return ret;
}
EXPORT_SYMBOL_NS_GPL(hsmp_cache_proto_ver, AMD_HSMP);
static const struct file_operations hsmp_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = hsmp_ioctl,
.compat_ioctl = hsmp_ioctl,
};
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;
hsmp_pdev.mdev.parent = dev;
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
hsmp_pdev.mdev.mode = 0644;
return misc_register(&hsmp_pdev.mdev);
}
EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, AMD_HSMP);
void hsmp_misc_deregister(void)
{
misc_deregister(&hsmp_pdev.mdev);
}
EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, AMD_HSMP);
struct hsmp_plat_device *get_hsmp_pdev(void)
{
return &hsmp_pdev;
}
EXPORT_SYMBOL_NS_GPL(get_hsmp_pdev, AMD_HSMP);
MODULE_DESCRIPTION("AMD HSMP Common driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* AMD HSMP Platform Driver
* Copyright (c) 2024, AMD.
* All Rights Reserved.
*
* Header file for HSMP driver
*/
#ifndef HSMP_H
#define HSMP_H
#include <linux/compiler_types.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
#define HSMP_ATTR_GRP_NAME_SIZE 10
#define MAX_AMD_SOCKETS 8
#define HSMP_CDEV_NAME "hsmp_cdev"
#define HSMP_DEVNODE_NAME "hsmp"
struct hsmp_mbaddr_info {
u32 base_addr;
u32 msg_id_off;
u32 msg_resp_off;
u32 msg_arg_off;
u32 size;
};
struct hsmp_socket {
struct bin_attribute hsmp_attr;
struct hsmp_mbaddr_info mbinfo;
void __iomem *metric_tbl_addr;
void __iomem *virt_base_addr;
struct semaphore hsmp_sem;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct pci_dev *root;
struct device *dev;
u16 sock_ind;
int (*amd_hsmp_rdwr)(struct hsmp_socket *sock, u32 off, u32 *val, bool rw);
};
struct hsmp_plat_device {
struct miscdevice mdev;
struct hsmp_socket *sock;
u32 proto_ver;
u16 num_sockets;
bool is_probed;
};
int hsmp_cache_proto_ver(u16 sock_ind);
int hsmp_test(u16 sock_ind, u32 value);
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);
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
struct hsmp_plat_device *get_hsmp_pdev(void);
#endif /* HSMP_H */

View File

@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD HSMP Platform Driver
* Copyright (c) 2024, AMD.
* All Rights Reserved.
*
* This file provides platform device implementations.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_hsmp.h>
#include <asm/amd_nb.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include "hsmp.h"
#define DRIVER_NAME "amd_hsmp"
#define DRIVER_VERSION "2.3"
/*
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
* register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
* Below are required SMN address for HSMP Mailbox register offsets in SMU address space
*/
#define SMN_HSMP_BASE 0x3B00000
#define SMN_HSMP_MSG_ID 0x0010534
#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
#define SMN_HSMP_MSG_RESP 0x0010980
#define SMN_HSMP_MSG_DATA 0x00109E0
#define HSMP_INDEX_REG 0xc4
#define HSMP_DATA_REG 0xc8
static struct hsmp_plat_device *hsmp_pdev;
static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
int ret;
if (!sock->root)
return -ENODEV;
ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
sock->mbinfo.base_addr + offset);
if (ret)
return ret;
ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
: pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
return ret;
}
static ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct hsmp_socket *sock;
u16 sock_ind;
sock_ind = (uintptr_t)bin_attr->private;
if (sock_ind >= hsmp_pdev->num_sockets)
return -EINVAL;
sock = &hsmp_pdev->sock[sock_ind];
return hsmp_metric_tbl_read(sock, buf, count);
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
struct bin_attribute *battr, int id)
{
u16 sock_ind;
sock_ind = (uintptr_t)battr->private;
if (id == 0 && sock_ind >= hsmp_pdev->num_sockets)
return SYSFS_GROUP_INVISIBLE;
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
return battr->attr.mode;
return 0;
}
/*
* AMD supports maximum of 8 sockets in a system.
* Static array of 8 + 1(for NULL) elements is created below
* to create sysfs groups for sockets.
* is_bin_visible function is used to show / hide the necessary groups.
*/
#define HSMP_BIN_ATTR(index, _list) \
static struct bin_attribute attr##index = { \
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
.private = (void *)index, \
.read = hsmp_metric_tbl_plat_read, \
.size = sizeof(struct hsmp_metric_table), \
}; \
static struct bin_attribute _list[] = { \
&attr##index, \
NULL \
}
HSMP_BIN_ATTR(0, *sock0_attr_list);
HSMP_BIN_ATTR(1, *sock1_attr_list);
HSMP_BIN_ATTR(2, *sock2_attr_list);
HSMP_BIN_ATTR(3, *sock3_attr_list);
HSMP_BIN_ATTR(4, *sock4_attr_list);
HSMP_BIN_ATTR(5, *sock5_attr_list);
HSMP_BIN_ATTR(6, *sock6_attr_list);
HSMP_BIN_ATTR(7, *sock7_attr_list);
#define HSMP_BIN_ATTR_GRP(index, _list, _name) \
static struct attribute_group sock##index##_attr_grp = { \
.bin_attrs = _list, \
.is_bin_visible = hsmp_is_sock_attr_visible, \
.name = #_name, \
}
HSMP_BIN_ATTR_GRP(0, sock0_attr_list, socket0);
HSMP_BIN_ATTR_GRP(1, sock1_attr_list, socket1);
HSMP_BIN_ATTR_GRP(2, sock2_attr_list, socket2);
HSMP_BIN_ATTR_GRP(3, sock3_attr_list, socket3);
HSMP_BIN_ATTR_GRP(4, sock4_attr_list, socket4);
HSMP_BIN_ATTR_GRP(5, sock5_attr_list, socket5);
HSMP_BIN_ATTR_GRP(6, sock6_attr_list, socket6);
HSMP_BIN_ATTR_GRP(7, sock7_attr_list, socket7);
static const struct attribute_group *hsmp_groups[] = {
&sock0_attr_grp,
&sock1_attr_grp,
&sock2_attr_grp,
&sock3_attr_grp,
&sock4_attr_grp,
&sock5_attr_grp,
&sock6_attr_grp,
&sock7_attr_grp,
NULL
};
static inline bool is_f1a_m0h(void)
{
if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
return true;
return false;
}
static int init_platform_device(struct device *dev)
{
struct hsmp_socket *sock;
int ret, i;
for (i = 0; i < hsmp_pdev->num_sockets; i++) {
if (!node_to_amd_nb(i))
return -ENODEV;
sock = &hsmp_pdev->sock[i];
sock->root = node_to_amd_nb(i)->root;
sock->sock_ind = i;
sock->dev = dev;
sock->mbinfo.base_addr = SMN_HSMP_BASE;
sock->amd_hsmp_rdwr = amd_hsmp_pci_rdwr;
/*
* This is a transitional change from non-ACPI to ACPI, only
* family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
*/
if (is_f1a_m0h())
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
else
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
sema_init(&sock->hsmp_sem, 1);
/* Test the hsmp interface on each socket */
ret = hsmp_test(i, 0xDEADBEEF);
if (ret) {
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
return ret;
}
ret = hsmp_cache_proto_ver(i);
if (ret) {
dev_err(dev, "Failed to read HSMP protocol version\n");
return ret;
}
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(i);
if (ret)
dev_err(dev, "Failed to init metric table\n");
}
}
return 0;
}
static int hsmp_pltdrv_probe(struct platform_device *pdev)
{
int ret;
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
sizeof(*hsmp_pdev->sock),
GFP_KERNEL);
if (!hsmp_pdev->sock)
return -ENOMEM;
ret = init_platform_device(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
return ret;
}
return hsmp_misc_register(&pdev->dev);
}
static void hsmp_pltdrv_remove(struct platform_device *pdev)
{
hsmp_misc_deregister();
}
static struct platform_driver amd_hsmp_driver = {
.probe = hsmp_pltdrv_probe,
.remove = hsmp_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
.dev_groups = hsmp_groups,
},
};
static struct platform_device *amd_hsmp_platdev;
static int hsmp_plat_dev_register(void)
{
int ret;
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
if (!amd_hsmp_platdev)
return -ENOMEM;
ret = platform_device_add(amd_hsmp_platdev);
if (ret)
platform_device_put(amd_hsmp_platdev);
return ret;
}
/*
* This check is only needed for backward compatibility of previous platforms.
* All new platforms are expected to support ACPI based probing.
*/
static bool legacy_hsmp_support(void)
{
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
return false;
switch (boot_cpu_data.x86) {
case 0x19:
switch (boot_cpu_data.x86_model) {
case 0x00 ... 0x1F:
case 0x30 ... 0x3F:
case 0x90 ... 0x9F:
case 0xA0 ... 0xAF:
return true;
default:
return false;
}
case 0x1A:
switch (boot_cpu_data.x86_model) {
case 0x00 ... 0x1F:
return true;
default:
return false;
}
default:
return false;
}
return false;
}
static int __init hsmp_plt_init(void)
{
int ret = -ENODEV;
if (!legacy_hsmp_support()) {
pr_info("HSMP is not supported on Family:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
return ret;
}
hsmp_pdev = get_hsmp_pdev();
if (!hsmp_pdev)
return -ENOMEM;
/*
* amd_nb_num() returns number of SMN/DF interfaces present in the system
* if we have N SMN/DF interfaces that ideally means N sockets
*/
hsmp_pdev->num_sockets = amd_nb_num();
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
return ret;
ret = platform_driver_register(&amd_hsmp_driver);
if (ret)
return ret;
ret = hsmp_plat_dev_register();
if (ret)
platform_driver_unregister(&amd_hsmp_driver);
return ret;
}
static void __exit hsmp_plt_exit(void)
{
platform_device_unregister(amd_hsmp_platdev);
platform_driver_unregister(&amd_hsmp_driver);
}
device_initcall(hsmp_plt_init);
module_exit(hsmp_plt_exit);
MODULE_IMPORT_NS(AMD_HSMP);
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");

View File

@ -1161,7 +1161,7 @@ static struct platform_driver amd_pmc_driver = {
.pm = pm_sleep_ptr(&amd_pmc_pm),
},
.probe = amd_pmc_probe,
.remove_new = amd_pmc_remove,
.remove = amd_pmc_remove,
};
module_platform_driver(amd_pmc_driver);

View File

@ -11,6 +11,7 @@ config AMD_PMF
select ACPI_PLATFORM_PROFILE
depends on TEE && AMDTEE
depends on AMD_SFH_HID
depends on HAS_IOMEM
help
This driver provides support for the AMD Platform Management Framework.
The goal is to enhance end user experience by making AMD PCs smarter,

View File

@ -433,37 +433,29 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
return 0;
}
static acpi_status apmf_walk_resources(struct acpi_resource *res, void *data)
{
struct amd_pmf_dev *dev = data;
switch (res->type) {
case ACPI_RESOURCE_TYPE_ADDRESS64:
dev->policy_addr = res->data.address64.address.minimum;
dev->policy_sz = res->data.address64.address.address_length;
break;
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
dev->policy_addr = res->data.fixed_memory32.address;
dev->policy_sz = res->data.fixed_memory32.address_length;
break;
}
if (!dev->policy_addr || dev->policy_sz > POLICY_BUF_MAX_SZ || dev->policy_sz == 0) {
pr_err("Incorrect Policy params, possibly a SBIOS bug\n");
return AE_ERROR;
}
return AE_OK;
}
int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
{
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
acpi_status status;
struct platform_device *pdev = to_platform_device(pmf_dev->dev);
status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, apmf_walk_resources, pmf_dev);
if (ACPI_FAILURE(status)) {
dev_dbg(pmf_dev->dev, "acpi_walk_resources failed :%d\n", status);
pmf_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!pmf_dev->res) {
dev_dbg(pmf_dev->dev, "Failed to get I/O memory resource\n");
return -EINVAL;
}
pmf_dev->policy_addr = pmf_dev->res->start;
/*
* We cannot use resource_size() here because it adds an extra byte to round off the size.
* In the case of PMF ResourceTemplate(), this rounding is already handled within the _CRS.
* Using resource_size() would increase the resource size by 1, causing a mismatch with the
* length field and leading to issues. Therefore, simply use end-start of the ACPI resource
* to obtain the actual length.
*/
pmf_dev->policy_sz = pmf_dev->res->end - pmf_dev->res->start;
if (!pmf_dev->policy_addr || pmf_dev->policy_sz > POLICY_BUF_MAX_SZ ||
pmf_dev->policy_sz == 0) {
dev_err(pmf_dev->dev, "Incorrect policy params, possibly a SBIOS bug\n");
return -EINVAL;
}

View File

@ -430,18 +430,18 @@ static int amd_pmf_probe(struct platform_device *pdev)
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_LO, &val);
if (err) {
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
}
base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_HI, &val);
if (err) {
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
pci_dev_put(rdev);
return pcibios_err_to_errno(err);
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
}
base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
@ -497,7 +497,7 @@ static struct platform_driver amd_pmf_driver = {
.pm = pm_sleep_ptr(&amd_pmf_pm),
},
.probe = amd_pmf_probe,
.remove_new = amd_pmf_remove,
.remove = amd_pmf_remove,
};
module_platform_driver(amd_pmf_driver);

View File

@ -13,6 +13,7 @@
#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#define POLICY_BUF_MAX_SZ 0x4b000
@ -355,19 +356,20 @@ struct amd_pmf_dev {
/* Smart PC solution builder */
struct dentry *esbin;
unsigned char *policy_buf;
u32 policy_sz;
resource_size_t policy_sz;
struct tee_context *tee_ctx;
struct tee_shm *fw_shm_pool;
u32 session_id;
void *shbuf;
struct delayed_work pb_work;
struct pmf_action_table *prev_data;
u64 policy_addr;
resource_size_t policy_addr;
void __iomem *policy_base;
bool smart_pc_enabled;
u16 pmf_if_version;
struct input_dev *pmf_idev;
size_t mtable_size;
struct resource *res;
};
struct apmf_sps_prop_granular_v2 {

View File

@ -257,7 +257,7 @@ static int amd_pmf_invoke_cmd_init(struct amd_pmf_dev *dev)
return -ENODEV;
}
dev_dbg(dev->dev, "Policy Binary size: %u bytes\n", dev->policy_sz);
dev_dbg(dev->dev, "Policy Binary size: %llu bytes\n", (unsigned long long)dev->policy_sz);
memset(dev->shbuf, 0, dev->policy_sz);
ta_sm = dev->shbuf;
in = &ta_sm->pmf_input.init_table;
@ -512,9 +512,9 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
if (ret)
goto error;
dev->policy_base = devm_ioremap(dev->dev, dev->policy_addr, dev->policy_sz);
if (!dev->policy_base) {
ret = -ENOMEM;
dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
if (IS_ERR(dev->policy_base)) {
ret = PTR_ERR(dev->policy_base);
goto error;
}

View File

@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD 3D V-Cache Performance Optimizer Driver
*
* Copyright (c) 2024, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Authors: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
* Perry Yuan <perry.yuan@amd.com>
* Mario Limonciello <mario.limonciello@amd.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/sysfs.h>
#include <linux/uuid.h>
static char *x3d_mode = "frequency";
module_param(x3d_mode, charp, 0);
MODULE_PARM_DESC(x3d_mode, "Initial 3D-VCache mode; 'frequency' (default) or 'cache'");
#define DSM_REVISION_ID 0
#define DSM_SET_X3D_MODE 1
static guid_t x3d_guid = GUID_INIT(0xdff8e55f, 0xbcfd, 0x46fb, 0xba, 0x0a,
0xef, 0xd0, 0x45, 0x0f, 0x34, 0xee);
enum amd_x3d_mode_type {
MODE_INDEX_FREQ,
MODE_INDEX_CACHE,
};
static const char * const amd_x3d_mode_strings[] = {
[MODE_INDEX_FREQ] = "frequency",
[MODE_INDEX_CACHE] = "cache",
};
struct amd_x3d_dev {
struct device *dev;
acpi_handle ahandle;
/* To protect x3d mode setting */
struct mutex lock;
enum amd_x3d_mode_type curr_mode;
};
static int amd_x3d_get_mode(struct amd_x3d_dev *data)
{
guard(mutex)(&data->lock);
return data->curr_mode;
}
static int amd_x3d_mode_switch(struct amd_x3d_dev *data, int new_state)
{
union acpi_object *out, argv;
guard(mutex)(&data->lock);
argv.type = ACPI_TYPE_INTEGER;
argv.integer.value = new_state;
out = acpi_evaluate_dsm(data->ahandle, &x3d_guid, DSM_REVISION_ID,
DSM_SET_X3D_MODE, &argv);
if (!out) {
dev_err(data->dev, "failed to evaluate _DSM\n");
return -EINVAL;
}
data->curr_mode = new_state;
kfree(out);
return 0;
}
static ssize_t amd_x3d_mode_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct amd_x3d_dev *data = dev_get_drvdata(dev);
int ret;
ret = sysfs_match_string(amd_x3d_mode_strings, buf);
if (ret < 0)
return ret;
ret = amd_x3d_mode_switch(data, ret);
if (ret < 0)
return ret;
return count;
}
static ssize_t amd_x3d_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct amd_x3d_dev *data = dev_get_drvdata(dev);
int mode = amd_x3d_get_mode(data);
return sysfs_emit(buf, "%s\n", amd_x3d_mode_strings[mode]);
}
static DEVICE_ATTR_RW(amd_x3d_mode);
static struct attribute *amd_x3d_attrs[] = {
&dev_attr_amd_x3d_mode.attr,
NULL
};
ATTRIBUTE_GROUPS(amd_x3d);
static int amd_x3d_resume_handler(struct device *dev)
{
struct amd_x3d_dev *data = dev_get_drvdata(dev);
int ret = amd_x3d_get_mode(data);
return amd_x3d_mode_switch(data, ret);
}
static DEFINE_SIMPLE_DEV_PM_OPS(amd_x3d_pm, NULL, amd_x3d_resume_handler);
static const struct acpi_device_id amd_x3d_acpi_ids[] = {
{"AMDI0101"},
{ },
};
MODULE_DEVICE_TABLE(acpi, amd_x3d_acpi_ids);
static int amd_x3d_probe(struct platform_device *pdev)
{
struct amd_x3d_dev *data;
acpi_handle handle;
int ret;
handle = ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
if (!acpi_check_dsm(handle, &x3d_guid, DSM_REVISION_ID, BIT(DSM_SET_X3D_MODE)))
return -ENODEV;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->dev = &pdev->dev;
ret = devm_mutex_init(data->dev, &data->lock);
if (ret)
return ret;
data->ahandle = handle;
platform_set_drvdata(pdev, data);
ret = match_string(amd_x3d_mode_strings, ARRAY_SIZE(amd_x3d_mode_strings), x3d_mode);
if (ret < 0)
return dev_err_probe(&pdev->dev, -EINVAL, "invalid mode %s\n", x3d_mode);
return amd_x3d_mode_switch(data, ret);
}
static struct platform_driver amd_3d_vcache_driver = {
.driver = {
.name = "amd_x3d_vcache",
.dev_groups = amd_x3d_groups,
.acpi_match_table = amd_x3d_acpi_ids,
.pm = pm_sleep_ptr(&amd_x3d_pm),
},
.probe = amd_x3d_probe,
};
module_platform_driver(amd_3d_vcache_driver);
MODULE_DESCRIPTION("AMD 3D V-Cache Performance Optimizer Driver");
MODULE_LICENSE("GPL");

View File

@ -135,7 +135,7 @@ static struct platform_driver amilo_rfkill_driver = {
.name = KBUILD_MODNAME,
},
.probe = amilo_rfkill_probe,
.remove_new = amilo_rfkill_remove,
.remove = amilo_rfkill_remove,
};
static int __init amilo_rfkill_init(void)

View File

@ -1832,8 +1832,8 @@ static int asus_acpi_add(struct acpi_device *device)
if (!asus)
return -ENOMEM;
asus->handle = device->handle;
strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
device->driver_data = asus;
asus->device = device;

View File

@ -3696,10 +3696,28 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
/* Throttle thermal policy ****************************************************/
static int throttle_thermal_policy_write(struct asus_wmi *asus)
{
u8 value = asus->throttle_thermal_policy_mode;
u32 retval;
u8 value;
int err;
if (asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO) {
switch (asus->throttle_thermal_policy_mode) {
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
value = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO;
break;
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
value = ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO;
break;
case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
value = ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO;
break;
default:
return -EINVAL;
}
} else {
value = asus->throttle_thermal_policy_mode;
}
err = asus_wmi_set_devstate(asus->throttle_thermal_policy_dev,
value, &retval);
@ -3737,28 +3755,6 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus)
return throttle_thermal_policy_write(asus);
}
static int throttle_thermal_policy_switch_next(struct asus_wmi *asus)
{
u8 new_mode = asus->throttle_thermal_policy_mode + 1;
int err;
if (new_mode > PLATFORM_PROFILE_MAX)
new_mode = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
asus->throttle_thermal_policy_mode = new_mode;
err = throttle_thermal_policy_write(asus);
if (err)
return err;
/*
* Ensure that platform_profile updates userspace with the change to ensure
* that platform_profile and throttle_thermal_policy_mode are in sync.
*/
platform_profile_notify();
return 0;
}
static ssize_t throttle_thermal_policy_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@ -3804,46 +3800,6 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
static DEVICE_ATTR_RW(throttle_thermal_policy);
/* Platform profile ***********************************************************/
static int asus_wmi_platform_profile_to_vivo(struct asus_wmi *asus, int mode)
{
bool vivo;
vivo = asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
if (vivo) {
switch (mode) {
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
return ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO;
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
return ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO;
case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
return ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO;
}
}
return mode;
}
static int asus_wmi_platform_profile_mode_from_vivo(struct asus_wmi *asus, int mode)
{
bool vivo;
vivo = asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
if (vivo) {
switch (mode) {
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO:
return ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO:
return ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST;
case ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO:
return ASUS_THROTTLE_THERMAL_POLICY_SILENT;
}
}
return mode;
}
static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
enum platform_profile_option *profile)
{
@ -3853,7 +3809,7 @@ static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
asus = container_of(pprof, struct asus_wmi, platform_profile_handler);
tp = asus->throttle_thermal_policy_mode;
switch (asus_wmi_platform_profile_mode_from_vivo(asus, tp)) {
switch (tp) {
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
*profile = PLATFORM_PROFILE_BALANCED;
break;
@ -3892,7 +3848,7 @@ static int asus_wmi_platform_profile_set(struct platform_profile_handler *pprof,
return -EOPNOTSUPP;
}
asus->throttle_thermal_policy_mode = asus_wmi_platform_profile_to_vivo(asus, tp);
asus->throttle_thermal_policy_mode = tp;
return throttle_thermal_policy_write(asus);
}
@ -4323,7 +4279,7 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
if (asus->fan_boost_mode_available)
fan_boost_mode_switch_next(asus);
if (asus->throttle_thermal_policy_dev)
throttle_thermal_policy_switch_next(asus);
platform_profile_cycle();
return;
}
@ -5076,7 +5032,7 @@ int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver)
return -EBUSY;
platform_driver = &driver->platform_driver;
platform_driver->remove_new = asus_wmi_remove;
platform_driver->remove = asus_wmi_remove;
platform_driver->driver.owner = driver->owner;
platform_driver->driver.name = driver->name;
platform_driver->driver.pm = &asus_pm_ops;

View File

@ -385,7 +385,7 @@ static struct platform_driver p50_gpio_driver = {
.name = DRIVER_NAME,
},
.probe = p50_gpio_probe,
.remove_new = p50_gpio_remove,
.remove = p50_gpio_remove,
};
/* Board setup */

View File

@ -12,6 +12,7 @@
#include <linux/backlight.h>
#include <linux/input.h>
#include <linux/rfkill.h>
#include <linux/sysfs.h>
struct cmpc_accel {
int sensitivity;
@ -208,7 +209,7 @@ static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev,
inputdev = dev_get_drvdata(&acpi->dev);
accel = dev_get_drvdata(&inputdev->dev);
return sprintf(buf, "%d\n", accel->sensitivity);
return sysfs_emit(buf, "%d\n", accel->sensitivity);
}
static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev,
@ -257,7 +258,7 @@ static ssize_t cmpc_accel_g_select_show_v4(struct device *dev,
inputdev = dev_get_drvdata(&acpi->dev);
accel = dev_get_drvdata(&inputdev->dev);
return sprintf(buf, "%d\n", accel->g_select);
return sysfs_emit(buf, "%d\n", accel->g_select);
}
static ssize_t cmpc_accel_g_select_store_v4(struct device *dev,
@ -550,7 +551,7 @@ static ssize_t cmpc_accel_sensitivity_show(struct device *dev,
inputdev = dev_get_drvdata(&acpi->dev);
accel = dev_get_drvdata(&inputdev->dev);
return sprintf(buf, "%d\n", accel->sensitivity);
return sysfs_emit(buf, "%d\n", accel->sensitivity);
}
static ssize_t cmpc_accel_sensitivity_store(struct device *dev,

View File

@ -68,6 +68,7 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/power_supply.h>
#include <linux/sysfs.h>
#include <linux/fb.h>
#include <acpi/video.h>
@ -368,7 +369,7 @@ static const struct rfkill_ops compal_rfkill_ops = {
static ssize_t NAME##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
return sysfs_emit(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
} \
static ssize_t NAME##_store(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t count) \
@ -393,7 +394,7 @@ static ssize_t pwm_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct compal_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", data->pwm_enable);
return sysfs_emit(buf, "%d\n", data->pwm_enable);
}
static ssize_t pwm_enable_store(struct device *dev,
@ -432,7 +433,7 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct compal_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%hhu\n", data->curr_pwm);
return sysfs_emit(buf, "%hhu\n", data->curr_pwm);
}
static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
@ -460,7 +461,7 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", get_fan_rpm());
return sysfs_emit(buf, "%d\n", get_fan_rpm());
}
@ -469,12 +470,12 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
static ssize_t temp_##POSTFIX(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
return sysfs_emit(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
} \
static ssize_t label_##POSTFIX(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%s\n", LABEL); \
return sysfs_emit(buf, "%s\n", LABEL); \
}
/* Labels as in service guide */
@ -1024,7 +1025,7 @@ static struct platform_driver compal_driver = {
.name = DRIVER_NAME,
},
.probe = compal_probe,
.remove_new = compal_remove,
.remove = compal_remove,
};
static int __init compal_init(void)

View File

@ -21,6 +21,7 @@ config ALIENWARE_WMI
depends on LEDS_CLASS
depends on NEW_LEDS
depends on ACPI_WMI
select ACPI_PLATFORM_PROFILE
help
This is a driver for controlling Alienware BIOS driven
features. It exposes an interface for controlling the AlienFX

View File

@ -8,8 +8,11 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/dmi.h>
#include <linux/leds.h>
@ -25,6 +28,13 @@
#define WMAX_METHOD_AMPLIFIER_CABLE 0x6
#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
#define WMAX_METHOD_THERMAL_INFORMATION 0x14
#define WMAX_METHOD_THERMAL_CONTROL 0x15
#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
#define WMAX_THERMAL_MODE_GMODE 0xAB
#define WMAX_FAILURE_CODE 0xFFFFFFFF
MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
MODULE_DESCRIPTION("Alienware special feature control");
@ -32,6 +42,14 @@ MODULE_LICENSE("GPL");
MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
static bool force_platform_profile;
module_param_unsafe(force_platform_profile, bool, 0);
MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
static bool force_gmode;
module_param_unsafe(force_gmode, bool, 0);
MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
enum INTERFACE_FLAGS {
LEGACY,
WMAX,
@ -49,11 +67,60 @@ enum WMAX_CONTROL_STATES {
WMAX_SUSPEND = 3,
};
enum WMAX_THERMAL_INFORMATION_OPERATIONS {
WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
WMAX_OPERATION_LIST_IDS = 0x03,
WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
};
enum WMAX_THERMAL_CONTROL_OPERATIONS {
WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
};
enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
};
enum WMAX_THERMAL_TABLES {
WMAX_THERMAL_TABLE_BASIC = 0x90,
WMAX_THERMAL_TABLE_USTT = 0xA0,
};
enum wmax_thermal_mode {
THERMAL_MODE_USTT_BALANCED,
THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
THERMAL_MODE_USTT_COOL,
THERMAL_MODE_USTT_QUIET,
THERMAL_MODE_USTT_PERFORMANCE,
THERMAL_MODE_USTT_LOW_POWER,
THERMAL_MODE_BASIC_QUIET,
THERMAL_MODE_BASIC_BALANCED,
THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
THERMAL_MODE_BASIC_PERFORMANCE,
THERMAL_MODE_LAST,
};
static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
[THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
[THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
[THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
[THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
[THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
[THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
[THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
[THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
[THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
[THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
};
struct quirk_entry {
u8 num_zones;
u8 hdmi_mux;
u8 amplifier;
u8 deepslp;
bool thermal;
bool gmode;
};
static struct quirk_entry *quirks;
@ -64,6 +131,8 @@ static struct quirk_entry quirk_inspiron5675 = {
.hdmi_mux = 0,
.amplifier = 0,
.deepslp = 0,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_unknown = {
@ -71,6 +140,8 @@ static struct quirk_entry quirk_unknown = {
.hdmi_mux = 0,
.amplifier = 0,
.deepslp = 0,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_x51_r1_r2 = {
@ -78,6 +149,8 @@ static struct quirk_entry quirk_x51_r1_r2 = {
.hdmi_mux = 0,
.amplifier = 0,
.deepslp = 0,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_x51_r3 = {
@ -85,6 +158,8 @@ static struct quirk_entry quirk_x51_r3 = {
.hdmi_mux = 0,
.amplifier = 1,
.deepslp = 0,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_asm100 = {
@ -92,6 +167,8 @@ static struct quirk_entry quirk_asm100 = {
.hdmi_mux = 1,
.amplifier = 0,
.deepslp = 0,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_asm200 = {
@ -99,6 +176,8 @@ static struct quirk_entry quirk_asm200 = {
.hdmi_mux = 1,
.amplifier = 0,
.deepslp = 1,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_asm201 = {
@ -106,6 +185,26 @@ static struct quirk_entry quirk_asm201 = {
.hdmi_mux = 1,
.amplifier = 1,
.deepslp = 1,
.thermal = false,
.gmode = false,
};
static struct quirk_entry quirk_g_series = {
.num_zones = 2,
.hdmi_mux = 0,
.amplifier = 0,
.deepslp = 0,
.thermal = true,
.gmode = true,
};
static struct quirk_entry quirk_x_series = {
.num_zones = 2,
.hdmi_mux = 0,
.amplifier = 0,
.deepslp = 0,
.thermal = true,
.gmode = false,
};
static int __init dmi_matched(const struct dmi_system_id *dmi)
@ -115,33 +214,6 @@ static int __init dmi_matched(const struct dmi_system_id *dmi)
}
static const struct dmi_system_id alienware_quirks[] __initconst = {
{
.callback = dmi_matched,
.ident = "Alienware X51 R3",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
},
.driver_data = &quirk_x51_r3,
},
{
.callback = dmi_matched,
.ident = "Alienware X51 R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
},
.driver_data = &quirk_x51_r1_r2,
},
{
.callback = dmi_matched,
.ident = "Alienware X51 R1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
},
.driver_data = &quirk_x51_r1_r2,
},
{
.callback = dmi_matched,
.ident = "Alienware ASM100",
@ -169,6 +241,123 @@ static const struct dmi_system_id alienware_quirks[] __initconst = {
},
.driver_data = &quirk_asm201,
},
{
.callback = dmi_matched,
.ident = "Alienware m17 R5",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
},
.driver_data = &quirk_x_series,
},
{
.callback = dmi_matched,
.ident = "Alienware m18 R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
},
.driver_data = &quirk_x_series,
},
{
.callback = dmi_matched,
.ident = "Alienware x15 R1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
},
.driver_data = &quirk_x_series,
},
{
.callback = dmi_matched,
.ident = "Alienware x17 R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
},
.driver_data = &quirk_x_series,
},
{
.callback = dmi_matched,
.ident = "Alienware X51 R1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
},
.driver_data = &quirk_x51_r1_r2,
},
{
.callback = dmi_matched,
.ident = "Alienware X51 R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
},
.driver_data = &quirk_x51_r1_r2,
},
{
.callback = dmi_matched,
.ident = "Alienware X51 R3",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
},
.driver_data = &quirk_x51_r3,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G15 5510",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G15 5511",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G15 5515",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G3 3500",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G3 3590",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. G5 5500",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
},
.driver_data = &quirk_g_series,
},
{
.callback = dmi_matched,
.ident = "Dell Inc. Inspiron 5675",
@ -214,10 +403,19 @@ struct wmax_led_args {
u8 state;
} __packed;
struct wmax_u32_args {
u8 operation;
u8 arg1;
u8 arg2;
u8 arg3;
};
static struct platform_device *platform_device;
static struct device_attribute *zone_dev_attrs;
static struct attribute **zone_attrs;
static struct platform_zone *zone_data;
static struct platform_profile_handler pp_handler;
static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
static struct platform_driver platform_driver = {
.driver = {
@ -292,7 +490,7 @@ static int alienware_update_led(struct platform_zone *zone)
guid = WMAX_CONTROL_GUID;
method_id = WMAX_METHOD_ZONE_CONTROL;
input.length = (acpi_size) sizeof(wmax_basic_args);
input.length = sizeof(wmax_basic_args);
input.pointer = &wmax_basic_args;
} else {
legacy_args.colors = zone->colors;
@ -306,7 +504,7 @@ static int alienware_update_led(struct platform_zone *zone)
guid = LEGACY_CONTROL_GUID;
method_id = zone->location + 1;
input.length = (acpi_size) sizeof(legacy_args);
input.length = sizeof(legacy_args);
input.pointer = &legacy_args;
}
pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
@ -358,7 +556,7 @@ static int wmax_brightness(int brightness)
.led_mask = 0xFF,
.percentage = brightness,
};
input.length = (acpi_size) sizeof(args);
input.length = sizeof(args);
input.pointer = &args;
status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
WMAX_METHOD_BRIGHTNESS, &input, NULL);
@ -500,15 +698,15 @@ static void alienware_zone_exit(struct platform_device *dev)
kfree(zone_attrs);
}
static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,
u32 command, int *out_data)
static acpi_status alienware_wmax_command(void *in_args, size_t in_size,
u32 command, u32 *out_data)
{
acpi_status status;
union acpi_object *obj;
struct acpi_buffer input;
struct acpi_buffer output;
input.length = (acpi_size) sizeof(*in_args);
input.length = in_size;
input.pointer = in_args;
if (out_data) {
output.length = ACPI_ALLOCATE_BUFFER;
@ -541,8 +739,8 @@ static ssize_t show_hdmi_cable(struct device *dev,
.arg = 0,
};
status =
alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
(u32 *) &out_data);
alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_HDMI_CABLE, &out_data);
if (ACPI_SUCCESS(status)) {
if (out_data == 0)
return sysfs_emit(buf, "[unconnected] connected unknown\n");
@ -562,8 +760,8 @@ static ssize_t show_hdmi_source(struct device *dev,
.arg = 0,
};
status =
alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
(u32 *) &out_data);
alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_HDMI_STATUS, &out_data);
if (ACPI_SUCCESS(status)) {
if (out_data == 1)
@ -589,7 +787,8 @@ static ssize_t toggle_hdmi_source(struct device *dev,
args.arg = 3;
pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
status = alienware_wmax_command(&args, sizeof(args),
WMAX_METHOD_HDMI_SOURCE, NULL);
if (ACPI_FAILURE(status))
pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
@ -642,8 +841,8 @@ static ssize_t show_amplifier_status(struct device *dev,
.arg = 0,
};
status =
alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
(u32 *) &out_data);
alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_AMPLIFIER_CABLE, &out_data);
if (ACPI_SUCCESS(status)) {
if (out_data == 0)
return sysfs_emit(buf, "[unconnected] connected unknown\n");
@ -694,8 +893,8 @@ static ssize_t show_deepsleep_status(struct device *dev,
struct wmax_basic_args in_args = {
.arg = 0,
};
status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
(u32 *) &out_data);
status = alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data);
if (ACPI_SUCCESS(status)) {
if (out_data == 0)
return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
@ -723,8 +922,8 @@ static ssize_t toggle_deepsleep(struct device *dev,
args.arg = 2;
pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
NULL);
status = alienware_wmax_command(&args, sizeof(args),
WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL);
if (ACPI_FAILURE(status))
pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
@ -760,6 +959,213 @@ static int create_deepsleep(struct platform_device *dev)
return ret;
}
/*
* Thermal Profile control
* - Provides thermal profile control through the Platform Profile API
*/
#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
#define WMAX_SENSOR_ID_MASK BIT(8)
static bool is_wmax_thermal_code(u32 code)
{
if (code & WMAX_SENSOR_ID_MASK)
return false;
if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
return false;
if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
(code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
return true;
if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
(code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
return true;
return false;
}
static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data)
{
acpi_status status;
struct wmax_u32_args in_args = {
.operation = operation,
.arg1 = arg,
.arg2 = 0,
.arg3 = 0,
};
status = alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_THERMAL_INFORMATION,
out_data);
if (ACPI_FAILURE(status))
return -EIO;
if (*out_data == WMAX_FAILURE_CODE)
return -EBADRQC;
return 0;
}
static int wmax_thermal_control(u8 profile)
{
acpi_status status;
struct wmax_u32_args in_args = {
.operation = WMAX_OPERATION_ACTIVATE_PROFILE,
.arg1 = profile,
.arg2 = 0,
.arg3 = 0,
};
u32 out_data;
status = alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_THERMAL_CONTROL,
&out_data);
if (ACPI_FAILURE(status))
return -EIO;
if (out_data == WMAX_FAILURE_CODE)
return -EBADRQC;
return 0;
}
static int wmax_game_shift_status(u8 operation, u32 *out_data)
{
acpi_status status;
struct wmax_u32_args in_args = {
.operation = operation,
.arg1 = 0,
.arg2 = 0,
.arg3 = 0,
};
status = alienware_wmax_command(&in_args, sizeof(in_args),
WMAX_METHOD_GAME_SHIFT_STATUS,
out_data);
if (ACPI_FAILURE(status))
return -EIO;
if (*out_data == WMAX_FAILURE_CODE)
return -EOPNOTSUPP;
return 0;
}
static int thermal_profile_get(struct platform_profile_handler *pprof,
enum platform_profile_option *profile)
{
u32 out_data;
int ret;
ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE,
0, &out_data);
if (ret < 0)
return ret;
if (out_data == WMAX_THERMAL_MODE_GMODE) {
*profile = PLATFORM_PROFILE_PERFORMANCE;
return 0;
}
if (!is_wmax_thermal_code(out_data))
return -ENODATA;
out_data &= WMAX_THERMAL_MODE_MASK;
*profile = wmax_mode_to_platform_profile[out_data];
return 0;
}
static int thermal_profile_set(struct platform_profile_handler *pprof,
enum platform_profile_option profile)
{
if (quirks->gmode) {
u32 gmode_status;
int ret;
ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
&gmode_status);
if (ret < 0)
return ret;
if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
(profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT,
&gmode_status);
if (ret < 0)
return ret;
}
}
return wmax_thermal_control(supported_thermal_profiles[profile]);
}
static int create_thermal_profile(void)
{
u32 out_data;
u8 sys_desc[4];
u32 first_mode;
enum wmax_thermal_mode mode;
enum platform_profile_option profile;
int ret;
ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION,
0, (u32 *) &sys_desc);
if (ret < 0)
return ret;
first_mode = sys_desc[0] + sys_desc[1];
for (u32 i = 0; i < sys_desc[3]; i++) {
ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS,
i + first_mode, &out_data);
if (ret == -EIO)
return ret;
if (ret == -EBADRQC)
break;
if (!is_wmax_thermal_code(out_data))
continue;
mode = out_data & WMAX_THERMAL_MODE_MASK;
profile = wmax_mode_to_platform_profile[mode];
supported_thermal_profiles[profile] = out_data;
set_bit(profile, pp_handler.choices);
}
if (bitmap_empty(pp_handler.choices, PLATFORM_PROFILE_LAST))
return -ENODEV;
if (quirks->gmode) {
supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
WMAX_THERMAL_MODE_GMODE;
set_bit(PLATFORM_PROFILE_PERFORMANCE, pp_handler.choices);
}
pp_handler.profile_get = thermal_profile_get;
pp_handler.profile_set = thermal_profile_set;
return platform_profile_register(&pp_handler);
}
static void remove_thermal_profile(void)
{
if (quirks->thermal)
platform_profile_remove();
}
static int __init alienware_wmi_init(void)
{
int ret;
@ -777,6 +1183,16 @@ static int __init alienware_wmi_init(void)
if (quirks == NULL)
quirks = &quirk_unknown;
if (force_platform_profile)
quirks->thermal = true;
if (force_gmode) {
if (quirks->thermal)
quirks->gmode = true;
else
pr_warn("force_gmode requires platform profile support\n");
}
ret = platform_driver_register(&platform_driver);
if (ret)
goto fail_platform_driver;
@ -807,6 +1223,12 @@ static int __init alienware_wmi_init(void)
goto fail_prep_deepsleep;
}
if (quirks->thermal) {
ret = create_thermal_profile();
if (ret)
goto fail_prep_thermal_profile;
}
ret = alienware_zone_init(platform_device);
if (ret)
goto fail_prep_zones;
@ -815,6 +1237,8 @@ static int __init alienware_wmi_init(void)
fail_prep_zones:
alienware_zone_exit(platform_device);
remove_thermal_profile();
fail_prep_thermal_profile:
fail_prep_deepsleep:
fail_prep_amplifier:
fail_prep_hdmi:
@ -834,6 +1258,7 @@ static void __exit alienware_wmi_exit(void)
if (platform_device) {
alienware_zone_exit(platform_device);
remove_hdmi(platform_device);
remove_thermal_profile();
platform_device_unregister(platform_device);
platform_driver_unregister(&platform_driver);
}

View File

@ -29,6 +29,7 @@
#include <linux/smp.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/mutex.h>
@ -132,14 +133,14 @@ static ssize_t smi_data_buf_phys_addr_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%x\n", (u32)smi_buf.dma);
return sysfs_emit(buf, "%x\n", (u32)smi_buf.dma);
}
static ssize_t smi_data_buf_size_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", smi_buf.size);
return sysfs_emit(buf, "%lu\n", smi_buf.size);
}
static ssize_t smi_data_buf_size_store(struct device *dev,
@ -200,7 +201,7 @@ static ssize_t host_control_action_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_action);
return sysfs_emit(buf, "%u\n", host_control_action);
}
static ssize_t host_control_action_store(struct device *dev,
@ -224,7 +225,7 @@ static ssize_t host_control_smi_type_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_smi_type);
return sysfs_emit(buf, "%u\n", host_control_smi_type);
}
static ssize_t host_control_smi_type_store(struct device *dev,
@ -239,7 +240,7 @@ static ssize_t host_control_on_shutdown_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%u\n", host_control_on_shutdown);
return sysfs_emit(buf, "%u\n", host_control_on_shutdown);
}
static ssize_t host_control_on_shutdown_store(struct device *dev,
@ -709,7 +710,7 @@ static struct platform_driver dcdbas_driver = {
.name = DRIVER_NAME,
},
.probe = dcdbas_probe,
.remove_new = dcdbas_remove,
.remove = dcdbas_remove,
};
static const struct platform_device_info dcdbas_dev_info __initconst = {

View File

@ -179,7 +179,7 @@ MODULE_DEVICE_TABLE(acpi, smo8800_ids);
static struct platform_driver smo8800_driver = {
.probe = smo8800_probe,
.remove_new = smo8800_remove,
.remove = smo8800_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = smo8800_ids,

View File

@ -393,7 +393,7 @@ static void dell_uart_bl_pdev_remove(struct platform_device *pdev)
static struct platform_driver dell_uart_bl_pdev_driver = {
.probe = dell_uart_bl_pdev_probe,
.remove_new = dell_uart_bl_pdev_remove,
.remove = dell_uart_bl_pdev_remove,
.driver = {
.name = "dell-uart-backlight",
},

View File

@ -25,6 +25,7 @@
#include <linux/rfkill.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/sysfs.h>
#include <linux/leds.h>
#include <linux/dmi.h>
#include <acpi/video.h>
@ -285,7 +286,7 @@ static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
if (value < 0)
return -EIO;
return sprintf(buf, "%d\n", value);
return sysfs_emit(buf, "%d\n", value);
}
#define EEEPC_ACPI_SHOW_FUNC(_name, _cm) \
@ -361,7 +362,7 @@ static ssize_t cpufv_show(struct device *dev,
if (get_cpufv(eeepc, &c))
return -ENODEV;
return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
return sysfs_emit(buf, "%#x\n", (c.num << 8) | c.cur);
}
static ssize_t cpufv_store(struct device *dev,
@ -393,7 +394,7 @@ static ssize_t cpufv_disabled_show(struct device *dev,
{
struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", eeepc->cpufv_disabled);
return sysfs_emit(buf, "%d\n", eeepc->cpufv_disabled);
}
static ssize_t cpufv_disabled_store(struct device *dev,
@ -1025,7 +1026,7 @@ static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
{
return sprintf(buf, "%d\n", get());
return sysfs_emit(buf, "%d\n", get());
}
#define EEEPC_SENSOR_SHOW_FUNC(_name, _get) \

View File

@ -531,14 +531,9 @@ void hp_exit_password_attributes(void)
struct kobject *attr_name_kobj =
bioscfg_drv.password_data[instance_id].attr_name_kobj;
if (attr_name_kobj) {
if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
if (attr_name_kobj)
sysfs_remove_group(attr_name_kobj,
&password_attr_group);
else
sysfs_remove_group(attr_name_kobj,
&password_attr_group);
}
}
bioscfg_drv.password_instances_count = 0;
kfree(bioscfg_drv.password_data);

View File

@ -1748,7 +1748,7 @@ static struct platform_driver hp_wmi_driver __refdata = {
.pm = &hp_wmi_pm_ops,
.dev_groups = hp_wmi_groups,
},
.remove_new = __exit_p(hp_wmi_bios_remove),
.remove = __exit_p(hp_wmi_bios_remove),
};
static umode_t hp_wmi_hwmon_is_visible(const void *data,

View File

@ -372,7 +372,7 @@ static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume);
/* For the HP MDPS aka 3D Driveguard */
static struct platform_driver lis3lv02d_driver = {
.probe = lis3lv02d_probe,
.remove_new = lis3lv02d_remove,
.remove = lis3lv02d_remove,
.driver = {
.name = "hp_accel",
.pm = &hp_accel_pm,

View File

@ -221,7 +221,7 @@ static struct platform_driver tc1100_driver = {
.pm = &tc1100_pm_ops,
#endif
},
.remove_new = tc1100_remove,
.remove = tc1100_remove,
};
static int __init tc1100_init(void)

View File

@ -842,7 +842,7 @@ static struct platform_driver huawei_wmi_driver = {
.name = "huawei-wmi",
},
.probe = huawei_wmi_probe,
.remove_new = huawei_wmi_remove,
.remove = huawei_wmi_remove,
};
static __init int huawei_wmi_init(void)

View File

@ -2309,7 +2309,7 @@ MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
static struct platform_driver ideapad_acpi_driver = {
.probe = ideapad_acpi_add,
.remove_new = ideapad_acpi_remove,
.remove = ideapad_acpi_remove,
.driver = {
.name = "ideapad_acpi",
.pm = &ideapad_pm,

View File

@ -17,50 +17,40 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/
# Intel input drivers
intel-hid-y := hid.o
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
intel-vbtn-y := vbtn.o
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
intel-target-$(CONFIG_INTEL_HID_EVENT) += hid.o
intel-target-$(CONFIG_INTEL_VBTN) += vbtn.o
# Intel miscellaneous drivers
obj-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o
intel_int0002_vgpio-y := int0002_vgpio.o
obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
intel_oaktrail-y := oaktrail.o
obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
intel_sdsi-y := sdsi.o
obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o
intel_vsec-y := vsec.o
obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o
intel-target-$(CONFIG_INTEL_INT0002_VGPIO) += int0002_vgpio.o
intel-target-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o
intel-target-$(CONFIG_INTEL_OAKTRAIL) += oaktrail.o
intel-target-$(CONFIG_INTEL_SDSI) += sdsi.o
intel-target-$(CONFIG_INTEL_VSEC) += vsec.o
# Intel PMIC / PMC / P-Unit drivers
intel_bxtwc_tmu-y := bxtwc_tmu.o
obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
intel_crystal_cove_charger-y := crystal_cove_charger.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o
intel_bytcrc_pwrsrc-y := bytcrc_pwrsrc.o
obj-$(CONFIG_INTEL_BYTCRC_PWRSRC) += intel_bytcrc_pwrsrc.o
intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
intel_chtwc_int33fe-y := chtwc_int33fe.o
obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o
intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
intel_punit_ipc-y := punit_ipc.o
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
intel-target-$(CONFIG_INTEL_BYTCRC_PWRSRC) += bytcrc_pwrsrc.o
intel-target-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += bxtwc_tmu.o
intel-target-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += chtdc_ti_pwrbtn.o
intel-target-$(CONFIG_INTEL_CHTWC_INT33FE) += chtwc_int33fe.o
intel-target-$(CONFIG_X86_ANDROID_TABLETS) += crystal_cove_charger.o
intel-target-$(CONFIG_INTEL_MRFLD_PWRBTN) += mrfld_pwrbtn.o
intel-target-$(CONFIG_INTEL_PUNIT_IPC) += punit_ipc.o
# TPMI drivers
intel_vsec_tpmi-y := tpmi.o
obj-$(CONFIG_INTEL_TPMI) += intel_vsec_tpmi.o
obj-$(CONFIG_INTEL_PLR_TPMI) += intel_plr_tpmi.o
intel_tpmi_power_domains-y := tpmi_power_domains.o
obj-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += intel_tpmi_power_domains.o
intel-target-$(CONFIG_INTEL_PLR_TPMI) += plr_tpmi.o
intel-target-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += tpmi_power_domains.o
intel-target-$(CONFIG_INTEL_TPMI) += vsec_tpmi.o
# Intel Uncore drivers
intel-rst-y := rst.o
obj-$(CONFIG_INTEL_RST) += intel-rst.o
intel-smartconnect-y := smartconnect.o
obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
intel_turbo_max_3-y := turbo_max_3.o
obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
intel-target-$(CONFIG_INTEL_RST) += rst.o
intel-target-$(CONFIG_INTEL_SMARTCONNECT) += smartconnect.o
intel-target-$(CONFIG_INTEL_TURBO_MAX_3) += turbo_max_3.o
# Add 'intel' prefix to each module listed in intel-target-*
define INTEL_OBJ_TARGET
intel-$(1)-y := $(1).o
obj-$(2) += intel-$(1).o
endef
$(foreach target, $(basename $(intel-target-y)), $(eval $(call INTEL_OBJ_TARGET,$(target),y)))
$(foreach target, $(basename $(intel-target-m)), $(eval $(call INTEL_OBJ_TARGET,$(target),m)))

View File

@ -131,7 +131,7 @@ MODULE_DEVICE_TABLE(platform, bxt_wcove_tmu_id_table);
static struct platform_driver bxt_wcove_tmu_driver = {
.probe = bxt_wcove_tmu_probe,
.remove_new = bxt_wcove_tmu_remove,
.remove = bxt_wcove_tmu_remove,
.driver = {
.name = "bxt_wcove_tmu",
.pm = &bxtwc_tmu_pm_ops,

View File

@ -167,7 +167,7 @@ static void crc_pwrsrc_remove(struct platform_device *pdev)
static struct platform_driver crc_pwrsrc_driver = {
.probe = crc_pwrsrc_probe,
.remove_new = crc_pwrsrc_remove,
.remove = crc_pwrsrc_remove,
.driver = {
.name = "crystal_cove_pwrsrc",
},

View File

@ -84,7 +84,7 @@ static struct platform_driver chtdc_ti_pwrbtn_driver = {
.name = KBUILD_MODNAME,
},
.probe = chtdc_ti_pwrbtn_probe,
.remove_new = chtdc_ti_pwrbtn_remove,
.remove = chtdc_ti_pwrbtn_remove,
.id_table = chtdc_ti_pwrbtn_id_table,
};
module_platform_driver(chtdc_ti_pwrbtn_driver);

View File

@ -427,7 +427,7 @@ static struct platform_driver cht_int33fe_typec_driver = {
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
},
.probe = cht_int33fe_typec_probe,
.remove_new = cht_int33fe_typec_remove,
.remove = cht_int33fe_typec_remove,
};
module_platform_driver(cht_int33fe_typec_driver);

View File

@ -118,6 +118,13 @@ static const struct dmi_system_id button_array_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),
},
},
{
.ident = "Lenovo ThinkPad X1 Tablet Gen 1",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X12 Detachable Gen 1"),
},
},
{
.ident = "Lenovo ThinkPad X1 Tablet Gen 2",
.matches = {
@ -747,7 +754,7 @@ static struct platform_driver intel_hid_pl_driver = {
.pm = &intel_hid_pl_pm_ops,
},
.probe = intel_hid_probe,
.remove_new = intel_hid_remove,
.remove = intel_hid_remove,
};
/*

View File

@ -272,7 +272,7 @@ static struct platform_driver int0002_driver = {
.pm = &int0002_pm_ops,
},
.probe = int0002_probe,
.remove_new = int0002_remove,
.remove = int0002_remove,
};
module_platform_driver(int0002_driver);

View File

@ -308,7 +308,7 @@ static void sar_remove(struct platform_device *device)
static struct platform_driver sar_driver = {
.probe = sar_probe,
.remove_new = sar_remove,
.remove = sar_remove,
.driver = {
.name = DRVNAME,
.acpi_match_table = ACPI_PTR(sar_device_ids)

View File

@ -392,7 +392,7 @@ static struct platform_driver int3472_discrete = {
.acpi_match_table = int3472_device_id,
},
.probe = skl_int3472_discrete_probe,
.remove_new = skl_int3472_discrete_remove,
.remove = skl_int3472_discrete_remove,
};
module_platform_driver(int3472_discrete);

View File

@ -97,7 +97,7 @@ static struct platform_driver mrfld_pwrbtn_driver = {
.name = "mrfld_bcove_pwrbtn",
},
.probe = mrfld_pwrbtn_probe,
.remove_new = mrfld_pwrbtn_remove,
.remove = mrfld_pwrbtn_remove,
.id_table = mrfld_pwrbtn_id_table,
};
module_platform_driver(mrfld_pwrbtn_driver);

View File

@ -687,9 +687,8 @@ static void arl_d3_fixup(void)
static int arl_resume(struct pmc_dev *pmcdev)
{
arl_d3_fixup();
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
return pmc_core_resume_common(pmcdev);
return cnl_resume(pmcdev);
}
int arl_core_init(struct pmc_dev *pmcdev)

View File

@ -8,6 +8,8 @@
*
*/
#include <linux/smp.h>
#include <linux/suspend.h>
#include "core.h"
/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */
@ -204,8 +206,57 @@ const struct pmc_reg_map cnp_reg_map = {
.etr3_offset = ETR3_OFFSET,
};
/*
* Disable C1 auto-demotion
*
* Aggressive C1 auto-demotion may lead to failure to enter the deepest C-state
* during suspend-to-idle, causing high power consumption. To prevent this, we
* disable C1 auto-demotion during suspend and re-enable on resume.
*
* Note that, although MSR_PKG_CST_CONFIG_CONTROL has 'package' in its name, it
* is actually a per-core MSR on client platforms, affecting only a single CPU.
* Therefore, it must be configured on all online CPUs. The online cpu mask is
* unchanged during the phase of suspend/resume as user space is frozen.
*/
static DEFINE_PER_CPU(u64, pkg_cst_config);
static void disable_c1_auto_demote(void *unused)
{
int cpunum = smp_processor_id();
u64 val;
rdmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
per_cpu(pkg_cst_config, cpunum) = val;
val &= ~NHM_C1_AUTO_DEMOTE;
wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum, val);
}
static void restore_c1_auto_demote(void *unused)
{
int cpunum = smp_processor_id();
wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, per_cpu(pkg_cst_config, cpunum));
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum,
per_cpu(pkg_cst_config, cpunum));
}
static void s2idle_cpu_quirk(smp_call_func_t func)
{
if (pm_suspend_via_firmware())
return;
on_each_cpu(func, NULL, true);
}
void cnl_suspend(struct pmc_dev *pmcdev)
{
s2idle_cpu_quirk(disable_c1_auto_demote);
/*
* Due to a hardware limitation, the GBE LTR blocks PC10
* when a cable is attached. To unblock PC10 during suspend,
@ -216,6 +267,8 @@ void cnl_suspend(struct pmc_dev *pmcdev)
int cnl_resume(struct pmc_dev *pmcdev)
{
s2idle_cpu_quirk(restore_c1_auto_demote);
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
return pmc_core_resume_common(pmcdev);

View File

@ -1676,7 +1676,7 @@ static struct platform_driver pmc_core_driver = {
.dev_groups = pmc_dev_groups,
},
.probe = pmc_core_probe,
.remove_new = pmc_core_remove,
.remove = pmc_core_remove,
};
module_platform_driver(pmc_core_driver);

View File

@ -546,9 +546,8 @@ static void lnl_d3_fixup(void)
static int lnl_resume(struct pmc_dev *pmcdev)
{
lnl_d3_fixup();
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
return pmc_core_resume_common(pmcdev);
return cnl_resume(pmcdev);
}
int lnl_core_init(struct pmc_dev *pmcdev)

View File

@ -986,9 +986,8 @@ static void mtl_d3_fixup(void)
static int mtl_resume(struct pmc_dev *pmcdev)
{
mtl_d3_fixup();
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
return pmc_core_resume_common(pmcdev);
return cnl_resume(pmcdev);
}
int mtl_core_init(struct pmc_dev *pmcdev)

View File

@ -59,10 +59,12 @@ pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)
}
int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
void __iomem *addr, u32 count)
void __iomem *addr, loff_t off, u32 count)
{
if (cb && cb->read_telem)
return cb->read_telem(pdev, guid, buf, count);
return cb->read_telem(pdev, guid, buf, off, count);
addr += off;
if (guid == GUID_SPR_PUNIT)
/* PUNIT on SPR only supports aligned 64-bit read */
@ -96,7 +98,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
count = entry->size - off;
count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf,
entry->base + off, count);
entry->base, off, count);
return count;
}
@ -207,7 +209,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
/*
* Some hardware use a different calculation for the base address
* when access_type == ACCESS_LOCAL. On the these systems
* ACCCESS_LOCAL refers to an address in the same BAR as the
* ACCESS_LOCAL refers to an address in the same BAR as the
* header but at a fixed offset. But as the header address was
* supplied to the driver, we don't know which BAR it was in.
* So search for the bar whose range includes the header address.

View File

@ -62,7 +62,7 @@ struct intel_pmt_namespace {
};
int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
void __iomem *addr, u32 count);
void __iomem *addr, loff_t off, u32 count);
bool intel_pmt_is_early_client_hw(struct device *dev);
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns,

View File

@ -219,7 +219,7 @@ int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count)
if (offset + NUM_BYTES_QWORD(count) > size)
return -EINVAL;
pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base + offset,
pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base, offset,
NUM_BYTES_QWORD(count));
return ep->present ? 0 : -EPIPE;

View File

@ -1163,7 +1163,7 @@ static void telemetry_pltdrv_remove(struct platform_device *pdev)
static struct platform_driver telemetry_soc_driver = {
.probe = telemetry_pltdrv_probe,
.remove_new = telemetry_pltdrv_remove,
.remove = telemetry_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
},

View File

@ -387,7 +387,7 @@ static struct platform_driver intel_vbtn_pl_driver = {
.pm = &intel_vbtn_pm_ops,
},
.probe = intel_vbtn_probe,
.remove_new = intel_vbtn_remove,
.remove = intel_vbtn_remove,
};
static acpi_status __init

View File

@ -79,17 +79,13 @@ static void intel_vsec_remove_aux(void *data)
auxiliary_device_uninit(data);
}
static DEFINE_MUTEX(vsec_ida_lock);
static void intel_vsec_dev_release(struct device *dev)
{
struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev);
xa_erase(&auxdev_array, intel_vsec_dev->id);
mutex_lock(&vsec_ida_lock);
ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
mutex_unlock(&vsec_ida_lock);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
@ -113,9 +109,7 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
return ret;
}
mutex_lock(&vsec_ida_lock);
id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
mutex_unlock(&vsec_ida_lock);
if (id < 0) {
xa_erase(&auxdev_array, intel_vsec_dev->id);
kfree(intel_vsec_dev->resource);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* intel-tpmi : Driver to enumerate TPMI features and create devices
* Driver to enumerate TPMI features and create devices
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.

View File

@ -13,6 +13,7 @@
* along with other APIs.
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
@ -56,11 +57,11 @@
struct intel_scu_ipc_dev {
struct device dev;
struct resource mem;
struct module *owner;
int irq;
void __iomem *ipc_base;
struct completion cmd_complete;
struct intel_scu_ipc_data data;
};
#define IPC_STATUS 0x04
@ -99,23 +100,21 @@ static struct class intel_scu_ipc_class = {
*/
struct intel_scu_ipc_dev *intel_scu_ipc_dev_get(void)
{
struct intel_scu_ipc_dev *scu = NULL;
guard(mutex)(&ipclock);
mutex_lock(&ipclock);
if (ipcdev) {
get_device(&ipcdev->dev);
/*
* Prevent the IPC provider from being unloaded while it
* is being used.
*/
if (!try_module_get(ipcdev->owner))
if (try_module_get(ipcdev->owner))
return ipcdev;
put_device(&ipcdev->dev);
else
scu = ipcdev;
}
mutex_unlock(&ipclock);
return scu;
return NULL;
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_dev_get);
@ -217,12 +216,6 @@ static inline u8 ipc_read_status(struct intel_scu_ipc_dev *scu)
return __raw_readl(scu->ipc_base + IPC_STATUS);
}
/* Read ipc byte data */
static inline u8 ipc_data_readb(struct intel_scu_ipc_dev *scu, u32 offset)
{
return readb(scu->ipc_base + IPC_READ_BUFFER + offset);
}
/* Read ipc u32 data */
static inline u32 ipc_data_readl(struct intel_scu_ipc_dev *scu, u32 offset)
{
@ -262,7 +255,7 @@ static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu)
static int intel_scu_ipc_check_status(struct intel_scu_ipc_dev *scu)
{
return scu->irq > 0 ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
return scu->data.irq > 0 ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
}
static struct intel_scu_ipc_dev *intel_scu_ipc_get(struct intel_scu_ipc_dev *scu)
@ -295,12 +288,11 @@ static int pwr_reg_rdwr(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
memset(cbuf, 0, sizeof(cbuf));
mutex_lock(&ipclock);
guard(mutex)(&ipclock);
scu = intel_scu_ipc_get(scu);
if (IS_ERR(scu)) {
mutex_unlock(&ipclock);
if (IS_ERR(scu))
return PTR_ERR(scu);
}
for (nc = 0; nc < count; nc++, offset += 2) {
cbuf[offset] = addr[nc];
@ -325,14 +317,15 @@ static int pwr_reg_rdwr(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
}
err = intel_scu_ipc_check_status(scu);
if (!err && id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
/* Workaround: values are read as 0 without memcpy_fromio */
memcpy_fromio(cbuf, scu->ipc_base + 0x90, 16);
for (nc = 0; nc < count; nc++)
data[nc] = ipc_data_readb(scu, nc);
}
mutex_unlock(&ipclock);
if (err)
return err;
/* Read rbuf */
for (nc = 0, offset = 0; nc < 4; nc++, offset += 4)
wbuf[nc] = ipc_data_readl(scu, offset);
memcpy(data, wbuf, count);
return 0;
}
/**
@ -453,17 +446,15 @@ int intel_scu_ipc_dev_simple_command(struct intel_scu_ipc_dev *scu, int cmd,
u32 cmdval;
int err;
mutex_lock(&ipclock);
guard(mutex)(&ipclock);
scu = intel_scu_ipc_get(scu);
if (IS_ERR(scu)) {
mutex_unlock(&ipclock);
if (IS_ERR(scu))
return PTR_ERR(scu);
}
cmdval = sub << 12 | cmd;
ipc_command(scu, cmdval);
err = intel_scu_ipc_check_status(scu);
mutex_unlock(&ipclock);
if (err)
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
return err;
@ -492,18 +483,17 @@ int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
{
size_t outbuflen = DIV_ROUND_UP(outlen, sizeof(u32));
size_t inbuflen = DIV_ROUND_UP(inlen, sizeof(u32));
u32 cmdval, inbuf[4] = {};
u32 cmdval, inbuf[4] = {}, outbuf[4] = {};
int i, err;
if (inbuflen > 4 || outbuflen > 4)
return -EINVAL;
mutex_lock(&ipclock);
guard(mutex)(&ipclock);
scu = intel_scu_ipc_get(scu);
if (IS_ERR(scu)) {
mutex_unlock(&ipclock);
if (IS_ERR(scu))
return PTR_ERR(scu);
}
memcpy(inbuf, in, inlen);
for (i = 0; i < inbuflen; i++)
@ -512,20 +502,17 @@ int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
cmdval = (size << 16) | (sub << 12) | cmd;
ipc_command(scu, cmdval);
err = intel_scu_ipc_check_status(scu);
if (!err) {
u32 outbuf[4] = {};
if (err) {
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
return err;
}
for (i = 0; i < outbuflen; i++)
outbuf[i] = ipc_data_readl(scu, 4 * i);
memcpy(out, outbuf, outlen);
}
mutex_unlock(&ipclock);
if (err)
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
return err;
return 0;
}
EXPORT_SYMBOL(intel_scu_ipc_dev_command_with_size);
@ -549,13 +536,13 @@ static irqreturn_t ioc(int irq, void *dev_id)
static void intel_scu_ipc_release(struct device *dev)
{
struct intel_scu_ipc_dev *scu;
struct intel_scu_ipc_dev *scu = container_of(dev, struct intel_scu_ipc_dev, dev);
struct intel_scu_ipc_data *data = &scu->data;
scu = container_of(dev, struct intel_scu_ipc_dev, dev);
if (scu->irq > 0)
free_irq(scu->irq, scu);
if (data->irq > 0)
free_irq(data->irq, scu);
iounmap(scu->ipc_base);
release_mem_region(scu->mem.start, resource_size(&scu->mem));
release_mem_region(data->mem.start, resource_size(&data->mem));
kfree(scu);
}
@ -576,46 +563,44 @@ __intel_scu_ipc_register(struct device *parent,
struct module *owner)
{
int err;
struct intel_scu_ipc_data *data;
struct intel_scu_ipc_dev *scu;
void __iomem *ipc_base;
mutex_lock(&ipclock);
guard(mutex)(&ipclock);
/* We support only one IPC */
if (ipcdev) {
err = -EBUSY;
goto err_unlock;
}
if (ipcdev)
return ERR_PTR(-EBUSY);
scu = kzalloc(sizeof(*scu), GFP_KERNEL);
if (!scu) {
err = -ENOMEM;
goto err_unlock;
}
if (!scu)
return ERR_PTR(-ENOMEM);
scu->owner = owner;
scu->dev.parent = parent;
scu->dev.class = &intel_scu_ipc_class;
scu->dev.release = intel_scu_ipc_release;
if (!request_mem_region(scu_data->mem.start, resource_size(&scu_data->mem),
"intel_scu_ipc")) {
memcpy(&scu->data, scu_data, sizeof(scu->data));
data = &scu->data;
if (!request_mem_region(data->mem.start, resource_size(&data->mem), "intel_scu_ipc")) {
err = -EBUSY;
goto err_free;
}
ipc_base = ioremap(scu_data->mem.start, resource_size(&scu_data->mem));
ipc_base = ioremap(data->mem.start, resource_size(&data->mem));
if (!ipc_base) {
err = -ENOMEM;
goto err_release;
}
scu->ipc_base = ipc_base;
scu->mem = scu_data->mem;
scu->irq = scu_data->irq;
init_completion(&scu->cmd_complete);
if (scu->irq > 0) {
err = request_irq(scu->irq, ioc, 0, "intel_scu_ipc", scu);
if (data->irq > 0) {
err = request_irq(data->irq, ioc, 0, "intel_scu_ipc", scu);
if (err)
goto err_unmap;
}
@ -628,24 +613,19 @@ __intel_scu_ipc_register(struct device *parent,
err = device_register(&scu->dev);
if (err) {
put_device(&scu->dev);
goto err_unlock;
return ERR_PTR(err);
}
/* Assign device at last */
ipcdev = scu;
mutex_unlock(&ipclock);
return scu;
err_unmap:
iounmap(ipc_base);
err_release:
release_mem_region(scu_data->mem.start, resource_size(&scu_data->mem));
release_mem_region(data->mem.start, resource_size(&data->mem));
err_free:
kfree(scu);
err_unlock:
mutex_unlock(&ipclock);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(__intel_scu_ipc_register);
@ -659,12 +639,12 @@ EXPORT_SYMBOL_GPL(__intel_scu_ipc_register);
*/
void intel_scu_ipc_unregister(struct intel_scu_ipc_dev *scu)
{
mutex_lock(&ipclock);
guard(mutex)(&ipclock);
if (!WARN_ON(!ipcdev)) {
ipcdev = NULL;
device_unregister(&scu->dev);
}
mutex_unlock(&ipclock);
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_unregister);

View File

@ -298,7 +298,7 @@ static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
static struct platform_driver yt2_1380_fc_pdev_driver = {
.probe = yt2_1380_fc_pdev_probe,
.remove_new = yt2_1380_fc_pdev_remove,
.remove = yt2_1380_fc_pdev_remove,
.driver = {
.name = YT2_1380_FC_PDEV_NAME,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,

View File

@ -536,7 +536,7 @@ static void yogabook_pdev_remove(struct platform_device *pdev)
static struct platform_driver yogabook_pdev_driver = {
.probe = yogabook_pdev_probe,
.remove_new = yogabook_pdev_remove,
.remove = yogabook_pdev_remove,
.driver = {
.name = YB_PDEV_NAME,
.pm = pm_sleep_ptr(&yogabook_pm_ops),

View File

@ -6633,7 +6633,7 @@ static struct platform_driver mlxplat_driver = {
.probe_type = PROBE_FORCE_SYNCHRONOUS,
},
.probe = mlxplat_probe,
.remove_new = mlxplat_remove,
.remove = mlxplat_remove,
};
static int __init mlxplat_init(void)

View File

@ -25,6 +25,7 @@
static const struct x86_cpu_id p2sb_cpu_ids[] = {
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT, P2SB_DEVFN_GOLDMONT),
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_PLUS, P2SB_DEVFN_GOLDMONT),
{}
};

View File

@ -614,8 +614,7 @@ static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr,
result = 1;
break;
default:
result = -EIO;
break;
return -EIO;
}
return sysfs_emit(buf, "%u\n", result);
}
@ -761,7 +760,12 @@ static ssize_t current_brightness_store(struct device *dev, struct device_attrib
static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%d\n", get_optd_power_state());
int state = get_optd_power_state();
if (state < 0)
return state;
return sysfs_emit(buf, "%d\n", state);
}
static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr,

View File

@ -78,7 +78,7 @@ static struct platform_driver samsungq10_driver = {
.name = KBUILD_MODNAME,
},
.probe = samsungq10_probe,
.remove_new = samsungq10_remove,
.remove = samsungq10_remove,
};
static struct platform_device *samsungq10_device;

View File

@ -235,7 +235,7 @@ MODULE_DEVICE_TABLE(acpi, sel3350_device_ids);
static struct platform_driver sel3350_platform_driver = {
.probe = sel3350_probe,
.remove_new = sel3350_remove,
.remove = sel3350_remove,
.driver = {
.name = "sel3350-platform",
.acpi_match_table = sel3350_device_ids,

View File

@ -409,7 +409,7 @@ static struct platform_driver smi_driver = {
.acpi_match_table = smi_acpi_ids,
},
.probe = smi_probe,
.remove_new = smi_remove,
.remove = smi_remove,
};
module_platform_driver(smi_driver);

View File

@ -37,7 +37,7 @@ static int simatic_ipc_batt_apollolake_probe(struct platform_device *pdev)
static struct platform_driver simatic_ipc_batt_driver = {
.probe = simatic_ipc_batt_apollolake_probe,
.remove_new = simatic_ipc_batt_apollolake_remove,
.remove = simatic_ipc_batt_apollolake_remove,
.driver = {
.name = KBUILD_MODNAME,
},

View File

@ -37,7 +37,7 @@ static int simatic_ipc_batt_elkhartlake_probe(struct platform_device *pdev)
static struct platform_driver simatic_ipc_batt_driver = {
.probe = simatic_ipc_batt_elkhartlake_probe,
.remove_new = simatic_ipc_batt_elkhartlake_remove,
.remove = simatic_ipc_batt_elkhartlake_remove,
.driver = {
.name = KBUILD_MODNAME,
},

View File

@ -73,7 +73,7 @@ static int simatic_ipc_batt_f7188x_probe(struct platform_device *pdev)
static struct platform_driver simatic_ipc_batt_driver = {
.probe = simatic_ipc_batt_f7188x_probe,
.remove_new = simatic_ipc_batt_f7188x_remove,
.remove = simatic_ipc_batt_f7188x_remove,
.driver = {
.name = KBUILD_MODNAME,
},

View File

@ -239,7 +239,7 @@ static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
static struct platform_driver simatic_ipc_batt_driver = {
.probe = simatic_ipc_batt_io_probe,
.remove_new = simatic_ipc_batt_io_remove,
.remove = simatic_ipc_batt_io_remove,
.driver = {
.name = KBUILD_MODNAME,
},

View File

@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mutex.h>
@ -173,7 +174,8 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
#define TLMI_PAP_PWD BIT(1) /* Power-on */
#define TLMI_HDD_PWD BIT(2) /* HDD/NVME */
#define TLMI_SMP_PWD BIT(6) /* System Management */
#define TLMI_CERT BIT(7) /* Certificate Based */
#define TLMI_CERT_SVC BIT(7) /* Admin Certificate Based */
#define TLMI_CERT_SMC BIT(8) /* System Certificate Based */
static const struct tlmi_err_codes tlmi_errs[] = {
{"Success", 0},
@ -391,7 +393,7 @@ static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
return sysfs_emit(buf, "%d\n", setting->valid);
return sysfs_emit(buf, "%d\n", setting->pwd_enabled || setting->cert_installed);
}
static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled);
@ -469,7 +471,12 @@ static ssize_t new_password_store(struct kobject *kobj,
if (ret)
goto out;
if (tlmi_priv.pwd_admin->valid) {
/*
* Note admin password is not always required if SMPControl enabled in BIOS,
* So only set if it's configured.
* Let BIOS figure it out - we'll get an error if operation is not permitted
*/
if (tlmi_priv.pwd_admin->pwd_enabled && strlen(tlmi_priv.pwd_admin->password)) {
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
tlmi_priv.pwd_admin->password);
if (ret)
@ -524,6 +531,10 @@ static struct kobj_attribute auth_max_pass_length = __ATTR_RO(max_password_lengt
static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
if (setting->cert_installed)
return sysfs_emit(buf, "certificate\n");
return sysfs_emit(buf, "password\n");
}
static struct kobj_attribute auth_mechanism = __ATTR_RO(mechanism);
@ -644,6 +655,17 @@ static ssize_t level_store(struct kobject *kobj,
static struct kobj_attribute auth_level = __ATTR_RW(level);
static char *cert_command(struct tlmi_pwd_setting *setting, const char *arg1, const char *arg2)
{
/* Prepend with SVC or SMC if multicert supported */
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT)
return kasprintf(GFP_KERNEL, "%s,%s,%s",
setting == tlmi_priv.pwd_admin ? "SVC" : "SMC",
arg1, arg2);
else
return kasprintf(GFP_KERNEL, "%s,%s", arg1, arg2);
}
static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
{
const struct acpi_buffer input = { strlen(arg), (char *)arg };
@ -669,18 +691,35 @@ static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
return count;
}
static char *thumbtypes[] = {"Md5", "Sha1", "Sha256"};
static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
unsigned int i;
int count = 0;
char *wmistr;
if (!tlmi_priv.certificate_support || !setting->cert_installed)
return -EOPNOTSUPP;
count += cert_thumbprint(buf, "Md5", count);
count += cert_thumbprint(buf, "Sha1", count);
count += cert_thumbprint(buf, "Sha256", count);
for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) {
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) {
/* Format: 'SVC | SMC, Thumbtype' */
wmistr = kasprintf(GFP_KERNEL, "%s,%s",
setting == tlmi_priv.pwd_admin ? "SVC" : "SMC",
thumbtypes[i]);
} else {
/* Format: 'Thumbtype' */
wmistr = kasprintf(GFP_KERNEL, "%s", thumbtypes[i]);
}
if (!wmistr)
return -ENOMEM;
count += cert_thumbprint(buf, wmistr, count);
kfree(wmistr);
}
return count;
}
@ -712,7 +751,7 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
return -ENOMEM;
/* Format: 'Password,Signature' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature);
auth_str = cert_command(setting, passwd, setting->signature);
if (!auth_str) {
kfree_sensitive(passwd);
return -ENOMEM;
@ -726,12 +765,19 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password);
enum cert_install_mode {
TLMI_CERT_INSTALL,
TLMI_CERT_UPDATE,
};
static ssize_t certificate_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
enum cert_install_mode install_mode = TLMI_CERT_INSTALL;
char *auth_str, *new_cert;
char *signature;
char *guid;
int ret;
@ -748,7 +794,7 @@ static ssize_t certificate_store(struct kobject *kobj,
return -EACCES;
/* Format: 'serial#, signature' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
auth_str = cert_command(setting,
dmi_get_system_info(DMI_PRODUCT_SERIAL),
setting->signature);
if (!auth_str)
@ -767,24 +813,44 @@ static ssize_t certificate_store(struct kobject *kobj,
if (setting->cert_installed) {
/* Certificate is installed so this is an update */
if (!setting->signature || !setting->signature[0]) {
install_mode = TLMI_CERT_UPDATE;
/* If admin account enabled - need to use its signature */
if (tlmi_priv.pwd_admin->pwd_enabled)
signature = tlmi_priv.pwd_admin->signature;
else
signature = setting->signature;
} else { /* Cert install */
/* Check if SMC and SVC already installed */
if ((setting == tlmi_priv.pwd_system) && tlmi_priv.pwd_admin->cert_installed) {
/* This gets treated as a cert update */
install_mode = TLMI_CERT_UPDATE;
signature = tlmi_priv.pwd_admin->signature;
} else { /* Regular cert install */
install_mode = TLMI_CERT_INSTALL;
signature = setting->signature;
}
}
if (install_mode == TLMI_CERT_UPDATE) {
/* This is a certificate update */
if (!signature || !signature[0]) {
kfree(new_cert);
return -EACCES;
}
guid = LENOVO_UPDATE_BIOS_CERT_GUID;
/* Format: 'Certificate,Signature' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
new_cert, setting->signature);
auth_str = cert_command(setting, new_cert, signature);
} else {
/* This is a fresh install */
if (!setting->valid || !setting->password[0]) {
/* To set admin cert, a password must be enabled */
if ((setting == tlmi_priv.pwd_admin) &&
(!setting->pwd_enabled || !setting->password[0])) {
kfree(new_cert);
return -EACCES;
}
guid = LENOVO_SET_BIOS_CERT_GUID;
/* Format: 'Certificate,Admin-password' */
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
new_cert, setting->password);
/* Format: 'Certificate, password' */
auth_str = cert_command(setting, new_cert, setting->password);
}
kfree(new_cert);
if (!auth_str)
@ -864,14 +930,19 @@ static umode_t auth_attr_is_visible(struct kobject *kobj,
return 0;
}
/* We only display certificates on Admin account, if supported */
/* We only display certificates, if supported */
if (attr == &auth_certificate.attr ||
attr == &auth_signature.attr ||
attr == &auth_save_signature.attr ||
attr == &auth_cert_thumb.attr ||
attr == &auth_cert_to_password.attr) {
if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support)
if (tlmi_priv.certificate_support) {
if (setting == tlmi_priv.pwd_admin)
return attr->mode;
if ((tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) &&
(setting == tlmi_priv.pwd_system))
return attr->mode;
}
return 0;
}
@ -1019,7 +1090,7 @@ static ssize_t current_value_store(struct kobject *kobj,
* Workstation's require the opcode to be set before changing the
* attribute.
*/
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
tlmi_priv.pwd_admin->password);
if (ret)
@ -1042,7 +1113,7 @@ static ssize_t current_value_store(struct kobject *kobj,
else
ret = tlmi_save_bios_settings("");
} else { /* old non-opcode based authentication method (deprecated) */
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
tlmi_priv.pwd_admin->password,
encoding_options[tlmi_priv.pwd_admin->encoding],
@ -1215,7 +1286,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
if (ret)
goto out;
} else if (tlmi_priv.opcode_support) {
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
tlmi_priv.pwd_admin->password);
if (ret)
@ -1223,7 +1294,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
}
ret = tlmi_save_bios_settings("");
} else { /* old non-opcode based authentication method (deprecated) */
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
tlmi_priv.pwd_admin->password,
encoding_options[tlmi_priv.pwd_admin->encoding],
@ -1273,7 +1344,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr
if (!new_setting)
return -ENOMEM;
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
tlmi_priv.pwd_admin->password,
encoding_options[tlmi_priv.pwd_admin->encoding],
@ -1637,14 +1708,14 @@ static int tlmi_analyze(void)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_PAP_PWD)
tlmi_priv.pwd_admin->valid = true;
tlmi_priv.pwd_admin->pwd_enabled = true;
tlmi_priv.pwd_power = tlmi_create_auth("pop", "power-on");
if (!tlmi_priv.pwd_power)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_POP_PWD)
tlmi_priv.pwd_power->valid = true;
tlmi_priv.pwd_power->pwd_enabled = true;
if (tlmi_priv.opcode_support) {
tlmi_priv.pwd_system = tlmi_create_auth("smp", "system");
@ -1652,7 +1723,7 @@ static int tlmi_analyze(void)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_SMP_PWD)
tlmi_priv.pwd_system->valid = true;
tlmi_priv.pwd_system->pwd_enabled = true;
tlmi_priv.pwd_hdd = tlmi_create_auth("hdd", "hdd");
if (!tlmi_priv.pwd_hdd)
@ -1670,7 +1741,7 @@ static int tlmi_analyze(void)
/* Check if PWD is configured and set index to first drive found */
if (tlmi_priv.pwdcfg.ext.hdd_user_password ||
tlmi_priv.pwdcfg.ext.hdd_master_password) {
tlmi_priv.pwd_hdd->valid = true;
tlmi_priv.pwd_hdd->pwd_enabled = true;
if (tlmi_priv.pwdcfg.ext.hdd_master_password)
tlmi_priv.pwd_hdd->index =
ffs(tlmi_priv.pwdcfg.ext.hdd_master_password) - 1;
@ -1680,7 +1751,7 @@ static int tlmi_analyze(void)
}
if (tlmi_priv.pwdcfg.ext.nvme_user_password ||
tlmi_priv.pwdcfg.ext.nvme_master_password) {
tlmi_priv.pwd_nvme->valid = true;
tlmi_priv.pwd_nvme->pwd_enabled = true;
if (tlmi_priv.pwdcfg.ext.nvme_master_password)
tlmi_priv.pwd_nvme->index =
ffs(tlmi_priv.pwdcfg.ext.nvme_master_password) - 1;
@ -1691,10 +1762,12 @@ static int tlmi_analyze(void)
}
}
if (tlmi_priv.certificate_support &&
(tlmi_priv.pwdcfg.core.password_state & TLMI_CERT))
tlmi_priv.pwd_admin->cert_installed = true;
if (tlmi_priv.certificate_support) {
tlmi_priv.pwd_admin->cert_installed =
tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
tlmi_priv.pwd_system->cert_installed =
tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
}
return 0;
fail_clear_attr:

View File

@ -41,6 +41,10 @@ enum save_mode {
};
/* password configuration details */
#define TLMI_PWDCFG_MODE_LEGACY 0
#define TLMI_PWDCFG_MODE_PASSWORD 1
#define TLMI_PWDCFG_MODE_MULTICERT 3
struct tlmi_pwdcfg_core {
uint32_t password_mode;
uint32_t password_state;
@ -65,7 +69,7 @@ struct tlmi_pwdcfg {
/* password setting details */
struct tlmi_pwd_setting {
struct kobject kobj;
bool valid;
bool pwd_enabled;
char password[TLMI_PWD_BUFSIZE];
const char *pwd_type;
const char *role;

View File

@ -22,7 +22,6 @@
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
@ -37,8 +36,6 @@ MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
static LIST_HEAD(wmi_block_list);
struct guid_block {
guid_t guid;
union {
@ -63,7 +60,6 @@ enum { /* wmi_block flags */
struct wmi_block {
struct wmi_device dev;
struct list_head list;
struct guid_block gblock;
struct acpi_device *acpi_device;
struct rw_semaphore notify_lock; /* Protects notify callback add/remove */
@ -73,6 +69,10 @@ struct wmi_block {
unsigned long flags;
};
struct wmi_guid_count_context {
const guid_t *guid;
int count;
};
/*
* If the GUID data block is marked as expensive, we must enable and
@ -91,7 +91,6 @@ static const struct acpi_device_id wmi_device_ids[] = {
MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
#define dev_to_wblock(__dev) container_of_const(__dev, struct wmi_block, dev.dev)
#define dev_to_wdev(__dev) container_of_const(__dev, struct wmi_device, dev)
/*
* GUID parsing functions
@ -199,7 +198,7 @@ static struct wmi_device *wmi_find_device_by_guid(const char *guid_string)
if (!dev)
return ERR_PTR(-ENODEV);
return dev_to_wdev(dev);
return to_wmi_device(dev);
}
static void wmi_device_put(struct wmi_device *wdev)
@ -654,8 +653,6 @@ char *wmi_get_acpi_device_uid(const char *guid_string)
}
EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid);
#define drv_to_wdrv(__drv) container_of_const(__drv, struct wmi_driver, driver)
/*
* sysfs interface
*/
@ -761,7 +758,7 @@ static DEVICE_ATTR_RO(object_id);
static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct wmi_device *wdev = dev_to_wdev(dev);
struct wmi_device *wdev = to_wmi_device(dev);
return sysfs_emit(buf, "%d\n", (int)wdev->setable);
}
@ -803,7 +800,7 @@ static void wmi_dev_release(struct device *dev)
static int wmi_dev_match(struct device *dev, const struct device_driver *driver)
{
const struct wmi_driver *wmi_driver = drv_to_wdrv(driver);
const struct wmi_driver *wmi_driver = to_wmi_driver(driver);
struct wmi_block *wblock = dev_to_wblock(dev);
const struct wmi_device_id *id = wmi_driver->id_table;
@ -827,7 +824,7 @@ static int wmi_dev_match(struct device *dev, const struct device_driver *driver)
static int wmi_dev_probe(struct device *dev)
{
struct wmi_block *wblock = dev_to_wblock(dev);
struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
int ret = 0;
/* Some older WMI drivers will break if instantiated multiple times,
@ -851,7 +848,7 @@ static int wmi_dev_probe(struct device *dev)
dev_warn(dev, "failed to enable device -- probing anyway\n");
if (wdriver->probe) {
ret = wdriver->probe(dev_to_wdev(dev),
ret = wdriver->probe(to_wmi_device(dev),
find_guid_context(wblock, wdriver));
if (ret) {
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
@ -871,19 +868,45 @@ static int wmi_dev_probe(struct device *dev)
static void wmi_dev_remove(struct device *dev)
{
struct wmi_block *wblock = dev_to_wblock(dev);
struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
down_write(&wblock->notify_lock);
wblock->driver_ready = false;
up_write(&wblock->notify_lock);
if (wdriver->remove)
wdriver->remove(dev_to_wdev(dev));
wdriver->remove(to_wmi_device(dev));
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
dev_warn(dev, "failed to disable device\n");
}
static void wmi_dev_shutdown(struct device *dev)
{
struct wmi_driver *wdriver;
struct wmi_block *wblock;
if (dev->driver) {
wdriver = to_wmi_driver(dev->driver);
wblock = dev_to_wblock(dev);
/*
* Some machines return bogus WMI event data when disabling
* the WMI event. Because of this we must prevent the associated
* WMI driver from receiving new WMI events before disabling it.
*/
down_write(&wblock->notify_lock);
wblock->driver_ready = false;
up_write(&wblock->notify_lock);
if (wdriver->shutdown)
wdriver->shutdown(to_wmi_device(dev));
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
dev_warn(dev, "Failed to disable device\n");
}
}
static struct class wmi_bus_class = {
.name = "wmi_bus",
};
@ -895,6 +918,7 @@ static const struct bus_type wmi_bus_type = {
.uevent = wmi_dev_uevent,
.probe = wmi_dev_probe,
.remove = wmi_dev_remove,
.shutdown = wmi_dev_shutdown,
};
static const struct device_type wmi_type_event = {
@ -915,21 +939,30 @@ static const struct device_type wmi_type_data = {
.release = wmi_dev_release,
};
/*
* _WDG is a static list that is only parsed at startup,
* so it's safe to count entries without extra protection.
*/
static int wmi_count_guids(struct device *dev, void *data)
{
struct wmi_guid_count_context *context = data;
struct wmi_block *wblock = dev_to_wblock(dev);
if (guid_equal(&wblock->gblock.guid, context->guid))
context->count++;
return 0;
}
static int guid_count(const guid_t *guid)
{
struct wmi_block *wblock;
int count = 0;
struct wmi_guid_count_context context = {
.guid = guid,
.count = 0,
};
int ret;
list_for_each_entry(wblock, &wmi_block_list, list) {
if (guid_equal(&wblock->gblock.guid, guid))
count++;
}
ret = bus_for_each_dev(&wmi_bus_type, NULL, &context, wmi_count_guids);
if (ret < 0)
return ret;
return count;
return context.count;
}
static int wmi_create_device(struct device *wmi_bus_dev,
@ -940,7 +973,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
struct acpi_device_info *info;
acpi_handle method_handle;
acpi_status status;
uint count;
int count;
if (wblock->gblock.flags & ACPI_WMI_EVENT) {
wblock->dev.dev.type = &wmi_type_event;
@ -1008,6 +1041,9 @@ static int wmi_create_device(struct device *wmi_bus_dev,
wblock->dev.dev.parent = wmi_bus_dev;
count = guid_count(&wblock->gblock.guid);
if (count < 0)
return count;
if (count) {
dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
set_bit(WMI_GUID_DUPLICATED, &wblock->flags);
@ -1093,14 +1129,11 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
continue;
}
list_add_tail(&wblock->list, &wmi_block_list);
retval = wmi_add_device(pdev, &wblock->dev);
if (retval) {
dev_err(wmi_bus_dev, "failed to register %pUL\n",
&wblock->gblock.guid);
list_del(&wblock->list);
put_device(&wblock->dev.dev);
}
}
@ -1138,7 +1171,7 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
{
struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver);
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
if (!obj && !driver->no_notify_data) {
dev_warn(&wblock->dev.dev, "Event contains no event data\n");
@ -1200,9 +1233,6 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context
static int wmi_remove_device(struct device *dev, void *data)
{
struct wmi_block *wblock = dev_to_wblock(dev);
list_del(&wblock->list);
device_unregister(dev);
return 0;
@ -1301,7 +1331,7 @@ static struct platform_driver acpi_wmi_driver = {
.acpi_match_table = wmi_device_ids,
},
.probe = acpi_wmi_probe,
.remove_new = acpi_wmi_remove,
.remove = acpi_wmi_remove,
};
static int __init acpi_wmi_init(void)

View File

@ -5,7 +5,9 @@
config X86_ANDROID_TABLETS
tristate "X86 Android tablet support"
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB && PMIC_OPREGION
depends on I2C && SPI && SERIAL_DEV_BUS
depends on GPIOLIB && PMIC_OPREGION
depends on ACPI && EFI && PCI
select NEW_LEDS
select LEDS_CLASS
help

View File

@ -11,11 +11,13 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/serdev.h>
#include <linux/string.h>
@ -155,26 +157,66 @@ static struct gpiod_lookup_table * const *gpiod_lookup_tables;
static const struct software_node *bat_swnode;
static void (*exit_handler)(void);
static struct i2c_adapter *
get_i2c_adap_by_handle(const struct x86_i2c_client_info *client_info)
{
acpi_handle handle;
acpi_status status;
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
if (ACPI_FAILURE(status)) {
pr_err("Error could not get %s handle\n", client_info->adapter_path);
return NULL;
}
return i2c_acpi_find_adapter_by_handle(handle);
}
static __init int match_parent(struct device *dev, const void *data)
{
return dev->parent == data;
}
static struct i2c_adapter *
get_i2c_adap_by_pci_parent(const struct x86_i2c_client_info *client_info)
{
struct i2c_adapter *adap = NULL;
struct device *pdev, *adap_dev;
pdev = bus_find_device_by_name(&pci_bus_type, NULL, client_info->adapter_path);
if (!pdev) {
pr_err("Error could not find %s PCI device\n", client_info->adapter_path);
return NULL;
}
adap_dev = bus_find_device(&i2c_bus_type, NULL, pdev, match_parent);
if (adap_dev) {
adap = i2c_verify_adapter(adap_dev);
if (!adap)
put_device(adap_dev);
}
put_device(pdev);
return adap;
}
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
int idx)
{
const struct x86_i2c_client_info *client_info = &dev_info->i2c_client_info[idx];
struct i2c_board_info board_info = client_info->board_info;
struct i2c_adapter *adap;
acpi_handle handle;
acpi_status status;
board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data);
if (board_info.irq < 0)
return board_info.irq;
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
if (ACPI_FAILURE(status)) {
pr_err("Error could not get %s handle\n", client_info->adapter_path);
return -ENODEV;
}
if (dev_info->use_pci_devname)
adap = get_i2c_adap_by_pci_parent(client_info);
else
adap = get_i2c_adap_by_handle(client_info);
adap = i2c_acpi_find_adapter_by_handle(handle);
if (!adap) {
pr_err("error could not get %s adapter\n", client_info->adapter_path);
return -ENODEV;
@ -458,7 +500,7 @@ static struct platform_driver x86_android_tablet_driver = {
.driver = {
.name = KBUILD_MODNAME,
},
.remove_new = x86_android_tablet_remove,
.remove = x86_android_tablet_remove,
};
static int __init x86_android_tablet_init(void)

View File

@ -179,6 +179,16 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
},
.driver_data = (void *)&peaq_c1010_info,
},
{
/* Vexia Edu Atla 10 tablet 9V version */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "08/25/2014"),
},
.driver_data = (void *)&vexia_edu_atla10_info,
},
{
/* Whitelabel (sold as various brands) TM800A550L */
.matches = {

View File

@ -12,6 +12,7 @@
#include <linux/gpio/machine.h>
#include <linux/input.h>
#include <linux/leds.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@ -597,6 +598,168 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
.gpiod_lookup_tables = whitelabel_tm800a550l_gpios,
};
/*
* Vexia EDU ATLA 10 tablet, Android 4.2 / 4.4 + Guadalinex Ubuntu tablet
* distributed to schools in the Spanish Andalucía region.
*/
const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
static const struct property_entry vexia_edu_atla10_ulpmc_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", crystal_cove_pwrsrc_psy),
{ }
};
const struct software_node vexia_edu_atla10_ulpmc_node = {
.properties = vexia_edu_atla10_ulpmc_props,
};
static const char * const vexia_edu_atla10_accel_mount_matrix[] = {
"0", "-1", "0",
"1", "0", "0",
"0", "0", "1"
};
static const struct property_entry vexia_edu_atla10_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", vexia_edu_atla10_accel_mount_matrix),
{ }
};
static const struct software_node vexia_edu_atla10_accel_node = {
.properties = vexia_edu_atla10_accel_props,
};
static const struct property_entry vexia_edu_atla10_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
{ }
};
static const struct software_node vexia_edu_atla10_touchscreen_node = {
.properties = vexia_edu_atla10_touchscreen_props,
};
static const struct property_entry vexia_edu_atla10_pmic_props[] = {
PROPERTY_ENTRY_BOOL("linux,register-pwrsrc-power_supply"),
{ }
};
static const struct software_node vexia_edu_atla10_pmic_node = {
.properties = vexia_edu_atla10_pmic_props,
};
static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initconst = {
{
/* I2C attached embedded controller, used to access fuel-gauge */
.board_info = {
.type = "vexia_atla10_ec",
.addr = 0x76,
.dev_name = "ulpmc",
.swnode = &vexia_edu_atla10_ulpmc_node,
},
.adapter_path = "0000:00:18.1",
}, {
/* RT5642 audio codec */
.board_info = {
.type = "rt5640",
.addr = 0x1c,
.dev_name = "rt5640",
},
.adapter_path = "0000:00:18.2",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 4,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
.con_id = "rt5640_irq",
},
}, {
/* kxtj21009 accelerometer */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &vexia_edu_atla10_accel_node,
},
.adapter_path = "0000:00:18.5",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "hid-over-i2c",
.addr = 0x38,
.dev_name = "FTSC1000",
.swnode = &vexia_edu_atla10_touchscreen_node,
},
.adapter_path = "0000:00:18.6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* Crystal Cove PMIC */
.board_info = {
.type = "intel_soc_pmic_crc",
.addr = 0x6e,
.dev_name = "intel_soc_pmic_crc",
.swnode = &vexia_edu_atla10_pmic_node,
},
.adapter_path = "0000:00:18.7",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x43,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}
};
static struct gpiod_lookup_table vexia_edu_atla10_ft5416_gpios = {
.dev_id = "i2c-FTSC1000",
.table = {
GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const vexia_edu_atla10_gpios[] = {
&vexia_edu_atla10_ft5416_gpios,
NULL
};
static int __init vexia_edu_atla10_init(struct device *dev)
{
struct pci_dev *pdev;
int ret;
/* Enable the Wifi module by setting the wifi_enable pin to 1 */
ret = x86_android_tablet_get_gpiod("INT33FC:02", 20, "wifi_enable",
false, GPIOD_OUT_HIGH, NULL);
if (ret)
return ret;
/* Reprobe the SDIO controller to enumerate the now enabled Wifi module */
pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0));
if (!pdev)
return -EPROBE_DEFER;
ret = device_reprobe(&pdev->dev);
if (ret)
pci_warn(pdev, "Reprobing error: %d\n", ret);
pci_dev_put(pdev);
return 0;
}
const struct x86_dev_info vexia_edu_atla10_info __initconst = {
.i2c_client_info = vexia_edu_atla10_i2c_clients,
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_i2c_clients),
.gpiod_lookup_tables = vexia_edu_atla10_gpios,
.init = vexia_edu_atla10_init,
.use_pci_devname = true,
};
/*
* The firmware node for ktd2026 on Xaomi pad2. It composed of a RGB LED node
* with three subnodes for each color (B/G/R). The RGB LED node is named

View File

@ -91,6 +91,7 @@ struct x86_dev_info {
int gpio_button_count;
int (*init)(struct device *dev);
void (*exit)(void);
bool use_pci_devname;
};
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
@ -119,6 +120,7 @@ extern const struct x86_dev_info nextbook_ares8_info;
extern const struct x86_dev_info nextbook_ares8a_info;
extern const struct x86_dev_info peaq_c1010_info;
extern const struct x86_dev_info whitelabel_tm800a550l_info;
extern const struct x86_dev_info vexia_edu_atla10_info;
extern const struct x86_dev_info xiaomi_mipad2_info;
extern const struct dmi_system_id x86_android_tablet_ids[];

View File

@ -68,7 +68,7 @@ static struct platform_driver xo1_rfkill_driver = {
.name = "xo1-rfkill",
},
.probe = xo1_rfkill_probe,
.remove_new = xo1_rfkill_remove,
.remove = xo1_rfkill_remove,
};
module_platform_driver(xo1_rfkill_driver);

View File

@ -74,10 +74,11 @@ enum intel_vsec_quirks {
* @pdev: PCI device reference for the callback's use
* @guid: ID of data to acccss
* @data: buffer for the data to be copied
* @off: offset into the requested buffer
* @count: size of buffer
*/
struct pmt_callbacks {
int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, u32 count);
int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, loff_t off, u32 count);
};
/**

View File

@ -2,9 +2,13 @@
#ifndef __PLATFORM_X86_INTEL_SCU_IPC_H_
#define __PLATFORM_X86_INTEL_SCU_IPC_H_
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/types.h>
struct device;
struct module;
struct intel_scu_ipc_dev;
/**

View File

@ -34,7 +34,7 @@ struct wmi_device {
*
* Cast a struct device to a struct wmi_device.
*/
#define to_wmi_device(device) container_of(device, struct wmi_device, dev)
#define to_wmi_device(device) container_of_const(device, struct wmi_device, dev)
extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev,
u8 instance, u32 method_id,
@ -56,6 +56,7 @@ u8 wmidev_instance_count(struct wmi_device *wdev);
* @no_singleton: Driver can be instantiated multiple times
* @probe: Callback for device binding
* @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
* @notify: Callback for receiving WMI events
*
* This represents WMI drivers which handle WMI devices.
@ -68,9 +69,18 @@ struct wmi_driver {
int (*probe)(struct wmi_device *wdev, const void *context);
void (*remove)(struct wmi_device *wdev);
void (*shutdown)(struct wmi_device *wdev);
void (*notify)(struct wmi_device *device, union acpi_object *data);
};
/**
* to_wmi_driver() - Helper macro to cast a driver to a wmi_driver
* @drv: driver struct
*
* Cast a struct device_driver to a struct wmi_driver.
*/
#define to_wmi_driver(drv) container_of_const(drv, struct wmi_driver, driver)
extern int __must_check __wmi_driver_register(struct wmi_driver *driver,
struct module *owner);
extern void wmi_driver_unregister(struct wmi_driver *driver);