platform-drivers-x86 for v6.6-1
Highlights: - hp-bioscfg: New firmware-attributes driver for changing BIOS settings from within Linux - asus-wmi: Add charger mode, middle fan and eGPU settings support - ideapad: Support keyboard backlight control on more models - mellanox: Support for new models - sel-3350: New LED and power-supply driver for this industrial mainboard - simatic-ipc: Add RTC battery monitor and various new models support - Miscellaneous other cleanups / fixes The following is an automated git shortlog grouped by driver: Add SEL-3350 platform driver: - Add SEL-3350 platform driver Documentation/ABI: - Add new attribute for mlxreg-io sysfs interfaces MAINTAINERS: - Add entries for Siemens IPC modules Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans: - Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans Merge remote-tracking branch 'pdx86/fixes' into pdx86/for-next: - Merge remote-tracking branch 'pdx86/fixes' into pdx86/for-next Merge remote-tracking branch 'pdx86/platform-drivers-x86-simatic-ipc' into review-hans: - Merge remote-tracking branch 'pdx86/platform-drivers-x86-simatic-ipc' into review-hans Merge tag 'ib-pdx86-simatic-v6.6' into review-hans: - Merge tag 'ib-pdx86-simatic-v6.6' into review-hans Merge tag 'ib-pdx86-simatic-v6.6-2' into review-hans: - Merge tag 'ib-pdx86-simatic-v6.6-2' into review-hans Move all simatic ipc drivers to the subdirectory siemens: - Move all simatic ipc drivers to the subdirectory siemens asus-wmi: - corrections to egpu safety check - Fix support for showing middle fan RPM - expose dGPU and CPU tunables for ROG - support setting mini-LED mode - add safety checks to gpu switching - don't allow eGPU switching if eGPU not connected - add WMI method to show if egpu connected - support middle fan custom curves - add support for showing middle fan RPM - add support for showing charger mode dell-sysman: - Fix reference leak doc: - TPMI: Add debugfs documentation hp-bioscfg: - Update steps order list elements are evaluated - Use kmemdup() to replace kmalloc + memcpy - Remove duplicate use of variable in inner loop - Change how password encoding size is evaluated - Change how enum possible values size is evaluated - Change how order list size is evaluated - Change how prerequisites size is evaluated - Replace the word HACK from source code - Fix uninitialized variable errors - Fix memory leaks in attribute packages - fix error reporting in hp_add_other_attributes() - prevent a small buffer overflow - fix a signedness bug in hp_wmi_perform_query() - MAINTAINERS - Makefile - surestart-attributes - string-attributes - spmobj-attributes - passwdobj-attributes - order-list-attributes - int-attributes - enum-attributes - biosattr-interface - bioscfg - bioscfg-h - Documentation ideapad-laptop: - Add support for keyboard backlights using KBLC ACPI symbol leds: - simatic-ipc-leds: default config switch to platform switch mlx-platform: - Add dependency on PCI to Kconfig mlxbf-bootctl: - Support sysfs entries for MFG fields - Support setting the ARM boot state to "OS up" - Support the large icmc write/read p2sb: - Make the Kconfig symbol hidden platform: - mellanox: nvsw-sn2201: change fans i2c busses. - mellanox: mlxreg-hotplug: Extend condition for notification callback processing - mellanox: Add initial support for PCIe based programming logic device - mellanox: mlx-platform: Get interrupt line through ACPI - mellanox: mlx-platform: Introduce ACPI init flow - mellanox: mlx-platform: Prepare driver to allow probing through ACPI infrastructure - mellanox: mlx-platform: Add reset callback - mellanox: Cosmetic changes - mellanox: mlx-platform: Modify power off callback - mellanox: mlx-platform: add support for additional CPLD - mellanox: mlx-platform: Add reset cause attribute - mellanox: mlx-platform: Modify health and power hotplug action - mellanox: Modify reset causes description - mellanox: Add field upgrade capability register - mellanox: Add new attributes - Explicitly include correct DT includes platform/x86/amd/pmc: - Fix build error with randconfig - Move PMC driver to separate directory platform/x86/amd/pmf: - Fix a missing cleanup path - Use str_on_off() helper platform/x86/intel/tpmi: - Add debugfs interface - Read feature control status platform/x86/siemens: - simatic-ipc-batt: fix logical error for BX-59A - simatic-ipc: fix logical error for BX-59A - simatic-ipc-batt: fix wrong pointer pass to PTR_ERR() - simatic-ipc-batt: add support for module BX-59A - simatic-ipc: add new models BX-56A/BX-59A - Kconfig: adjust help text - simatic-ipc-batt: fix bat reading in BX_21A simatic-ipc: - use extra module loading for watchdog - add auto-loading of hwmon modules - add another model - drop PCI runtime depends and header - add CMOS battery monitoring - add another model BX-21A system76: - Handle new KBLED ACPI methods thinkpad_acpi: - Switch to memdup_user_nul() helper - use lockdep annotations - take mutex for hotkey_mask_{set,get} tools/power/x86/intel-speed-select: - v1.17 release - Change mem-frequency display name - Prevent CPU 0 offline - Error on CPU count exceed in request - Support more than 8 sockets. - Fix CPU count display watchdog: - simatic: Use idiomatic selection of P2SB - simatic: add PCI dependency - make Siemens Simatic watchdog driver default on platform - simatic-ipc-wdt: make IO region access of one model muxed wmi-bmof: - Update MAINTAINERS entry - Simplify read_bmof() - Use device_create_bin_file() -----BEGIN PGP SIGNATURE----- iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmTx720UHGhkZWdvZWRl QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9wHYggAgPpl4tcbqUK7S/fwVP2SzBKI+eF3 5PjO7PlMEVzyKyjHdkqoV8miMffWAGVPE+LV33Uifs5WTFjHRq2fq/Esvj9mAstG 1fCoHJ442xYkwNCUT1CCP7VsmcvV5eFXBjBantvwmIs8TyknGHwtq1h+d95evp4n 2uyQlRMmrWh/+8fjD8x5V35T0tky+4a4EX2WNLul13LlHCybGT/F2Kq456WdthjJ zTQSL+qAMWmAiQKSEmhI3bRnFYPdpTetjiNSTlQczch8Y4qV3mJQlnkyDPX7SM9w M2uZ4W39Ptxihx0ks/MIcuA4yrTfUX8BnhMTD29ZYCKHS7xBqW8YC5HQug== =GaQL -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v6.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86 Pull x86 platform driver updates from Hans de Goede: - hp-bioscfg: New firmware-attributes driver for changing BIOS settings from within Linux - asus-wmi: Add charger mode, middle fan and eGPU settings support - ideapad: Support keyboard backlight control on more models - mellanox: Support for new models - sel-3350: New LED and power-supply driver for this industrial mainboard - simatic-ipc: Add RTC battery monitor and various new models support - miscellaneous other cleanups / fixes * tag 'platform-drivers-x86-v6.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (101 commits) platform/x86: asus-wmi: corrections to egpu safety check platform/x86: mlx-platform: Add dependency on PCI to Kconfig platform/x86: ideapad-laptop: Add support for keyboard backlights using KBLC ACPI symbol platform/x86/amd/pmc: Fix build error with randconfig platform/x86/amd/pmf: Fix a missing cleanup path watchdog: simatic: Use idiomatic selection of P2SB platform/x86: p2sb: Make the Kconfig symbol hidden Documentation/ABI: Add new attribute for mlxreg-io sysfs interfaces platform: mellanox: nvsw-sn2201: change fans i2c busses. platform: mellanox: mlxreg-hotplug: Extend condition for notification callback processing platform: mellanox: Add initial support for PCIe based programming logic device platform: mellanox: mlx-platform: Get interrupt line through ACPI platform: mellanox: mlx-platform: Introduce ACPI init flow platform: mellanox: mlx-platform: Prepare driver to allow probing through ACPI infrastructure platform: mellanox: mlx-platform: Add reset callback platform: mellanox: Cosmetic changes platform: mellanox: mlx-platform: Modify power off callback platform: mellanox: mlx-platform: add support for additional CPLD platform: mellanox: mlx-platform: Add reset cause attribute platform: mellanox: mlx-platform: Modify health and power hotplug action ...
This commit is contained in:
Коммит
e2c874f999
|
@ -662,3 +662,56 @@ Description: This file shows the system reset cause due to AC power failure.
|
|||
Value 1 in file means this is reset cause, 0 - otherwise.
|
||||
|
||||
The file is read only.
|
||||
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_pn
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_version
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_version_min
|
||||
Date: August 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: Vadim Pasternak <vadimp@nvidia.com>
|
||||
Description: These files show with which CPLD part numbers, version and minor
|
||||
versions have been burned the 5-th CPLD device equipped on a
|
||||
system.
|
||||
|
||||
The files are read only.
|
||||
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/jtag_cap
|
||||
Date: August 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: Vadim Pasternak <vadimp@nvidia.com>
|
||||
Description: This file indicates the available method of CPLD/FPGA devices
|
||||
field update through the JTAG chain:
|
||||
|
||||
b00 - field update through LPC bus register memory space.
|
||||
b01 - Reserved.
|
||||
b10 - Reserved.
|
||||
b11 - field update through CPU GPIOs bit-banging.
|
||||
|
||||
The file is read only.
|
||||
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/lid_open
|
||||
Date: August 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: Vadim Pasternak <vadimp@nvidia.com>
|
||||
Description: 1 - indicates that system lid is opened, otherwise 0.
|
||||
|
||||
The file is read only.
|
||||
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_long_pwr_pb
|
||||
Date: August 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: Vadim Pasternak <vadimp@nvidia.com>
|
||||
Description: This file if set 1 indicates that system has been reset by
|
||||
long press of power button.
|
||||
|
||||
The file is read only.
|
||||
|
||||
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_swb_dc_dc_pwr_fail
|
||||
Date: August 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: Vadim Pasternak <vadimp@nvidia.com>
|
||||
Description: This file shows 1 in case the system reset happened due to the
|
||||
failure of any DC-DC power converter devices equipped on the
|
||||
switch board.
|
||||
|
||||
The file is read only.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
What: /sys/kernel/debug/tpmi-<n>/pfs_dump
|
||||
Date: November 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: srinivas.pandruvada@linux.intel.com
|
||||
Description:
|
||||
The PFS (PM Feature Structure) table, shows details of each power
|
||||
management feature. This includes:
|
||||
tpmi_id, number of entries, entry size, offset, vsec offset, lock status
|
||||
and disabled status.
|
||||
Users: Debugging, any user space test suite
|
||||
|
||||
What: /sys/kernel/debug/tpmi-<n>/tpmi-id-<n>/mem_dump
|
||||
Date: November 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: srinivas.pandruvada@linux.intel.com
|
||||
Description:
|
||||
Shows the memory dump of the MMIO region for a TPMI ID.
|
||||
Users: Debugging, any user space test suite
|
||||
|
||||
What: /sys/kernel/debug/tpmi-<n>/tpmi-id-<n>/mem_write
|
||||
Date: November 2023
|
||||
KernelVersion: 6.6
|
||||
Contact: srinivas.pandruvada@linux.intel.com
|
||||
Description:
|
||||
Allows to write at any offset. It doesn't check for Read/Write access
|
||||
as hardware will not allow to write at read-only memory. This write is
|
||||
at offset multiples of 4. The format is instance,offset,contents.
|
||||
Example:
|
||||
echo 0,0x20,0xff > mem_write
|
||||
echo 1,64,64 > mem_write
|
||||
Users: Debugging, any user space test suite
|
|
@ -22,6 +22,11 @@ Description:
|
|||
- integer: a range of numerical values
|
||||
- string
|
||||
|
||||
HP specific types
|
||||
-----------------
|
||||
- ordered-list - a set of ordered list valid values
|
||||
|
||||
|
||||
All attribute types support the following values:
|
||||
|
||||
current_value:
|
||||
|
@ -126,6 +131,21 @@ Description:
|
|||
value will not be effective through sysfs until this rule is
|
||||
met.
|
||||
|
||||
HP specific class extensions
|
||||
------------------------------
|
||||
|
||||
On HP systems the following additional attributes are available:
|
||||
|
||||
"ordered-list"-type specific properties:
|
||||
|
||||
elements:
|
||||
A file that can be read to obtain the possible
|
||||
list of values of the <attr>. Values are separated using
|
||||
semi-colon (``;``) and listed according to their priority.
|
||||
An element listed first has the highest priority. Writing
|
||||
the list in a different order to current_value alters
|
||||
the priority order for the particular attribute.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/authentication/
|
||||
Date: February 2021
|
||||
KernelVersion: 5.11
|
||||
|
@ -206,7 +226,7 @@ Description:
|
|||
Drivers may emit a CHANGE uevent when a password is set or unset
|
||||
userspace may check it again.
|
||||
|
||||
On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes
|
||||
On Dell, Lenovo and HP systems, if Admin password is set, then all BIOS attributes
|
||||
require password validation.
|
||||
On Lenovo systems if you change the Admin password the new password is not active until
|
||||
the next boot.
|
||||
|
@ -296,6 +316,15 @@ Description:
|
|||
echo "signature" > authentication/Admin/signature
|
||||
echo "password" > authentication/Admin/certificate_to_password
|
||||
|
||||
HP specific class extensions
|
||||
--------------------------------
|
||||
|
||||
On HP systems the following additional settings are available:
|
||||
|
||||
role: enhanced-bios-auth:
|
||||
This role is specific to Secure Platform Management (SPM) attribute.
|
||||
It requires configuring an endorsement (kek) and signing certificate (sk).
|
||||
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
|
||||
Date: February 2021
|
||||
|
@ -311,7 +340,7 @@ Description:
|
|||
== =========================================
|
||||
0 All BIOS attributes setting are current
|
||||
1 A reboot is necessary to get pending BIOS
|
||||
attribute changes applied
|
||||
attribute changes applied
|
||||
== =========================================
|
||||
|
||||
Note, userspace applications need to follow below steps for efficient
|
||||
|
@ -364,3 +393,71 @@ Description:
|
|||
use it to enable extra debug attributes or BIOS features for testing purposes.
|
||||
|
||||
Note that any changes to this attribute requires a reboot for changes to take effect.
|
||||
|
||||
|
||||
HP specific class extensions - Secure Platform Manager (SPM)
|
||||
--------------------------------
|
||||
|
||||
What: /sys/class/firmware-attributes/*/authentication/SPM/kek
|
||||
Date: March 2023
|
||||
KernelVersion: 5.18
|
||||
Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
|
||||
Description:
|
||||
'kek' Key-Encryption-Key is a write-only file that can be used to configure the
|
||||
RSA public key that will be used by the BIOS to verify
|
||||
signatures when setting the signing key. When written,
|
||||
the bytes should correspond to the KEK certificate
|
||||
(x509 .DER format containing an OU). The size of the
|
||||
certificate must be less than or equal to 4095 bytes.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/authentication/SPM/sk
|
||||
Date: March 2023
|
||||
KernelVersion: 5.18
|
||||
Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
|
||||
Description:
|
||||
'sk' Signature Key is a write-only file that can be used to configure the RSA
|
||||
public key that will be used by the BIOS to verify signatures
|
||||
when configuring BIOS settings and security features. When
|
||||
written, the bytes should correspond to the modulus of the
|
||||
public key. The exponent is assumed to be 0x10001.
|
||||
|
||||
What: /sys/class/firmware-attributes/*/authentication/SPM/status
|
||||
Date: March 2023
|
||||
KernelVersion: 5.18
|
||||
Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
|
||||
Description:
|
||||
'status' is a read-only file that returns ASCII text in JSON format reporting
|
||||
the status information.
|
||||
|
||||
"State": "not provisioned | provisioned | provisioning in progress",
|
||||
"Version": "Major.Minor",
|
||||
"Nonce": <16-bit unsigned number display in base 10>,
|
||||
"FeaturesInUse": <16-bit unsigned number display in base 10>,
|
||||
"EndorsementKeyMod": "<256 bytes in base64>",
|
||||
"SigningKeyMod": "<256 bytes in base64>"
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/Sure_Start/audit_log_entries
|
||||
Date: March 2023
|
||||
KernelVersion: 5.18
|
||||
Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
|
||||
Description:
|
||||
'audit_log_entries' is a read-only file that returns the events in the log.
|
||||
|
||||
Audit log entry format
|
||||
|
||||
Byte 0-15: Requested Audit Log entry (Each Audit log is 16 bytes)
|
||||
Byte 16-127: Unused
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/Sure_Start/audit_log_entry_count
|
||||
Date: March 2023
|
||||
KernelVersion: 5.18
|
||||
Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
|
||||
Description:
|
||||
'audit_log_entry_count' is a read-only file that returns the number of existing
|
||||
audit log events available to be read. Values are separated using comma. (``,``)
|
||||
|
||||
[No of entries],[log entry size],[Max number of entries supported]
|
||||
|
||||
log entry size identifies audit log size for the current BIOS version.
|
||||
The current size is 16 bytes but it can be up to 128 bytes long in future BIOS
|
||||
versions.
|
||||
|
|
|
@ -98,3 +98,91 @@ Description:
|
|||
Enable an LCD response-time boost to reduce or remove ghosting:
|
||||
* 0 - Disable,
|
||||
* 1 - Enable
|
||||
|
||||
What: /sys/devices/platform/<platform>/charge_mode
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Get the current charging mode being used:
|
||||
* 1 - Barrel connected charger,
|
||||
* 2 - USB-C charging
|
||||
* 3 - Both connected, barrel used for charging
|
||||
|
||||
What: /sys/devices/platform/<platform>/egpu_connected
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Show if the egpu (XG Mobile) is correctly connected:
|
||||
* 0 - False,
|
||||
* 1 - True
|
||||
|
||||
What: /sys/devices/platform/<platform>/mini_led_mode
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Change the mini-LED mode:
|
||||
* 0 - Single-zone,
|
||||
* 1 - Multi-zone
|
||||
|
||||
What: /sys/devices/platform/<platform>/ppt_pl1_spl
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD.
|
||||
Shown on Intel+Nvidia or AMD+Nvidia based systems:
|
||||
|
||||
* min=5, max=250
|
||||
|
||||
What: /sys/devices/platform/<platform>/ppt_pl2_sppt
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT,
|
||||
on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems:
|
||||
|
||||
* min=5, max=250
|
||||
|
||||
What: /sys/devices/platform/<platform>/ppt_fppt
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only:
|
||||
* min=5, max=250
|
||||
|
||||
What: /sys/devices/platform/<platform>/ppt_apu_sppt
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the APU SPPT limit. Shown on full AMD systems only:
|
||||
* min=5, max=130
|
||||
|
||||
What: /sys/devices/platform/<platform>/ppt_platform_sppt
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the platform SPPT limit. Shown on full AMD systems only:
|
||||
* min=5, max=130
|
||||
|
||||
What: /sys/devices/platform/<platform>/nv_dynamic_boost
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the dynamic boost limit of the Nvidia dGPU:
|
||||
* min=5, max=25
|
||||
|
||||
What: /sys/devices/platform/<platform>/nv_temp_target
|
||||
Date: Jun 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "Luke Jones" <luke@ljones.dev>
|
||||
Description:
|
||||
Set the target temperature limit of the Nvidia dGPU:
|
||||
* min=75, max=87
|
||||
|
|
|
@ -84,3 +84,69 @@ Description:
|
|||
The file used to write BlueField boot log with the format
|
||||
"[INFO|WARN|ERR|ASSERT ]<msg>". Log level 'INFO' is used by
|
||||
default if not specified.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/oob_mac
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "oob_mac" sysfs attribute holds the MAC address for
|
||||
the out-of-band 1Gbps Ethernet port. This MAC address is
|
||||
provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/opn
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "opn" sysfs attribute holds the board's part number.
|
||||
This value is provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/sku
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "sku" sysfs attribute holds the board's SKU number.
|
||||
This value is provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/modl
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "modl" sysfs attribute holds the board's model number.
|
||||
This value is provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/sn
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "sn" sysfs attribute holds the board's serial number.
|
||||
This value is provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/uuid
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "uuid" sysfs attribute holds the board's UUID.
|
||||
This value is provided by the manufacturing team.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/rev
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "rev" sysfs attribute holds the board's revision.
|
||||
This value is provided on a board-level label.
|
||||
|
||||
What: /sys/bus/platform/devices/MLNXBF04:00/mfg_lock
|
||||
Date: August 2023
|
||||
KernelVersion: 6.5
|
||||
Contact: "David Thompson <davthompson@nvidia.com>"
|
||||
Description:
|
||||
The "mfg_lock" sysfs attribute is write-only.
|
||||
A successful write to this attribute will latch the
|
||||
board-level attributes into EEPROM, making them read-only.
|
||||
|
|
41
MAINTAINERS
41
MAINTAINERS
|
@ -1018,7 +1018,7 @@ AMD PMC DRIVER
|
|||
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/amd/pmc.c
|
||||
F: drivers/platform/x86/amd/pmc/
|
||||
|
||||
AMD PMF DRIVER
|
||||
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
|
@ -9522,6 +9522,12 @@ S: Obsolete
|
|||
W: http://w1.fi/hostap-driver.html
|
||||
F: drivers/net/wireless/intersil/hostap/
|
||||
|
||||
HP BIOSCFG DRIVER
|
||||
M: Jorge Lopez <jorge.lopez2@hp.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/hp/hp-bioscfg/
|
||||
|
||||
HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Orphan
|
||||
|
@ -10802,6 +10808,7 @@ INTEL TPMI DRIVER
|
|||
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: include/linux/intel_tpmi.h
|
||||
|
||||
|
@ -19496,6 +19503,32 @@ F: drivers/media/mmc/siano/
|
|||
F: drivers/media/usb/siano/
|
||||
F: drivers/media/usb/siano/
|
||||
|
||||
SIEMENS IPC LED DRIVERS
|
||||
M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
M: Xing Tong Wu <xingtong.wu@siemens.com>
|
||||
M: Tobias Schaffner <tobias.schaffner@siemens.com>
|
||||
L: linux-leds@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/leds/simple/
|
||||
|
||||
SIEMENS IPC PLATFORM DRIVERS
|
||||
M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
M: Xing Tong Wu <xingtong.wu@siemens.com>
|
||||
M: Tobias Schaffner <tobias.schaffner@siemens.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/siemens/
|
||||
F: include/linux/platform_data/x86/simatic-ipc-base.h
|
||||
F: include/linux/platform_data/x86/simatic-ipc.h
|
||||
|
||||
SIEMENS IPC WATCHDOG DRIVERS
|
||||
M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
M: Xing Tong Wu <xingtong.wu@siemens.com>
|
||||
M: Tobias Schaffner <tobias.schaffner@siemens.com>
|
||||
L: linux-watchdog@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/watchdog/simatic-ipc-wdt.c
|
||||
|
||||
SIFIVE DRIVERS
|
||||
M: Palmer Dabbelt <palmer@dabbelt.com>
|
||||
M: Paul Walmsley <paul.walmsley@sifive.com>
|
||||
|
@ -23112,8 +23145,10 @@ S: Orphan
|
|||
F: drivers/net/wireless/legacy/wl3501*
|
||||
|
||||
WMI BINARY MOF DRIVER
|
||||
L: platform-drivers-x86@vger.kernel.org
|
||||
S: Orphan
|
||||
M: Armin Wolf <W_Armin@gmx.de>
|
||||
R: Thomas Weißschuh <linux@weissschuh.net>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/stable/sysfs-platform-wmi-bmof
|
||||
F: Documentation/wmi/devices/wmi-bmof.rst
|
||||
F: drivers/platform/x86/wmi-bmof.c
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
config LEDS_SIEMENS_SIMATIC_IPC
|
||||
tristate "LED driver for Siemens Simatic IPCs"
|
||||
depends on SIEMENS_SIMATIC_IPC
|
||||
default y
|
||||
help
|
||||
This option enables support for the LEDs of several Industrial PCs
|
||||
from Siemens.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/cros_ec_commands.h>
|
||||
#include <linux/platform_data/cros_ec_proto.h>
|
||||
#include <linux/slab.h>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
@ -79,6 +80,52 @@ static void __iomem *mlxbf_rsh_scratch_buf_data;
|
|||
static const char * const mlxbf_rsh_log_level[] = {
|
||||
"INFO", "WARN", "ERR", "ASSERT"};
|
||||
|
||||
static DEFINE_MUTEX(icm_ops_lock);
|
||||
static DEFINE_MUTEX(os_up_lock);
|
||||
static DEFINE_MUTEX(mfg_ops_lock);
|
||||
|
||||
/*
|
||||
* Objects are stored within the MFG partition per type.
|
||||
* Type 0 is not supported.
|
||||
*/
|
||||
enum {
|
||||
MLNX_MFG_TYPE_OOB_MAC = 1,
|
||||
MLNX_MFG_TYPE_OPN_0,
|
||||
MLNX_MFG_TYPE_OPN_1,
|
||||
MLNX_MFG_TYPE_OPN_2,
|
||||
MLNX_MFG_TYPE_SKU_0,
|
||||
MLNX_MFG_TYPE_SKU_1,
|
||||
MLNX_MFG_TYPE_SKU_2,
|
||||
MLNX_MFG_TYPE_MODL_0,
|
||||
MLNX_MFG_TYPE_MODL_1,
|
||||
MLNX_MFG_TYPE_MODL_2,
|
||||
MLNX_MFG_TYPE_SN_0,
|
||||
MLNX_MFG_TYPE_SN_1,
|
||||
MLNX_MFG_TYPE_SN_2,
|
||||
MLNX_MFG_TYPE_UUID_0,
|
||||
MLNX_MFG_TYPE_UUID_1,
|
||||
MLNX_MFG_TYPE_UUID_2,
|
||||
MLNX_MFG_TYPE_UUID_3,
|
||||
MLNX_MFG_TYPE_UUID_4,
|
||||
MLNX_MFG_TYPE_REV,
|
||||
};
|
||||
|
||||
#define MLNX_MFG_OPN_VAL_LEN 24
|
||||
#define MLNX_MFG_SKU_VAL_LEN 24
|
||||
#define MLNX_MFG_MODL_VAL_LEN 24
|
||||
#define MLNX_MFG_SN_VAL_LEN 24
|
||||
#define MLNX_MFG_UUID_VAL_LEN 40
|
||||
#define MLNX_MFG_REV_VAL_LEN 8
|
||||
#define MLNX_MFG_VAL_QWORD_CNT(type) \
|
||||
(MLNX_MFG_##type##_VAL_LEN / sizeof(u64))
|
||||
|
||||
/*
|
||||
* The MAC address consists of 6 bytes (2 digits each) separated by ':'.
|
||||
* The expected format is: "XX:XX:XX:XX:XX:XX"
|
||||
*/
|
||||
#define MLNX_MFG_OOB_MAC_FORMAT_LEN \
|
||||
((ETH_ALEN * 2) + (ETH_ALEN - 1))
|
||||
|
||||
/* ARM SMC call which is atomic and no need for lock. */
|
||||
static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg)
|
||||
{
|
||||
|
@ -391,6 +438,444 @@ done:
|
|||
return count;
|
||||
}
|
||||
|
||||
static ssize_t large_icm_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
|
||||
mutex_lock(&icm_ops_lock);
|
||||
arm_smccc_smc(MLNX_HANDLE_GET_ICM_INFO, 0, 0, 0, 0,
|
||||
0, 0, 0, &res);
|
||||
mutex_unlock(&icm_ops_lock);
|
||||
if (res.a0)
|
||||
return -EPERM;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "0x%lx", res.a1);
|
||||
}
|
||||
|
||||
static ssize_t large_icm_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
unsigned long icm_data;
|
||||
int err;
|
||||
|
||||
err = kstrtoul(buf, MLXBF_LARGE_ICMC_MAX_STRING_SIZE, &icm_data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if ((icm_data != 0 && icm_data < MLXBF_LARGE_ICMC_SIZE_MIN) ||
|
||||
icm_data > MLXBF_LARGE_ICMC_SIZE_MAX || icm_data % MLXBF_LARGE_ICMC_GRANULARITY)
|
||||
return -EPERM;
|
||||
|
||||
mutex_lock(&icm_ops_lock);
|
||||
arm_smccc_smc(MLNX_HANDLE_SET_ICM_INFO, icm_data, 0, 0, 0, 0, 0, 0, &res);
|
||||
mutex_unlock(&icm_ops_lock);
|
||||
|
||||
return res.a0 ? -EPERM : count;
|
||||
}
|
||||
|
||||
static ssize_t os_up_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
unsigned long val;
|
||||
int err;
|
||||
|
||||
err = kstrtoul(buf, 10, &val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&os_up_lock);
|
||||
arm_smccc_smc(MLNX_HANDLE_OS_UP, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
mutex_unlock(&os_up_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t oob_mac_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
u8 *mac_byte_ptr;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC, 0, 0, 0,
|
||||
0, 0, 0, &res);
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
if (res.a0)
|
||||
return -EPERM;
|
||||
|
||||
mac_byte_ptr = (u8 *)&res.a1;
|
||||
|
||||
return sysfs_format_mac(buf, mac_byte_ptr, ETH_ALEN);
|
||||
}
|
||||
|
||||
static ssize_t oob_mac_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned int byte[MLNX_MFG_OOB_MAC_FORMAT_LEN] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int byte_idx, len;
|
||||
u64 mac_addr = 0;
|
||||
u8 *mac_byte_ptr;
|
||||
|
||||
if ((count - 1) != MLNX_MFG_OOB_MAC_FORMAT_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
len = sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
&byte[0], &byte[1], &byte[2],
|
||||
&byte[3], &byte[4], &byte[5]);
|
||||
if (len != ETH_ALEN)
|
||||
return -EINVAL;
|
||||
|
||||
mac_byte_ptr = (u8 *)&mac_addr;
|
||||
|
||||
for (byte_idx = 0; byte_idx < ETH_ALEN; byte_idx++)
|
||||
mac_byte_ptr[byte_idx] = (u8)byte[byte_idx];
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC,
|
||||
ETH_ALEN, mac_addr, 0, 0, 0, 0, &res);
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return res.a0 ? -EPERM : count;
|
||||
}
|
||||
|
||||
static ssize_t opn_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 opn_data[MLNX_MFG_VAL_QWORD_CNT(OPN) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_OPN_0 + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
opn_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)opn_data);
|
||||
}
|
||||
|
||||
static ssize_t opn_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 opn[MLNX_MFG_VAL_QWORD_CNT(OPN)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_OPN_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(opn, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_OPN_0 + word,
|
||||
sizeof(u64), opn[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t sku_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 sku_data[MLNX_MFG_VAL_QWORD_CNT(SKU) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_SKU_0 + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
sku_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)sku_data);
|
||||
}
|
||||
|
||||
static ssize_t sku_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 sku[MLNX_MFG_VAL_QWORD_CNT(SKU)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_SKU_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(sku, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_SKU_0 + word,
|
||||
sizeof(u64), sku[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t modl_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 modl_data[MLNX_MFG_VAL_QWORD_CNT(MODL) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_MODL_0 + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
modl_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)modl_data);
|
||||
}
|
||||
|
||||
static ssize_t modl_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 modl[MLNX_MFG_VAL_QWORD_CNT(MODL)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_MODL_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(modl, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_MODL_0 + word,
|
||||
sizeof(u64), modl[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t sn_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 sn_data[MLNX_MFG_VAL_QWORD_CNT(SN) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_SN_0 + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
sn_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)sn_data);
|
||||
}
|
||||
|
||||
static ssize_t sn_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 sn[MLNX_MFG_VAL_QWORD_CNT(SN)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_SN_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(sn, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_SN_0 + word,
|
||||
sizeof(u64), sn[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t uuid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 uuid_data[MLNX_MFG_VAL_QWORD_CNT(UUID) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_UUID_0 + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
uuid_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)uuid_data);
|
||||
}
|
||||
|
||||
static ssize_t uuid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 uuid[MLNX_MFG_VAL_QWORD_CNT(UUID)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_UUID_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(uuid, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_UUID_0 + word,
|
||||
sizeof(u64), uuid[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t rev_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u64 rev_data[MLNX_MFG_VAL_QWORD_CNT(REV) + 1] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_REV + word,
|
||||
0, 0, 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
rev_data[word] = res.a1;
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s", (char *)rev_data);
|
||||
}
|
||||
|
||||
static ssize_t rev_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u64 rev[MLNX_MFG_VAL_QWORD_CNT(REV)] = { 0 };
|
||||
struct arm_smccc_res res;
|
||||
int word;
|
||||
|
||||
if (count > MLNX_MFG_REV_VAL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(rev, buf, count);
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) {
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
|
||||
MLNX_MFG_TYPE_REV + word,
|
||||
sizeof(u64), rev[word], 0, 0, 0, 0, &res);
|
||||
if (res.a0) {
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t mfg_lock_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
unsigned long val;
|
||||
int err;
|
||||
|
||||
err = kstrtoul(buf, 10, &val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mfg_ops_lock);
|
||||
arm_smccc_smc(MLXBF_BOOTCTL_LOCK_MFG_INFO, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
mutex_unlock(&mfg_ops_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(post_reset_wdog);
|
||||
static DEVICE_ATTR_RW(reset_action);
|
||||
static DEVICE_ATTR_RW(second_reset_action);
|
||||
|
@ -398,6 +883,16 @@ static DEVICE_ATTR_RO(lifecycle_state);
|
|||
static DEVICE_ATTR_RO(secure_boot_fuse_state);
|
||||
static DEVICE_ATTR_WO(fw_reset);
|
||||
static DEVICE_ATTR_WO(rsh_log);
|
||||
static DEVICE_ATTR_RW(large_icm);
|
||||
static DEVICE_ATTR_WO(os_up);
|
||||
static DEVICE_ATTR_RW(oob_mac);
|
||||
static DEVICE_ATTR_RW(opn);
|
||||
static DEVICE_ATTR_RW(sku);
|
||||
static DEVICE_ATTR_RW(modl);
|
||||
static DEVICE_ATTR_RW(sn);
|
||||
static DEVICE_ATTR_RW(uuid);
|
||||
static DEVICE_ATTR_RW(rev);
|
||||
static DEVICE_ATTR_WO(mfg_lock);
|
||||
|
||||
static struct attribute *mlxbf_bootctl_attrs[] = {
|
||||
&dev_attr_post_reset_wdog.attr,
|
||||
|
@ -407,6 +902,16 @@ static struct attribute *mlxbf_bootctl_attrs[] = {
|
|||
&dev_attr_secure_boot_fuse_state.attr,
|
||||
&dev_attr_fw_reset.attr,
|
||||
&dev_attr_rsh_log.attr,
|
||||
&dev_attr_large_icm.attr,
|
||||
&dev_attr_os_up.attr,
|
||||
&dev_attr_oob_mac.attr,
|
||||
&dev_attr_opn.attr,
|
||||
&dev_attr_sku.attr,
|
||||
&dev_attr_modl.attr,
|
||||
&dev_attr_sn.attr,
|
||||
&dev_attr_uuid.attr,
|
||||
&dev_attr_rev.attr,
|
||||
&dev_attr_mfg_lock.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
|
@ -81,6 +81,28 @@
|
|||
*/
|
||||
#define MLXBF_BOOTCTL_FW_RESET 0x8200000D
|
||||
|
||||
/*
|
||||
* SMC function IDs to set, get and lock the manufacturing information
|
||||
* stored within the eeprom.
|
||||
*/
|
||||
#define MLXBF_BOOTCTL_SET_MFG_INFO 0x8200000E
|
||||
#define MLXBF_BOOTCTL_GET_MFG_INFO 0x8200000F
|
||||
#define MLXBF_BOOTCTL_LOCK_MFG_INFO 0x82000011
|
||||
|
||||
/*
|
||||
* SMC function IDs to set and get the large ICM carveout size
|
||||
* stored in the eeprom.
|
||||
*/
|
||||
#define MLNX_HANDLE_SET_ICM_INFO 0x82000012
|
||||
#define MLNX_HANDLE_GET_ICM_INFO 0x82000013
|
||||
|
||||
#define MAX_ICM_BUFFER_SIZE 10
|
||||
|
||||
/*
|
||||
* SMC function ID to set the ARM boot state to up
|
||||
*/
|
||||
#define MLNX_HANDLE_OS_UP 0x82000014
|
||||
|
||||
/* SMC function IDs for SiP Service queries */
|
||||
#define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00
|
||||
#define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01
|
||||
|
@ -106,4 +128,9 @@
|
|||
/* Additional value to disable the MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. */
|
||||
#define MLXBF_BOOTCTL_NONE 0x7fffffff /* Don't change next boot action */
|
||||
|
||||
#define MLXBF_LARGE_ICMC_MAX_STRING_SIZE 16
|
||||
#define MLXBF_LARGE_ICMC_SIZE_MIN 0x80
|
||||
#define MLXBF_LARGE_ICMC_SIZE_MAX 0x100000
|
||||
#define MLXBF_LARGE_ICMC_GRANULARITY MLXBF_LARGE_ICMC_SIZE_MIN
|
||||
|
||||
#endif /* __MLXBF_BOOTCTL_H__ */
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_data/mlxreg.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
@ -113,7 +112,7 @@ static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv,
|
|||
* Return if adapter number is negative. It could be in case hotplug
|
||||
* event is not associated with hotplug device.
|
||||
*/
|
||||
if (data->hpdev.nr < 0)
|
||||
if (data->hpdev.nr < 0 && data->hpdev.action != MLXREG_HOTPLUG_DEVICE_NO_ACTION)
|
||||
return 0;
|
||||
|
||||
pdata = dev_get_platdata(&priv->pdev->dev);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_data/mlxreg.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
|
|
@ -84,6 +84,10 @@
|
|||
#define NVSW_SN2201_MAIN_MUX_CH5_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 5)
|
||||
#define NVSW_SN2201_MAIN_MUX_CH6_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 6)
|
||||
#define NVSW_SN2201_MAIN_MUX_CH7_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 7)
|
||||
#define NVSW_SN2201_2ND_MUX_CH0_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 1)
|
||||
#define NVSW_SN2201_2ND_MUX_CH1_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 2)
|
||||
#define NVSW_SN2201_2ND_MUX_CH2_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 3)
|
||||
#define NVSW_SN2201_2ND_MUX_CH3_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 4)
|
||||
|
||||
#define NVSW_SN2201_CPLD_NR NVSW_SN2201_MAIN_MUX_CH0_NR
|
||||
#define NVSW_SN2201_NR_NONE -1
|
||||
|
@ -425,28 +429,28 @@ static struct mlxreg_core_data nvsw_sn2201_fan_items_data[] = {
|
|||
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
|
||||
.mask = BIT(0),
|
||||
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[0],
|
||||
.hpdev.nr = NVSW_SN2201_NR_NONE,
|
||||
.hpdev.nr = NVSW_SN2201_2ND_MUX_CH0_NR,
|
||||
},
|
||||
{
|
||||
.label = "fan2",
|
||||
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
|
||||
.mask = BIT(1),
|
||||
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[1],
|
||||
.hpdev.nr = NVSW_SN2201_NR_NONE,
|
||||
.hpdev.nr = NVSW_SN2201_2ND_MUX_CH1_NR,
|
||||
},
|
||||
{
|
||||
.label = "fan3",
|
||||
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
|
||||
.mask = BIT(2),
|
||||
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[2],
|
||||
.hpdev.nr = NVSW_SN2201_NR_NONE,
|
||||
.hpdev.nr = NVSW_SN2201_2ND_MUX_CH2_NR,
|
||||
},
|
||||
{
|
||||
.label = "fan4",
|
||||
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
|
||||
.mask = BIT(3),
|
||||
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[3],
|
||||
.hpdev.nr = NVSW_SN2201_NR_NONE,
|
||||
.hpdev.nr = NVSW_SN2201_2ND_MUX_CH3_NR,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -965,7 +965,7 @@ config SERIAL_MULTI_INSTANTIATE
|
|||
|
||||
config MLX_PLATFORM
|
||||
tristate "Mellanox Technologies platform support"
|
||||
depends on I2C
|
||||
depends on ACPI && I2C && PCI
|
||||
select REGMAP
|
||||
help
|
||||
This option enables system support for the Mellanox Technologies
|
||||
|
@ -1074,17 +1074,7 @@ config INTEL_SCU_IPC_UTIL
|
|||
low level access for debug work and updating the firmware. Say
|
||||
N unless you will be doing this on an Intel MID platform.
|
||||
|
||||
config SIEMENS_SIMATIC_IPC
|
||||
tristate "Siemens Simatic IPC Class driver"
|
||||
depends on PCI
|
||||
help
|
||||
This Simatic IPC class driver is the central of several drivers. It
|
||||
is mainly used for system identification, after which drivers in other
|
||||
classes will take care of driving specifics of those machines.
|
||||
i.e. LEDs and watchdog.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc.
|
||||
source "drivers/platform/x86/siemens/Kconfig"
|
||||
|
||||
config WINMATE_FM07_KEYS
|
||||
tristate "Winmate FM07/FM07P front-panel keys driver"
|
||||
|
@ -1094,10 +1084,25 @@ config WINMATE_FM07_KEYS
|
|||
buttons below the display. This module adds an input device
|
||||
that delivers key events when these buttons are pressed.
|
||||
|
||||
config SEL3350_PLATFORM
|
||||
tristate "SEL-3350 LEDs and power supplies"
|
||||
depends on ACPI
|
||||
depends on GPIOLIB
|
||||
depends on PINCTRL_BROXTON
|
||||
select POWER_SUPPLY
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
select LEDS_GPIO
|
||||
help
|
||||
Support for LEDs and power supplies on SEL-3350 computers.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called sel3350-platform.
|
||||
|
||||
endif # X86_PLATFORM_DEVICES
|
||||
|
||||
config P2SB
|
||||
bool "Primary to Sideband (P2SB) bridge access support"
|
||||
bool
|
||||
depends on PCI && X86
|
||||
help
|
||||
The Primary to Sideband (P2SB) bridge is an interface to some
|
||||
|
|
|
@ -131,7 +131,10 @@ obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
|
|||
obj-$(CONFIG_X86_INTEL_LPSS) += pmc_atom.o
|
||||
|
||||
# Siemens Simatic Industrial PCs
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += siemens/
|
||||
|
||||
# Winmate
|
||||
obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o
|
||||
|
||||
# SEL
|
||||
obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o
|
||||
|
|
|
@ -4,21 +4,7 @@
|
|||
#
|
||||
|
||||
source "drivers/platform/x86/amd/pmf/Kconfig"
|
||||
|
||||
config AMD_PMC
|
||||
tristate "AMD SoC PMC driver"
|
||||
depends on ACPI && PCI && RTC_CLASS && AMD_NB
|
||||
select SERIO
|
||||
help
|
||||
The driver provides support for AMD Power Management Controller
|
||||
primarily responsible for S2Idle transactions that are driven from
|
||||
a platform firmware running on SMU. This driver also provides a debug
|
||||
mechanism to investigate the S2Idle transactions and failures.
|
||||
|
||||
Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd-pmc.
|
||||
source "drivers/platform/x86/amd/pmc/Kconfig"
|
||||
|
||||
config AMD_HSMP
|
||||
tristate "AMD HSMP Driver"
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
# AMD x86 Platform-Specific Drivers
|
||||
#
|
||||
|
||||
amd-pmc-y := pmc.o pmc-quirks.o
|
||||
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
|
||||
obj-$(CONFIG_AMD_PMC) += pmc/
|
||||
amd_hsmp-y := hsmp.o
|
||||
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
|
||||
obj-$(CONFIG_AMD_PMF) += pmf/
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# AMD PMC Driver
|
||||
#
|
||||
|
||||
config AMD_PMC
|
||||
tristate "AMD SoC PMC driver"
|
||||
depends on ACPI && PCI && RTC_CLASS && AMD_NB
|
||||
depends on SUSPEND
|
||||
select SERIO
|
||||
help
|
||||
The driver provides support for AMD Power Management Controller
|
||||
primarily responsible for S2Idle transactions that are driven from
|
||||
a platform firmware running on SMU. This driver also provides a debug
|
||||
mechanism to investigate the S2Idle transactions and failures.
|
||||
|
||||
Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd-pmc.
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for linux/drivers/platform/x86/amd/pmc
|
||||
# AMD Power Management Controller Driver
|
||||
#
|
||||
|
||||
amd-pmc-objs := pmc.o pmc-quirks.o
|
||||
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
|
|
@ -8,6 +8,7 @@
|
|||
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "pmf.h"
|
||||
|
||||
|
@ -399,7 +400,7 @@ static ssize_t cnqf_enable_store(struct device *dev,
|
|||
amd_pmf_set_sps_power_limits(pdev);
|
||||
}
|
||||
|
||||
dev_dbg(pdev->dev, "Received CnQF %s\n", input ? "on" : "off");
|
||||
dev_dbg(pdev->dev, "Received CnQF %s\n", str_on_off(input));
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -409,7 +410,7 @@ static ssize_t cnqf_enable_show(struct device *dev,
|
|||
{
|
||||
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", pdev->cnqf_enabled ? "on" : "off");
|
||||
return sysfs_emit(buf, "%s\n", str_on_off(pdev->cnqf_enabled));
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(cnqf_enable);
|
||||
|
|
|
@ -324,7 +324,8 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
|
|||
|
||||
static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
|
||||
{
|
||||
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
|
||||
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR) ||
|
||||
is_apmf_func_supported(dev, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
|
||||
power_supply_unreg_notifier(&dev->pwr_src_notifier);
|
||||
amd_pmf_deinit_sps(dev);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ module_param(fnlock_default, bool, 0444);
|
|||
|
||||
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
|
||||
|
||||
#define ASUS_MID_FAN_DESC "mid_fan"
|
||||
#define ASUS_GPU_FAN_DESC "gpu_fan"
|
||||
#define ASUS_FAN_DESC "cpu_fan"
|
||||
#define ASUS_FAN_MFUN 0x13
|
||||
|
@ -112,9 +113,20 @@ module_param(fnlock_default, bool, 0444);
|
|||
#define FAN_CURVE_BUF_LEN 32
|
||||
#define FAN_CURVE_DEV_CPU 0x00
|
||||
#define FAN_CURVE_DEV_GPU 0x01
|
||||
#define FAN_CURVE_DEV_MID 0x02
|
||||
/* Mask to determine if setting temperature or percentage */
|
||||
#define FAN_CURVE_PWM_MASK 0x04
|
||||
|
||||
/* Limits for tunables available on ASUS ROG laptops */
|
||||
#define PPT_TOTAL_MIN 5
|
||||
#define PPT_TOTAL_MAX 250
|
||||
#define PPT_CPU_MIN 5
|
||||
#define PPT_CPU_MAX 130
|
||||
#define NVIDIA_BOOST_MIN 5
|
||||
#define NVIDIA_BOOST_MAX 25
|
||||
#define NVIDIA_TEMP_MIN 75
|
||||
#define NVIDIA_TEMP_MAX 87
|
||||
|
||||
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
|
||||
|
||||
static int throttle_thermal_policy_write(struct asus_wmi *);
|
||||
|
@ -229,18 +241,31 @@ struct asus_wmi {
|
|||
|
||||
enum fan_type fan_type;
|
||||
enum fan_type gpu_fan_type;
|
||||
enum fan_type mid_fan_type;
|
||||
int fan_pwm_mode;
|
||||
int gpu_fan_pwm_mode;
|
||||
int mid_fan_pwm_mode;
|
||||
int agfn_pwm;
|
||||
|
||||
bool fan_boost_mode_available;
|
||||
u8 fan_boost_mode_mask;
|
||||
u8 fan_boost_mode;
|
||||
|
||||
bool charge_mode_available;
|
||||
bool egpu_enable_available;
|
||||
bool egpu_connect_available;
|
||||
bool dgpu_disable_available;
|
||||
bool gpu_mux_mode_available;
|
||||
|
||||
/* Tunables provided by ASUS for gaming laptops */
|
||||
bool ppt_pl2_sppt_available;
|
||||
bool ppt_pl1_spl_available;
|
||||
bool ppt_apu_sppt_available;
|
||||
bool ppt_plat_sppt_available;
|
||||
bool ppt_fppt_available;
|
||||
bool nv_dyn_boost_available;
|
||||
bool nv_temp_tgt_available;
|
||||
|
||||
bool kbd_rgb_mode_available;
|
||||
bool kbd_rgb_state_available;
|
||||
|
||||
|
@ -249,7 +274,8 @@ struct asus_wmi {
|
|||
|
||||
bool cpu_fan_curve_available;
|
||||
bool gpu_fan_curve_available;
|
||||
struct fan_curve_data custom_fan_curves[2];
|
||||
bool mid_fan_curve_available;
|
||||
struct fan_curve_data custom_fan_curves[3];
|
||||
|
||||
struct platform_profile_handler platform_profile_handler;
|
||||
bool platform_profile_support;
|
||||
|
@ -258,6 +284,7 @@ struct asus_wmi {
|
|||
bool battery_rsoc_available;
|
||||
|
||||
bool panel_overdrive_available;
|
||||
bool mini_led_mode_available;
|
||||
|
||||
struct hotplug_slot hotplug_slot;
|
||||
struct mutex hotplug_lock;
|
||||
|
@ -586,6 +613,22 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
|
|||
asus_wmi_tablet_sw_report(asus, result);
|
||||
}
|
||||
|
||||
/* Charging mode, 1=Barrel, 2=USB ******************************************/
|
||||
static ssize_t charge_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int result, value;
|
||||
|
||||
result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", value & 0xff);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(charge_mode);
|
||||
|
||||
/* dGPU ********************************************************************/
|
||||
static ssize_t dgpu_disable_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
|
@ -622,6 +665,18 @@ static ssize_t dgpu_disable_store(struct device *dev,
|
|||
if (disable > 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (asus->gpu_mux_mode_available) {
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
|
||||
if (result < 0)
|
||||
/* An error here may signal greater failure of GPU handling */
|
||||
return result;
|
||||
if (!result && disable) {
|
||||
err = -ENODEV;
|
||||
pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set dgpu disable: %d\n", err);
|
||||
|
@ -670,14 +725,34 @@ static ssize_t egpu_enable_store(struct device *dev,
|
|||
if (enable > 1)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
|
||||
if (err < 0) {
|
||||
pr_warn("Failed to get egpu connection status: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (asus->gpu_mux_mode_available) {
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
|
||||
if (result < 0) {
|
||||
/* An error here may signal greater failure of GPU handling */
|
||||
pr_warn("Failed to get gpu mux status: %d\n", result);
|
||||
return result;
|
||||
}
|
||||
if (!result && enable) {
|
||||
err = -ENODEV;
|
||||
pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set egpu disable: %d\n", err);
|
||||
pr_warn("Failed to set egpu state: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set egpu disable (retval): 0x%x\n", result);
|
||||
pr_warn("Failed to set egpu state (retval): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
@ -687,6 +762,22 @@ static ssize_t egpu_enable_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RW(egpu_enable);
|
||||
|
||||
/* Is eGPU connected? *********************************************************/
|
||||
static ssize_t egpu_connected_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int result;
|
||||
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", result);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(egpu_connected);
|
||||
|
||||
/* gpu mux switch *************************************************************/
|
||||
static ssize_t gpu_mux_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
|
@ -716,6 +807,30 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
|
|||
if (optimus > 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (asus->dgpu_disable_available) {
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
|
||||
if (result < 0)
|
||||
/* An error here may signal greater failure of GPU handling */
|
||||
return result;
|
||||
if (result && !optimus) {
|
||||
err = -ENODEV;
|
||||
pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (asus->egpu_enable_available) {
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
|
||||
if (result < 0)
|
||||
/* An error here may signal greater failure of GPU handling */
|
||||
return result;
|
||||
if (result && !optimus) {
|
||||
err = -ENODEV;
|
||||
pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
|
||||
|
@ -859,6 +974,244 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
|
||||
static ssize_t ppt_pl2_sppt_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set ppt_pl2_sppt: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ppt_pl2_sppt);
|
||||
|
||||
/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
|
||||
static ssize_t ppt_pl1_spl_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set ppt_pl1_spl: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ppt_pl1_spl);
|
||||
|
||||
/* Tunable: PPT APU FPPT ******************************************************/
|
||||
static ssize_t ppt_fppt_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set ppt_fppt: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ppt_fppt);
|
||||
|
||||
/* Tunable: PPT APU SPPT *****************************************************/
|
||||
static ssize_t ppt_apu_sppt_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set ppt_apu_sppt: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ppt_apu_sppt);
|
||||
|
||||
/* Tunable: PPT platform SPPT ************************************************/
|
||||
static ssize_t ppt_platform_sppt_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set ppt_platform_sppt: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(ppt_platform_sppt);
|
||||
|
||||
/* Tunable: NVIDIA dynamic boost *********************************************/
|
||||
static ssize_t nv_dynamic_boost_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set nv_dynamic_boost: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(nv_dynamic_boost);
|
||||
|
||||
/* Tunable: NVIDIA temperature target ****************************************/
|
||||
static ssize_t nv_temp_target_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 value;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &value);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result);
|
||||
if (err) {
|
||||
pr_warn("Failed to set nv_temp_target: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_WO(nv_temp_target);
|
||||
|
||||
/* Battery ********************************************************************/
|
||||
|
||||
/* The battery maximum charging percentage */
|
||||
|
@ -1734,6 +2087,54 @@ static ssize_t panel_od_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RW(panel_od);
|
||||
|
||||
/* Mini-LED mode **************************************************************/
|
||||
static ssize_t mini_led_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int result;
|
||||
|
||||
result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", result);
|
||||
}
|
||||
|
||||
static ssize_t mini_led_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result, err;
|
||||
u32 mode;
|
||||
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtou32(buf, 10, &mode);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (mode > 1)
|
||||
return -EINVAL;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result);
|
||||
|
||||
if (err) {
|
||||
pr_warn("Failed to set mini-LED: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result > 1) {
|
||||
pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode");
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(mini_led_mode);
|
||||
|
||||
/* Quirks *********************************************************************/
|
||||
|
||||
static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
|
||||
|
@ -2070,6 +2471,8 @@ static ssize_t pwm1_enable_store(struct device *dev,
|
|||
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
|
||||
if (asus->gpu_fan_curve_available)
|
||||
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
|
||||
if (asus->mid_fan_curve_available)
|
||||
asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -2122,6 +2525,31 @@ static ssize_t fan2_label_show(struct device *dev,
|
|||
return sysfs_emit(buf, "%s\n", ASUS_GPU_FAN_DESC);
|
||||
}
|
||||
|
||||
/* Middle/Center fan on modern ROG laptops */
|
||||
static ssize_t fan3_input_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int value;
|
||||
int ret;
|
||||
|
||||
ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_MID_FAN_CTRL, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
value &= 0xffff;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", value * 100);
|
||||
}
|
||||
|
||||
static ssize_t fan3_label_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", ASUS_MID_FAN_DESC);
|
||||
}
|
||||
|
||||
static ssize_t pwm2_enable_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -2168,6 +2596,52 @@ static ssize_t pwm2_enable_store(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static ssize_t pwm3_enable_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", asus->mid_fan_pwm_mode);
|
||||
}
|
||||
|
||||
static ssize_t pwm3_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
int state;
|
||||
int value;
|
||||
int ret;
|
||||
u32 retval;
|
||||
|
||||
ret = kstrtouint(buf, 10, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (state) { /* standard documented hwmon values */
|
||||
case ASUS_FAN_CTRL_FULLSPEED:
|
||||
value = 1;
|
||||
break;
|
||||
case ASUS_FAN_CTRL_AUTO:
|
||||
value = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_MID_FAN_CTRL,
|
||||
value, &retval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (retval != 1)
|
||||
return -EIO;
|
||||
|
||||
asus->mid_fan_pwm_mode = state;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Fan1 */
|
||||
static DEVICE_ATTR_RW(pwm1);
|
||||
static DEVICE_ATTR_RW(pwm1_enable);
|
||||
|
@ -2177,6 +2651,10 @@ static DEVICE_ATTR_RO(fan1_label);
|
|||
static DEVICE_ATTR_RW(pwm2_enable);
|
||||
static DEVICE_ATTR_RO(fan2_input);
|
||||
static DEVICE_ATTR_RO(fan2_label);
|
||||
/* Fan3 - Middle/center fan */
|
||||
static DEVICE_ATTR_RW(pwm3_enable);
|
||||
static DEVICE_ATTR_RO(fan3_input);
|
||||
static DEVICE_ATTR_RO(fan3_label);
|
||||
|
||||
/* Temperature */
|
||||
static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
|
||||
|
@ -2185,10 +2663,13 @@ static struct attribute *hwmon_attributes[] = {
|
|||
&dev_attr_pwm1.attr,
|
||||
&dev_attr_pwm1_enable.attr,
|
||||
&dev_attr_pwm2_enable.attr,
|
||||
&dev_attr_pwm3_enable.attr,
|
||||
&dev_attr_fan1_input.attr,
|
||||
&dev_attr_fan1_label.attr,
|
||||
&dev_attr_fan2_input.attr,
|
||||
&dev_attr_fan2_label.attr,
|
||||
&dev_attr_fan3_input.attr,
|
||||
&dev_attr_fan3_label.attr,
|
||||
|
||||
&dev_attr_temp1_input.attr,
|
||||
NULL
|
||||
|
@ -2214,6 +2695,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|
|||
|| attr == &dev_attr_pwm2_enable.attr) {
|
||||
if (asus->gpu_fan_type == FAN_TYPE_NONE)
|
||||
return 0;
|
||||
} else if (attr == &dev_attr_fan3_input.attr
|
||||
|| attr == &dev_attr_fan3_label.attr
|
||||
|| attr == &dev_attr_pwm3_enable.attr) {
|
||||
if (asus->mid_fan_type == FAN_TYPE_NONE)
|
||||
return 0;
|
||||
} else if (attr == &dev_attr_temp1_input.attr) {
|
||||
int err = asus_wmi_get_devstate(asus,
|
||||
ASUS_WMI_DEVID_THERMAL_CTRL,
|
||||
|
@ -2257,6 +2743,7 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
|
|||
static int asus_wmi_fan_init(struct asus_wmi *asus)
|
||||
{
|
||||
asus->gpu_fan_type = FAN_TYPE_NONE;
|
||||
asus->mid_fan_type = FAN_TYPE_NONE;
|
||||
asus->fan_type = FAN_TYPE_NONE;
|
||||
asus->agfn_pwm = -1;
|
||||
|
||||
|
@ -2271,6 +2758,10 @@ static int asus_wmi_fan_init(struct asus_wmi *asus)
|
|||
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL))
|
||||
asus->gpu_fan_type = FAN_TYPE_SPEC83;
|
||||
|
||||
/* Some models also have a center/middle fan */
|
||||
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MID_FAN_CTRL))
|
||||
asus->mid_fan_type = FAN_TYPE_SPEC83;
|
||||
|
||||
if (asus->fan_type == FAN_TYPE_NONE)
|
||||
return -ENODEV;
|
||||
|
||||
|
@ -2418,9 +2909,8 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
|
|||
{
|
||||
struct fan_curve_data *curves;
|
||||
u8 buf[FAN_CURVE_BUF_LEN];
|
||||
int fan_idx = 0;
|
||||
int err, fan_idx;
|
||||
u8 mode = 0;
|
||||
int err;
|
||||
|
||||
if (asus->throttle_thermal_policy_available)
|
||||
mode = asus->throttle_thermal_policy_mode;
|
||||
|
@ -2430,10 +2920,6 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
|
|||
else if (mode == 1)
|
||||
mode = 2;
|
||||
|
||||
if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
|
||||
fan_idx = FAN_CURVE_DEV_GPU;
|
||||
|
||||
curves = &asus->custom_fan_curves[fan_idx];
|
||||
err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf,
|
||||
FAN_CURVE_BUF_LEN);
|
||||
if (err) {
|
||||
|
@ -2441,9 +2927,17 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
fan_curve_copy_from_buf(curves, buf);
|
||||
fan_idx = FAN_CURVE_DEV_CPU;
|
||||
if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
|
||||
fan_idx = FAN_CURVE_DEV_GPU;
|
||||
|
||||
if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE)
|
||||
fan_idx = FAN_CURVE_DEV_MID;
|
||||
|
||||
curves = &asus->custom_fan_curves[fan_idx];
|
||||
curves->device_id = fan_dev;
|
||||
|
||||
fan_curve_copy_from_buf(curves, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2473,7 +2967,7 @@ static struct fan_curve_data *fan_curve_attr_select(struct asus_wmi *asus,
|
|||
{
|
||||
int index = to_sensor_dev_attr(attr)->index;
|
||||
|
||||
return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU];
|
||||
return &asus->custom_fan_curves[index];
|
||||
}
|
||||
|
||||
/* Determine which fan the attribute is for if SENSOR_ATTR_2 */
|
||||
|
@ -2482,7 +2976,7 @@ static struct fan_curve_data *fan_curve_attr_2_select(struct asus_wmi *asus,
|
|||
{
|
||||
int nr = to_sensor_dev_attr_2(attr)->nr;
|
||||
|
||||
return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU];
|
||||
return &asus->custom_fan_curves[nr & ~FAN_CURVE_PWM_MASK];
|
||||
}
|
||||
|
||||
static ssize_t fan_curve_show(struct device *dev,
|
||||
|
@ -2491,13 +2985,13 @@ static ssize_t fan_curve_show(struct device *dev,
|
|||
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
struct fan_curve_data *data;
|
||||
int value, index, nr;
|
||||
int value, pwm, index;
|
||||
|
||||
data = fan_curve_attr_2_select(asus, attr);
|
||||
pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
|
||||
index = dev_attr->index;
|
||||
nr = dev_attr->nr;
|
||||
|
||||
if (nr & FAN_CURVE_PWM_MASK)
|
||||
if (pwm)
|
||||
value = data->percents[index];
|
||||
else
|
||||
value = data->temps[index];
|
||||
|
@ -2540,23 +3034,21 @@ static ssize_t fan_curve_store(struct device *dev,
|
|||
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
struct fan_curve_data *data;
|
||||
int err, pwm, index;
|
||||
u8 value;
|
||||
int err;
|
||||
|
||||
int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
|
||||
int index = dev_attr->index;
|
||||
|
||||
data = fan_curve_attr_2_select(asus, attr);
|
||||
pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
|
||||
index = dev_attr->index;
|
||||
|
||||
err = kstrtou8(buf, 10, &value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (pwm) {
|
||||
if (pwm)
|
||||
data->percents[index] = value;
|
||||
} else {
|
||||
else
|
||||
data->temps[index] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark as disabled so the user has to explicitly enable to apply a
|
||||
|
@ -2669,7 +3161,7 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, fan_curve,
|
|||
FAN_CURVE_DEV_CPU, 7);
|
||||
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0);
|
||||
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve,
|
||||
|
@ -2721,6 +3213,42 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve,
|
|||
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7);
|
||||
|
||||
/* MID */
|
||||
static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_MID);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 0);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 1);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 2);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 3);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 4);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 5);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 6);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve,
|
||||
FAN_CURVE_DEV_MID, 7);
|
||||
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 0);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 1);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 2);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 3);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 4);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 5);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 6);
|
||||
static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve,
|
||||
FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 7);
|
||||
|
||||
static struct attribute *asus_fan_curve_attr[] = {
|
||||
/* CPU */
|
||||
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
||||
|
@ -2758,6 +3286,24 @@ static struct attribute *asus_fan_curve_attr[] = {
|
|||
&sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr,
|
||||
/* MID */
|
||||
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point6_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point7_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point8_temp.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point6_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point7_pwm.dev_attr.attr,
|
||||
&sensor_dev_attr_pwm3_auto_point8_pwm.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -2777,6 +3323,9 @@ static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
|
|||
if (asus->gpu_fan_curve_available && attr->name[3] == '2')
|
||||
return 0644;
|
||||
|
||||
if (asus->mid_fan_curve_available && attr->name[3] == '3')
|
||||
return 0644;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2806,7 +3355,14 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available)
|
||||
err = fan_curve_check_present(asus, &asus->mid_fan_curve_available,
|
||||
ASUS_WMI_DEVID_MID_FAN_CURVE);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!asus->cpu_fan_curve_available
|
||||
&& !asus->gpu_fan_curve_available
|
||||
&& !asus->mid_fan_curve_available)
|
||||
return 0;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_groups(
|
||||
|
@ -2875,6 +3431,8 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
|
|||
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
|
||||
if (asus->gpu_fan_curve_available)
|
||||
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
|
||||
if (asus->mid_fan_curve_available)
|
||||
asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -3472,14 +4030,24 @@ static struct attribute *platform_attributes[] = {
|
|||
&dev_attr_camera.attr,
|
||||
&dev_attr_cardr.attr,
|
||||
&dev_attr_touchpad.attr,
|
||||
&dev_attr_charge_mode.attr,
|
||||
&dev_attr_egpu_enable.attr,
|
||||
&dev_attr_egpu_connected.attr,
|
||||
&dev_attr_dgpu_disable.attr,
|
||||
&dev_attr_gpu_mux_mode.attr,
|
||||
&dev_attr_lid_resume.attr,
|
||||
&dev_attr_als_enable.attr,
|
||||
&dev_attr_fan_boost_mode.attr,
|
||||
&dev_attr_throttle_thermal_policy.attr,
|
||||
&dev_attr_ppt_pl2_sppt.attr,
|
||||
&dev_attr_ppt_pl1_spl.attr,
|
||||
&dev_attr_ppt_fppt.attr,
|
||||
&dev_attr_ppt_apu_sppt.attr,
|
||||
&dev_attr_ppt_platform_sppt.attr,
|
||||
&dev_attr_nv_dynamic_boost.attr,
|
||||
&dev_attr_nv_temp_target.attr,
|
||||
&dev_attr_panel_od.attr,
|
||||
&dev_attr_mini_led_mode.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -3501,8 +4069,12 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
|
|||
devid = ASUS_WMI_DEVID_LID_RESUME;
|
||||
else if (attr == &dev_attr_als_enable.attr)
|
||||
devid = ASUS_WMI_DEVID_ALS_ENABLE;
|
||||
else if (attr == &dev_attr_charge_mode.attr)
|
||||
ok = asus->charge_mode_available;
|
||||
else if (attr == &dev_attr_egpu_enable.attr)
|
||||
ok = asus->egpu_enable_available;
|
||||
else if (attr == &dev_attr_egpu_connected.attr)
|
||||
ok = asus->egpu_connect_available;
|
||||
else if (attr == &dev_attr_dgpu_disable.attr)
|
||||
ok = asus->dgpu_disable_available;
|
||||
else if (attr == &dev_attr_gpu_mux_mode.attr)
|
||||
|
@ -3511,8 +4083,24 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
|
|||
ok = asus->fan_boost_mode_available;
|
||||
else if (attr == &dev_attr_throttle_thermal_policy.attr)
|
||||
ok = asus->throttle_thermal_policy_available;
|
||||
else if (attr == &dev_attr_ppt_pl2_sppt.attr)
|
||||
ok = asus->ppt_pl2_sppt_available;
|
||||
else if (attr == &dev_attr_ppt_pl1_spl.attr)
|
||||
ok = asus->ppt_pl1_spl_available;
|
||||
else if (attr == &dev_attr_ppt_fppt.attr)
|
||||
ok = asus->ppt_fppt_available;
|
||||
else if (attr == &dev_attr_ppt_apu_sppt.attr)
|
||||
ok = asus->ppt_apu_sppt_available;
|
||||
else if (attr == &dev_attr_ppt_platform_sppt.attr)
|
||||
ok = asus->ppt_plat_sppt_available;
|
||||
else if (attr == &dev_attr_nv_dynamic_boost.attr)
|
||||
ok = asus->nv_dyn_boost_available;
|
||||
else if (attr == &dev_attr_nv_temp_target.attr)
|
||||
ok = asus->nv_temp_tgt_available;
|
||||
else if (attr == &dev_attr_panel_od.attr)
|
||||
ok = asus->panel_overdrive_available;
|
||||
else if (attr == &dev_attr_mini_led_mode.attr)
|
||||
ok = asus->mini_led_mode_available;
|
||||
|
||||
if (devid != -1)
|
||||
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
|
||||
|
@ -3767,12 +4355,22 @@ static int asus_wmi_add(struct platform_device *pdev)
|
|||
if (err)
|
||||
goto fail_platform;
|
||||
|
||||
asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
|
||||
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
|
||||
asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
|
||||
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
|
||||
asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
|
||||
asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
|
||||
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
|
||||
asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT);
|
||||
asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL);
|
||||
asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT);
|
||||
asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT);
|
||||
asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT);
|
||||
asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST);
|
||||
asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET);
|
||||
asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
|
||||
asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
|
||||
|
||||
err = fan_boost_mode_check_present(asus);
|
||||
if (err)
|
||||
|
|
|
@ -393,6 +393,7 @@ static int init_bios_attributes(int attr_type, const char *guid)
|
|||
struct kobject *attr_name_kobj; //individual attribute names
|
||||
union acpi_object *obj = NULL;
|
||||
union acpi_object *elements;
|
||||
struct kobject *duplicate;
|
||||
struct kset *tmp_set;
|
||||
int min_elements;
|
||||
|
||||
|
@ -451,9 +452,11 @@ static int init_bios_attributes(int attr_type, const char *guid)
|
|||
else
|
||||
tmp_set = wmi_priv.main_dir_kset;
|
||||
|
||||
if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) {
|
||||
pr_debug("duplicate attribute name found - %s\n",
|
||||
elements[ATTR_NAME].string.pointer);
|
||||
duplicate = kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer);
|
||||
if (duplicate) {
|
||||
pr_debug("Duplicate attribute name found - %s\n",
|
||||
elements[ATTR_NAME].string.pointer);
|
||||
kobject_put(duplicate);
|
||||
goto nextobj;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,4 +60,20 @@ config TC1100_WMI
|
|||
This is a driver for the WMI extensions (wireless and bluetooth power
|
||||
control) of the HP Compaq TC1100 tablet.
|
||||
|
||||
config HP_BIOSCFG
|
||||
tristate "HP BIOS Configuration Driver"
|
||||
default m
|
||||
depends on ACPI_WMI
|
||||
select NLS
|
||||
select FW_ATTR_CLASS
|
||||
help
|
||||
This driver enables administrators to securely manage BIOS settings
|
||||
using digital certificates and public-key cryptography that eliminate
|
||||
the need for passwords for both remote and local management. It supports
|
||||
changing BIOS settings on many HP machines from 2018 and newer without
|
||||
the use of any additional software.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called hp-bioscfg.
|
||||
|
||||
endif # X86_PLATFORM_DRIVERS_HP
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
|
||||
obj-$(CONFIG_HP_WMI) += hp-wmi.o
|
||||
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
|
||||
obj-$(CONFIG_HP_BIOSCFG) += hp-bioscfg/
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
obj-$(CONFIG_HP_BIOSCFG) := hp-bioscfg.o
|
||||
|
||||
hp-bioscfg-objs := bioscfg.o \
|
||||
biosattr-interface.o \
|
||||
enum-attributes.o \
|
||||
int-attributes.o \
|
||||
order-list-attributes.o \
|
||||
passwdobj-attributes.o \
|
||||
spmobj-attributes.o \
|
||||
string-attributes.o \
|
||||
surestart-attributes.o
|
|
@ -0,0 +1,312 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to methods under BIOS interface GUID
|
||||
* for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 Hewlett-Packard Inc.
|
||||
*/
|
||||
|
||||
#include <linux/wmi.h>
|
||||
#include "bioscfg.h"
|
||||
|
||||
/*
|
||||
* struct bios_args buffer is dynamically allocated. New WMI command types
|
||||
* were introduced that exceeds 128-byte data size. Changes to handle
|
||||
* the data size allocation scheme were kept in hp_wmi_perform_query function.
|
||||
*/
|
||||
struct bios_args {
|
||||
u32 signature;
|
||||
u32 command;
|
||||
u32 commandtype;
|
||||
u32 datasize;
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
/**
|
||||
* hp_set_attribute
|
||||
*
|
||||
* @a_name: The attribute name
|
||||
* @a_value: The attribute value
|
||||
*
|
||||
* Sets an attribute to new value
|
||||
*
|
||||
* Returns zero on success
|
||||
* -ENODEV if device is not found
|
||||
* -EINVAL if the instance of 'Setup Admin' password is not found.
|
||||
* -ENOMEM unable to allocate memory
|
||||
*/
|
||||
int hp_set_attribute(const char *a_name, const char *a_value)
|
||||
{
|
||||
int security_area_size;
|
||||
int a_name_size, a_value_size;
|
||||
u16 *buffer = NULL;
|
||||
u16 *start;
|
||||
int buffer_size, instance, ret;
|
||||
char *auth_token_choice;
|
||||
|
||||
mutex_lock(&bioscfg_drv.mutex);
|
||||
|
||||
instance = hp_get_password_instance_for_type(SETUP_PASSWD);
|
||||
if (instance < 0) {
|
||||
ret = -EINVAL;
|
||||
goto out_set_attribute;
|
||||
}
|
||||
|
||||
/* Select which auth token to use; password or [auth token] */
|
||||
if (bioscfg_drv.spm_data.auth_token)
|
||||
auth_token_choice = bioscfg_drv.spm_data.auth_token;
|
||||
else
|
||||
auth_token_choice = bioscfg_drv.password_data[instance].current_password;
|
||||
|
||||
a_name_size = hp_calculate_string_buffer(a_name);
|
||||
a_value_size = hp_calculate_string_buffer(a_value);
|
||||
security_area_size = hp_calculate_security_buffer(auth_token_choice);
|
||||
buffer_size = a_name_size + a_value_size + security_area_size;
|
||||
|
||||
buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto out_set_attribute;
|
||||
}
|
||||
|
||||
/* build variables to set */
|
||||
start = buffer;
|
||||
start = hp_ascii_to_utf16_unicode(start, a_name);
|
||||
if (!start) {
|
||||
ret = -EINVAL;
|
||||
goto out_set_attribute;
|
||||
}
|
||||
|
||||
start = hp_ascii_to_utf16_unicode(start, a_value);
|
||||
if (!start) {
|
||||
ret = -EINVAL;
|
||||
goto out_set_attribute;
|
||||
}
|
||||
|
||||
ret = hp_populate_security_buffer(start, auth_token_choice);
|
||||
if (ret < 0)
|
||||
goto out_set_attribute;
|
||||
|
||||
ret = hp_wmi_set_bios_setting(buffer, buffer_size);
|
||||
|
||||
out_set_attribute:
|
||||
kfree(buffer);
|
||||
mutex_unlock(&bioscfg_drv.mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_wmi_perform_query
|
||||
*
|
||||
* @query: The commandtype (enum hp_wmi_commandtype)
|
||||
* @command: The command (enum hp_wmi_command)
|
||||
* @buffer: Buffer used as input and/or output
|
||||
* @insize: Size of input buffer
|
||||
* @outsize: Size of output buffer
|
||||
*
|
||||
* returns zero on success
|
||||
* an HP WMI query specific error code (which is positive)
|
||||
* -EINVAL if the query was not successful at all
|
||||
* -EINVAL if the output buffer size exceeds buffersize
|
||||
*
|
||||
* Note: The buffersize must at least be the maximum of the input and output
|
||||
* size. E.g. Battery info query is defined to have 1 byte input
|
||||
* and 128 byte output. The caller would do:
|
||||
* buffer = kzalloc(128, GFP_KERNEL);
|
||||
* ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
|
||||
* buffer, 1, 128)
|
||||
*/
|
||||
int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
|
||||
u32 insize, u32 outsize)
|
||||
{
|
||||
struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct bios_return *bios_return;
|
||||
union acpi_object *obj = NULL;
|
||||
struct bios_args *args = NULL;
|
||||
int mid, actual_outsize, ret;
|
||||
size_t bios_args_size;
|
||||
|
||||
mid = hp_encode_outsize_for_pvsz(outsize);
|
||||
if (WARN_ON(mid < 0))
|
||||
return mid;
|
||||
|
||||
bios_args_size = struct_size(args, data, insize);
|
||||
args = kmalloc(bios_args_size, GFP_KERNEL);
|
||||
if (!args)
|
||||
return -ENOMEM;
|
||||
|
||||
input.length = bios_args_size;
|
||||
input.pointer = args;
|
||||
|
||||
/* BIOS expects 'SECU' in hex as the signature value*/
|
||||
args->signature = 0x55434553;
|
||||
args->command = command;
|
||||
args->commandtype = query;
|
||||
args->datasize = insize;
|
||||
memcpy(args->data, buffer, flex_array_size(args, data, insize));
|
||||
|
||||
ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj) {
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER ||
|
||||
obj->buffer.length < sizeof(*bios_return)) {
|
||||
pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
bios_return = (struct bios_return *)obj->buffer.pointer;
|
||||
ret = bios_return->return_code;
|
||||
if (ret) {
|
||||
if (ret != INVALID_CMD_VALUE && ret != INVALID_CMD_TYPE)
|
||||
pr_warn("query 0x%x returned error 0x%x\n", query, ret);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* Ignore output data of zero size */
|
||||
if (!outsize)
|
||||
goto out_free;
|
||||
|
||||
actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return));
|
||||
memcpy_and_pad(buffer, outsize, obj->buffer.pointer + sizeof(*bios_return),
|
||||
actual_outsize, 0);
|
||||
|
||||
out_free:
|
||||
ret = hp_wmi_error_and_message(ret);
|
||||
|
||||
kfree(obj);
|
||||
kfree(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *utf16_empty_string(u16 *p)
|
||||
{
|
||||
*p++ = 2;
|
||||
*p++ = 0x00;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode
|
||||
*
|
||||
* BIOS supports UTF-16 characters that are 2 bytes long. No variable
|
||||
* multi-byte language supported.
|
||||
*
|
||||
* @p: Unicode buffer address
|
||||
* @str: string to convert to unicode
|
||||
*
|
||||
* Returns a void pointer to the buffer string
|
||||
*/
|
||||
void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str)
|
||||
{
|
||||
int len = strlen(str);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Add null character when reading an empty string
|
||||
* "02 00 00 00"
|
||||
*/
|
||||
if (len == 0)
|
||||
return utf16_empty_string(p);
|
||||
|
||||
/* Move pointer len * 2 number of bytes */
|
||||
*p++ = len * 2;
|
||||
ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
|
||||
if (ret < 0) {
|
||||
dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ret * sizeof(u16) > U16_MAX) {
|
||||
dev_err(bioscfg_drv.class_dev, "Error string too long\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p += len;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_wmi_set_bios_setting - Set setting's value in BIOS
|
||||
*
|
||||
* @input_buffer: Input buffer address
|
||||
* @input_size: Input buffer size
|
||||
*
|
||||
* Returns: Count of unicode characters written to BIOS if successful, otherwise
|
||||
* -ENOMEM unable to allocate memory
|
||||
* -EINVAL buffer not allocated or too small
|
||||
*/
|
||||
int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
struct acpi_buffer input = {input_size, input_buffer};
|
||||
struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
|
||||
int ret;
|
||||
|
||||
ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj)
|
||||
return -EINVAL;
|
||||
|
||||
if (obj->type != ACPI_TYPE_INTEGER) {
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
ret = obj->integer.value;
|
||||
if (ret) {
|
||||
ret = hp_wmi_error_and_message(ret);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
out_free:
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hp_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
mutex_lock(&bioscfg_drv.mutex);
|
||||
mutex_unlock(&bioscfg_drv.mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hp_attr_set_interface_remove(struct wmi_device *wdev)
|
||||
{
|
||||
mutex_lock(&bioscfg_drv.mutex);
|
||||
mutex_unlock(&bioscfg_drv.mutex);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id hp_attr_set_interface_id_table[] = {
|
||||
{ .guid_string = HP_WMI_BIOS_GUID},
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct wmi_driver hp_attr_set_interface_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = hp_attr_set_interface_probe,
|
||||
.remove = hp_attr_set_interface_remove,
|
||||
.id_table = hp_attr_set_interface_id_table,
|
||||
};
|
||||
|
||||
int hp_init_attr_set_interface(void)
|
||||
{
|
||||
return wmi_driver_register(&hp_attr_set_interface_driver);
|
||||
}
|
||||
|
||||
void hp_exit_attr_set_interface(void)
|
||||
{
|
||||
wmi_driver_unregister(&hp_attr_set_interface_driver);
|
||||
}
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, hp_attr_set_interface_id_table);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,487 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Definitions for kernel modules using hp_bioscfg driver
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#ifndef _HP_BIOSCFG_H_
|
||||
#define _HP_BIOSCFG_H_
|
||||
|
||||
#include <linux/wmi.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/nls.h>
|
||||
|
||||
#define DRIVER_NAME "hp-bioscfg"
|
||||
|
||||
#define MAX_BUFF_SIZE 512
|
||||
#define MAX_KEY_MOD_SIZE 256
|
||||
#define MAX_PASSWD_SIZE 64
|
||||
#define MAX_PREREQUISITES_SIZE 20
|
||||
#define MAX_REQ_ELEM_SIZE 128
|
||||
#define MAX_VALUES_SIZE 16
|
||||
#define MAX_ENCODINGS_SIZE 16
|
||||
#define MAX_ELEMENTS_SIZE 16
|
||||
|
||||
#define SPM_STR_DESC "Secure Platform Management"
|
||||
#define SPM_STR "SPM"
|
||||
#define SURE_START_DESC "Sure Start"
|
||||
#define SURE_START_STR "Sure_Start"
|
||||
#define SETUP_PASSWD "Setup Password"
|
||||
#define POWER_ON_PASSWD "Power-On Password"
|
||||
|
||||
#define LANG_CODE_STR "en_US.UTF-8"
|
||||
#define SCHEDULE_POWER_ON "Scheduled Power-On"
|
||||
|
||||
#define COMMA_SEP ","
|
||||
#define SEMICOLON_SEP ";"
|
||||
|
||||
/* Sure Admin Functions */
|
||||
|
||||
#define UTF_PREFIX "<utf-16/>"
|
||||
#define BEAM_PREFIX "<BEAM/>"
|
||||
|
||||
enum mechanism_values {
|
||||
PASSWORD = 0x00,
|
||||
SIGNING_KEY = 0x01,
|
||||
ENDORSEMENT_KEY = 0x02,
|
||||
};
|
||||
|
||||
#define BIOS_ADMIN "bios-admin"
|
||||
#define POWER_ON "power-on"
|
||||
#define BIOS_SPM "enhanced-bios-auth"
|
||||
|
||||
#define PASSWD_MECHANISM_TYPES "password"
|
||||
|
||||
#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
|
||||
|
||||
#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C"
|
||||
#define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05"
|
||||
#define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133"
|
||||
#define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745"
|
||||
#define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D"
|
||||
#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E"
|
||||
|
||||
enum hp_wmi_spm_commandtype {
|
||||
HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
|
||||
HPWMI_SECUREPLATFORM_SET_KEK = 0x11,
|
||||
HPWMI_SECUREPLATFORM_SET_SK = 0x12,
|
||||
};
|
||||
|
||||
enum hp_wmi_surestart_commandtype {
|
||||
HPWMI_SURESTART_GET_LOG_COUNT = 0x01,
|
||||
HPWMI_SURESTART_GET_LOG = 0x02,
|
||||
};
|
||||
|
||||
enum hp_wmi_command {
|
||||
HPWMI_READ = 0x01,
|
||||
HPWMI_WRITE = 0x02,
|
||||
HPWMI_ODM = 0x03,
|
||||
HPWMI_SURESTART = 0x20006,
|
||||
HPWMI_GM = 0x20008,
|
||||
HPWMI_SECUREPLATFORM = 0x20010,
|
||||
};
|
||||
|
||||
struct bios_return {
|
||||
u32 sigpass;
|
||||
u32 return_code;
|
||||
};
|
||||
|
||||
enum wmi_error_values {
|
||||
SUCCESS = 0x00,
|
||||
CMD_FAILED = 0x01,
|
||||
INVALID_SIGN = 0x02,
|
||||
INVALID_CMD_VALUE = 0x03,
|
||||
INVALID_CMD_TYPE = 0x04,
|
||||
INVALID_DATA_SIZE = 0x05,
|
||||
INVALID_CMD_PARAM = 0x06,
|
||||
ENCRYP_CMD_REQUIRED = 0x07,
|
||||
NO_SECURE_SESSION = 0x08,
|
||||
SECURE_SESSION_FOUND = 0x09,
|
||||
SECURE_SESSION_FAILED = 0x0A,
|
||||
AUTH_FAILED = 0x0B,
|
||||
INVALID_BIOS_AUTH = 0x0E,
|
||||
NONCE_DID_NOT_MATCH = 0x18,
|
||||
GENERIC_ERROR = 0x1C,
|
||||
BIOS_ADMIN_POLICY_NOT_MET = 0x28,
|
||||
BIOS_ADMIN_NOT_SET = 0x38,
|
||||
P21_NO_PROVISIONED = 0x1000,
|
||||
P21_PROVISION_IN_PROGRESS = 0x1001,
|
||||
P21_IN_USE = 0x1002,
|
||||
HEP_NOT_ACTIVE = 0x1004,
|
||||
HEP_ALREADY_SET = 0x1006,
|
||||
HEP_CHECK_STATE = 0x1007,
|
||||
};
|
||||
|
||||
struct common_data {
|
||||
u8 display_name[MAX_BUFF_SIZE];
|
||||
u8 path[MAX_BUFF_SIZE];
|
||||
u32 is_readonly;
|
||||
u32 display_in_ui;
|
||||
u32 requires_physical_presence;
|
||||
u32 sequence;
|
||||
u32 prerequisites_size;
|
||||
u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF_SIZE];
|
||||
u32 security_level;
|
||||
};
|
||||
|
||||
struct string_data {
|
||||
struct common_data common;
|
||||
struct kobject *attr_name_kobj;
|
||||
u8 current_value[MAX_BUFF_SIZE];
|
||||
u8 new_value[MAX_BUFF_SIZE];
|
||||
u32 min_length;
|
||||
u32 max_length;
|
||||
};
|
||||
|
||||
struct integer_data {
|
||||
struct common_data common;
|
||||
struct kobject *attr_name_kobj;
|
||||
u32 current_value;
|
||||
u32 new_value;
|
||||
u32 lower_bound;
|
||||
u32 upper_bound;
|
||||
u32 scalar_increment;
|
||||
};
|
||||
|
||||
struct enumeration_data {
|
||||
struct common_data common;
|
||||
struct kobject *attr_name_kobj;
|
||||
u8 current_value[MAX_BUFF_SIZE];
|
||||
u8 new_value[MAX_BUFF_SIZE];
|
||||
u32 possible_values_size;
|
||||
u8 possible_values[MAX_VALUES_SIZE][MAX_BUFF_SIZE];
|
||||
};
|
||||
|
||||
struct ordered_list_data {
|
||||
struct common_data common;
|
||||
struct kobject *attr_name_kobj;
|
||||
u8 current_value[MAX_BUFF_SIZE];
|
||||
u8 new_value[MAX_BUFF_SIZE];
|
||||
u32 elements_size;
|
||||
u8 elements[MAX_ELEMENTS_SIZE][MAX_BUFF_SIZE];
|
||||
};
|
||||
|
||||
struct password_data {
|
||||
struct common_data common;
|
||||
struct kobject *attr_name_kobj;
|
||||
u8 current_password[MAX_PASSWD_SIZE];
|
||||
u8 new_password[MAX_PASSWD_SIZE];
|
||||
u32 min_password_length;
|
||||
u32 max_password_length;
|
||||
u32 encodings_size;
|
||||
u8 encodings[MAX_ENCODINGS_SIZE][MAX_BUFF_SIZE];
|
||||
bool is_enabled;
|
||||
|
||||
/*
|
||||
* 'role' identifies the type of authentication.
|
||||
* Two known types are bios-admin and power-on.
|
||||
* 'bios-admin' represents BIOS administrator password
|
||||
* 'power-on' represents a password required to use the system
|
||||
*/
|
||||
u32 role;
|
||||
|
||||
/*
|
||||
* 'mechanism' represents the means of authentication.
|
||||
* Only supported type currently is "password"
|
||||
*/
|
||||
u32 mechanism;
|
||||
};
|
||||
|
||||
struct secure_platform_data {
|
||||
struct kobject *attr_name_kobj;
|
||||
u8 attribute_name[MAX_BUFF_SIZE];
|
||||
u8 *endorsement_key;
|
||||
u8 *signing_key;
|
||||
u8 *auth_token;
|
||||
bool is_enabled;
|
||||
u32 mechanism;
|
||||
};
|
||||
|
||||
struct bioscfg_priv {
|
||||
struct kset *authentication_dir_kset;
|
||||
struct kset *main_dir_kset;
|
||||
struct device *class_dev;
|
||||
struct string_data *string_data;
|
||||
u32 string_instances_count;
|
||||
struct integer_data *integer_data;
|
||||
u32 integer_instances_count;
|
||||
struct enumeration_data *enumeration_data;
|
||||
u32 enumeration_instances_count;
|
||||
struct ordered_list_data *ordered_list_data;
|
||||
u32 ordered_list_instances_count;
|
||||
struct password_data *password_data;
|
||||
u32 password_instances_count;
|
||||
|
||||
struct kobject *sure_start_attr_kobj;
|
||||
struct secure_platform_data spm_data;
|
||||
u8 display_name_language_code[MAX_BUFF_SIZE];
|
||||
bool pending_reboot;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
/* global structure used by multiple WMI interfaces */
|
||||
extern struct bioscfg_priv bioscfg_drv;
|
||||
|
||||
enum hp_wmi_data_type {
|
||||
HPWMI_STRING_TYPE,
|
||||
HPWMI_INTEGER_TYPE,
|
||||
HPWMI_ENUMERATION_TYPE,
|
||||
HPWMI_ORDERED_LIST_TYPE,
|
||||
HPWMI_PASSWORD_TYPE,
|
||||
HPWMI_SECURE_PLATFORM_TYPE,
|
||||
HPWMI_SURE_START_TYPE,
|
||||
};
|
||||
|
||||
enum hp_wmi_data_elements {
|
||||
/* Common elements */
|
||||
NAME = 0,
|
||||
VALUE = 1,
|
||||
PATH = 2,
|
||||
IS_READONLY = 3,
|
||||
DISPLAY_IN_UI = 4,
|
||||
REQUIRES_PHYSICAL_PRESENCE = 5,
|
||||
SEQUENCE = 6,
|
||||
PREREQUISITES_SIZE = 7,
|
||||
PREREQUISITES = 8,
|
||||
SECURITY_LEVEL = 9,
|
||||
|
||||
/* String elements */
|
||||
STR_MIN_LENGTH = 10,
|
||||
STR_MAX_LENGTH = 11,
|
||||
STR_ELEM_CNT = 12,
|
||||
|
||||
/* Integer elements */
|
||||
INT_LOWER_BOUND = 10,
|
||||
INT_UPPER_BOUND = 11,
|
||||
INT_SCALAR_INCREMENT = 12,
|
||||
INT_ELEM_CNT = 13,
|
||||
|
||||
/* Enumeration elements */
|
||||
ENUM_CURRENT_VALUE = 10,
|
||||
ENUM_SIZE = 11,
|
||||
ENUM_POSSIBLE_VALUES = 12,
|
||||
ENUM_ELEM_CNT = 13,
|
||||
|
||||
/* Ordered list elements */
|
||||
ORD_LIST_SIZE = 10,
|
||||
ORD_LIST_ELEMENTS = 11,
|
||||
ORD_ELEM_CNT = 12,
|
||||
|
||||
/* Password elements */
|
||||
PSWD_MIN_LENGTH = 10,
|
||||
PSWD_MAX_LENGTH = 11,
|
||||
PSWD_SIZE = 12,
|
||||
PSWD_ENCODINGS = 13,
|
||||
PSWD_IS_SET = 14,
|
||||
PSWD_ELEM_CNT = 15,
|
||||
};
|
||||
|
||||
#define GET_INSTANCE_ID(type) \
|
||||
static int get_##type##_instance_id(struct kobject *kobj) \
|
||||
{ \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \
|
||||
if (!strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \
|
||||
return i; \
|
||||
} \
|
||||
return -EIO; \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
int i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \
|
||||
return -EIO; \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
int i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \
|
||||
return -EIO; \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
|
||||
static ssize_t curr_val##_store(struct kobject *kobj, \
|
||||
struct kobj_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
char *attr_value = NULL; \
|
||||
int i; \
|
||||
int ret = -EIO; \
|
||||
\
|
||||
attr_value = kstrdup(buf, GFP_KERNEL); \
|
||||
if (!attr_value) \
|
||||
return -ENOMEM; \
|
||||
\
|
||||
ret = hp_enforce_single_line_input(attr_value, count); \
|
||||
if (!ret) { \
|
||||
i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
ret = validate_##type##_input(i, attr_value); \
|
||||
} \
|
||||
if (!ret) \
|
||||
ret = hp_set_attribute(kobj->name, attr_value); \
|
||||
if (!ret) { \
|
||||
update_##type##_value(i, attr_value); \
|
||||
if (bioscfg_drv.type##_data[i].common.requires_physical_presence) \
|
||||
hp_set_reboot_and_signal_event(); \
|
||||
} \
|
||||
hp_clear_all_credentials(); \
|
||||
kfree(attr_value); \
|
||||
\
|
||||
return ret ? ret : count; \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_SPM_N_PROPERTY_SHOW(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data.name); \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_SPM_S_PROPERTY_SHOW(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data.name); \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_VALUES_PROPERTY_SHOW(name, type, sep) \
|
||||
static ssize_t name##_show(struct kobject *kobj, \
|
||||
struct kobj_attribute *attr, char *buf) \
|
||||
{ \
|
||||
int i; \
|
||||
int len = 0; \
|
||||
int instance_id = get_##type##_instance_id(kobj); \
|
||||
\
|
||||
if (instance_id < 0) \
|
||||
return 0; \
|
||||
\
|
||||
for (i = 0; i < bioscfg_drv.type##_data[instance_id].name##_size; i++) { \
|
||||
if (i) \
|
||||
len += sysfs_emit_at(buf, len, "%s", sep); \
|
||||
\
|
||||
len += sysfs_emit_at(buf, len, "%s", \
|
||||
bioscfg_drv.type##_data[instance_id].name[i]); \
|
||||
} \
|
||||
len += sysfs_emit_at(buf, len, "\n"); \
|
||||
return len; \
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_S_COMMON_PROPERTY_SHOW(name, type) \
|
||||
static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
int i = get_##type##_instance_id(kobj); \
|
||||
if (i >= 0) \
|
||||
return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].common.name); \
|
||||
return -EIO; \
|
||||
}
|
||||
|
||||
extern struct kobj_attribute common_display_langcode;
|
||||
|
||||
/* Prototypes */
|
||||
|
||||
/* String attributes */
|
||||
int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_string_data(void);
|
||||
void hp_exit_string_attributes(void);
|
||||
int hp_populate_string_package_data(union acpi_object *str_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
/* Integer attributes */
|
||||
int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_integer_data(void);
|
||||
void hp_exit_integer_attributes(void);
|
||||
int hp_populate_integer_package_data(union acpi_object *integer_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
/* Enumeration attributes */
|
||||
int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_enumeration_data(void);
|
||||
void hp_exit_enumeration_attributes(void);
|
||||
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
/* Ordered list */
|
||||
int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr,
|
||||
u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_ordered_list_data(void);
|
||||
void hp_exit_ordered_list_attributes(void);
|
||||
int hp_populate_ordered_list_package_data(union acpi_object *order_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
/* Password authentication attributes */
|
||||
int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_populate_password_package_data(union acpi_object *password_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_password_data(void);
|
||||
int hp_get_password_instance_for_type(const char *name);
|
||||
int hp_clear_all_credentials(void);
|
||||
int hp_set_attribute(const char *a_name, const char *a_value);
|
||||
|
||||
/* SPM attributes */
|
||||
void hp_exit_password_attributes(void);
|
||||
void hp_exit_secure_platform_attributes(void);
|
||||
int hp_populate_secure_platform_data(struct kobject *attr_name_kobj);
|
||||
int hp_populate_security_buffer(u16 *buffer, const char *authentication);
|
||||
|
||||
/* Bios Attributes interface */
|
||||
int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size);
|
||||
int hp_wmi_perform_query(int query, enum hp_wmi_command command,
|
||||
void *buffer, u32 insize, u32 outsize);
|
||||
|
||||
/* Sure Start attributes */
|
||||
void hp_exit_sure_start_attributes(void);
|
||||
int hp_populate_sure_start_data(struct kobject *attr_name_kobj);
|
||||
|
||||
/* Bioscfg */
|
||||
|
||||
void hp_exit_attr_set_interface(void);
|
||||
int hp_init_attr_set_interface(void);
|
||||
size_t hp_calculate_string_buffer(const char *str);
|
||||
size_t hp_calculate_security_buffer(const char *authentication);
|
||||
void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str);
|
||||
int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer);
|
||||
int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size);
|
||||
int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len);
|
||||
int hp_encode_outsize_for_pvsz(int outsize);
|
||||
int hp_enforce_single_line_input(char *buf, size_t count);
|
||||
void hp_set_reboot_and_signal_event(void);
|
||||
ssize_t display_name_language_code_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf);
|
||||
union acpi_object *hp_get_wmiobj_pointer(int instance_id, const char *guid_string);
|
||||
int hp_get_instance_count(const char *guid_string);
|
||||
void hp_update_attribute_permissions(bool isreadonly, struct kobj_attribute *current_val);
|
||||
void hp_friendly_user_name_update(char *path, const char *attr_name,
|
||||
char *attr_display, int attr_size);
|
||||
int hp_wmi_error_and_message(int error_code);
|
||||
int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, struct common_data *common);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,457 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to enumeration type attributes under
|
||||
* BIOS Enumeration GUID for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
|
||||
GET_INSTANCE_ID(enumeration);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_enumeration_instance_id(kobj);
|
||||
|
||||
if (instance_id < 0)
|
||||
return -EIO;
|
||||
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
bioscfg_drv.enumeration_data[instance_id].current_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_enumeration_input() -
|
||||
* Validate input of current_value against possible values
|
||||
*
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_enumeration_input(int instance_id, const char *buf)
|
||||
{
|
||||
int i;
|
||||
int found = 0;
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
|
||||
/* Is it a read only attribute */
|
||||
if (enum_data->common.is_readonly)
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < enum_data->possible_values_size && !found; i++)
|
||||
if (!strcmp(enum_data->possible_values[i], buf))
|
||||
found = 1;
|
||||
|
||||
if (!found)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_enumeration_value(int instance_id, char *attr_value)
|
||||
{
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
|
||||
strscpy(enum_data->current_value,
|
||||
attr_value,
|
||||
sizeof(enum_data->current_value));
|
||||
}
|
||||
|
||||
ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration);
|
||||
static struct kobj_attribute enumeration_display_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
ATTRIBUTE_PROPERTY_STORE(current_value, enumeration);
|
||||
static struct kobj_attribute enumeration_current_val =
|
||||
__ATTR_RW(current_value);
|
||||
|
||||
ATTRIBUTE_VALUES_PROPERTY_SHOW(possible_values, enumeration, SEMICOLON_SEP);
|
||||
static struct kobj_attribute enumeration_poss_val =
|
||||
__ATTR_RO(possible_values);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "enumeration\n");
|
||||
}
|
||||
|
||||
static struct kobj_attribute enumeration_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *enumeration_attrs[] = {
|
||||
&common_display_langcode.attr,
|
||||
&enumeration_display_name.attr,
|
||||
&enumeration_current_val.attr,
|
||||
&enumeration_poss_val.attr,
|
||||
&enumeration_type.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group enumeration_attr_group = {
|
||||
.attrs = enumeration_attrs,
|
||||
};
|
||||
|
||||
int hp_alloc_enumeration_data(void)
|
||||
{
|
||||
bioscfg_drv.enumeration_instances_count =
|
||||
hp_get_instance_count(HP_WMI_BIOS_ENUMERATION_GUID);
|
||||
|
||||
bioscfg_drv.enumeration_data = kcalloc(bioscfg_drv.enumeration_instances_count,
|
||||
sizeof(*bioscfg_drv.enumeration_data), GFP_KERNEL);
|
||||
if (!bioscfg_drv.enumeration_data) {
|
||||
bioscfg_drv.enumeration_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expected Values types associated with each element */
|
||||
static const acpi_object_type expected_enum_types[] = {
|
||||
[NAME] = ACPI_TYPE_STRING,
|
||||
[VALUE] = ACPI_TYPE_STRING,
|
||||
[PATH] = ACPI_TYPE_STRING,
|
||||
[IS_READONLY] = ACPI_TYPE_INTEGER,
|
||||
[DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
|
||||
[REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
|
||||
[SEQUENCE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES] = ACPI_TYPE_STRING,
|
||||
[SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
|
||||
[ENUM_CURRENT_VALUE] = ACPI_TYPE_STRING,
|
||||
[ENUM_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[ENUM_POSSIBLE_VALUES] = ACPI_TYPE_STRING,
|
||||
};
|
||||
|
||||
static int hp_populate_enumeration_elements_from_package(union acpi_object *enum_obj,
|
||||
int enum_obj_count,
|
||||
int instance_id)
|
||||
{
|
||||
char *str_value = NULL;
|
||||
int value_len;
|
||||
u32 size = 0;
|
||||
u32 int_value = 0;
|
||||
int elem = 0;
|
||||
int reqs;
|
||||
int pos_values;
|
||||
int ret;
|
||||
int eloc;
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
|
||||
for (elem = 1, eloc = 1; elem < enum_obj_count; elem++, eloc++) {
|
||||
/* ONLY look at the first ENUM_ELEM_CNT elements */
|
||||
if (eloc == ENUM_ELEM_CNT)
|
||||
goto exit_enumeration_package;
|
||||
|
||||
switch (enum_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
if (PREREQUISITES != elem && ENUM_POSSIBLE_VALUES != elem) {
|
||||
ret = hp_convert_hexstr_to_str(enum_obj[elem].string.pointer,
|
||||
enum_obj[elem].string.length,
|
||||
&str_value, &value_len);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
int_value = (u32)enum_obj[elem].integer.value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported object type [%d]\n", enum_obj[elem].type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_enum_types[eloc] != enum_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_enum_types[eloc], elem, enum_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Assign appropriate element value to corresponding field */
|
||||
switch (eloc) {
|
||||
case NAME:
|
||||
case VALUE:
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(enum_data->common.path, str_value,
|
||||
sizeof(enum_data->common.path));
|
||||
break;
|
||||
case IS_READONLY:
|
||||
enum_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
enum_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
enum_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
enum_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
enum_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem >= enum_obj_count) {
|
||||
pr_err("Error enum-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer,
|
||||
enum_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
strscpy(enum_data->common.prerequisites[reqs],
|
||||
str_value,
|
||||
sizeof(enum_data->common.prerequisites[reqs]));
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
enum_data->common.security_level = int_value;
|
||||
break;
|
||||
|
||||
case ENUM_CURRENT_VALUE:
|
||||
strscpy(enum_data->current_value,
|
||||
str_value, sizeof(enum_data->current_value));
|
||||
break;
|
||||
case ENUM_SIZE:
|
||||
if (int_value > MAX_VALUES_SIZE) {
|
||||
pr_warn("Possible number values size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_VALUES_SIZE;
|
||||
}
|
||||
enum_data->possible_values_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. POSSIBLE_VALUES
|
||||
* object is omitted by BIOS when the size is zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
|
||||
case ENUM_POSSIBLE_VALUES:
|
||||
size = enum_data->possible_values_size;
|
||||
|
||||
for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE;
|
||||
pos_values++) {
|
||||
if (elem >= enum_obj_count) {
|
||||
pr_err("Error enum-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(enum_obj[elem + pos_values].string.pointer,
|
||||
enum_obj[elem + pos_values].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* ignore strings when possible values size
|
||||
* is greater than MAX_VALUES_SIZE
|
||||
*/
|
||||
if (size < MAX_VALUES_SIZE)
|
||||
strscpy(enum_data->possible_values[pos_values],
|
||||
str_value,
|
||||
sizeof(enum_data->possible_values[pos_values]));
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
|
||||
exit_enumeration_package:
|
||||
kfree(str_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_enumeration_package_data() -
|
||||
* Populate all properties of an instance under enumeration attribute
|
||||
*
|
||||
* @enum_obj: ACPI object with enumeration data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
|
||||
enum_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_enumeration_elements_from_package(enum_obj,
|
||||
enum_obj->package.count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(enum_data->common.is_readonly,
|
||||
&enumeration_current_val);
|
||||
/*
|
||||
* Several attributes have names such "MONDAY". Friendly
|
||||
* user nane is generated to make the name more descriptive
|
||||
*/
|
||||
hp_friendly_user_name_update(enum_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
enum_data->common.display_name,
|
||||
sizeof(enum_data->common.display_name));
|
||||
return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
|
||||
}
|
||||
|
||||
static int hp_populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id)
|
||||
{
|
||||
int values;
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Only data relevant to this driver and its functionality is
|
||||
* read. BIOS defines the order in which each * element is
|
||||
* read. Element 0 data is not relevant to this
|
||||
* driver hence it is ignored. For clarity, all element names
|
||||
* (DISPLAY_IN_UI) which defines the order in which is read
|
||||
* and the name matches the variable where the data is stored.
|
||||
*
|
||||
* In earlier implementation, reported errors were ignored
|
||||
* causing the data to remain uninitialized. It is not
|
||||
* possible to determine if data read from BIOS is valid or
|
||||
* not. It is for this reason functions may return a error
|
||||
* without validating the data itself.
|
||||
*/
|
||||
|
||||
// VALUE:
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, enum_data->current_value,
|
||||
sizeof(enum_data->current_value));
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// COMMON:
|
||||
ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &enum_data->common);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// ENUM_CURRENT_VALUE:
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
|
||||
enum_data->current_value,
|
||||
sizeof(enum_data->current_value));
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// ENUM_SIZE:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&enum_data->possible_values_size);
|
||||
|
||||
if (enum_data->possible_values_size > MAX_VALUES_SIZE) {
|
||||
/* Report a message and limit possible values size to maximum value */
|
||||
pr_warn("Enum Possible size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
enum_data->possible_values_size = MAX_VALUES_SIZE;
|
||||
}
|
||||
|
||||
// ENUM_POSSIBLE_VALUES:
|
||||
for (values = 0; values < enum_data->possible_values_size; values++) {
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
|
||||
enum_data->possible_values[values],
|
||||
sizeof(enum_data->possible_values[values]));
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_enumeration_buffer_data() -
|
||||
* Populate all properties of an instance under enumeration attribute
|
||||
*
|
||||
* @buffer_ptr: Buffer pointer
|
||||
* @buffer_size: Buffer size
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
enum_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
/* Populate enumeration elements */
|
||||
ret = hp_populate_enumeration_elements_from_buffer(buffer_ptr, buffer_size,
|
||||
instance_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hp_update_attribute_permissions(enum_data->common.is_readonly,
|
||||
&enumeration_current_val);
|
||||
/*
|
||||
* Several attributes have names such "MONDAY". A Friendlier
|
||||
* user nane is generated to make the name more descriptive
|
||||
*/
|
||||
hp_friendly_user_name_update(enum_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
enum_data->common.display_name,
|
||||
sizeof(enum_data->common.display_name));
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_exit_enumeration_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void hp_exit_enumeration_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < bioscfg_drv.enumeration_instances_count;
|
||||
instance_id++) {
|
||||
struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
|
||||
struct kobject *attr_name_kobj = enum_data->attr_name_kobj;
|
||||
|
||||
if (attr_name_kobj)
|
||||
sysfs_remove_group(attr_name_kobj, &enumeration_attr_group);
|
||||
}
|
||||
bioscfg_drv.enumeration_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.enumeration_data);
|
||||
bioscfg_drv.enumeration_data = NULL;
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to integer type attributes under
|
||||
* BIOS Enumeration GUID for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 Hewlett-Packard Inc.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
|
||||
GET_INSTANCE_ID(integer);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_integer_instance_id(kobj);
|
||||
|
||||
if (instance_id < 0)
|
||||
return -EIO;
|
||||
|
||||
return sysfs_emit(buf, "%d\n",
|
||||
bioscfg_drv.integer_data[instance_id].current_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_integer_input() -
|
||||
* Validate input of current_value against lower and upper bound
|
||||
*
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_integer_input(int instance_id, char *buf)
|
||||
{
|
||||
int in_val;
|
||||
int ret;
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
|
||||
/* BIOS treats it as a read only attribute */
|
||||
if (integer_data->common.is_readonly)
|
||||
return -EIO;
|
||||
|
||||
ret = kstrtoint(buf, 10, &in_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (in_val < integer_data->lower_bound ||
|
||||
in_val > integer_data->upper_bound)
|
||||
return -ERANGE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_integer_value(int instance_id, char *attr_value)
|
||||
{
|
||||
int in_val;
|
||||
int ret;
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
|
||||
ret = kstrtoint(attr_value, 10, &in_val);
|
||||
if (ret == 0)
|
||||
integer_data->current_value = in_val;
|
||||
else
|
||||
pr_warn("Invalid integer value found: %s\n", attr_value);
|
||||
}
|
||||
|
||||
ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer);
|
||||
static struct kobj_attribute integer_display_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
ATTRIBUTE_PROPERTY_STORE(current_value, integer);
|
||||
static struct kobj_attribute integer_current_val =
|
||||
__ATTR_RW_MODE(current_value, 0644);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer);
|
||||
static struct kobj_attribute integer_lower_bound =
|
||||
__ATTR_RO(lower_bound);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer);
|
||||
static struct kobj_attribute integer_upper_bound =
|
||||
__ATTR_RO(upper_bound);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer);
|
||||
static struct kobj_attribute integer_scalar_increment =
|
||||
__ATTR_RO(scalar_increment);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "integer\n");
|
||||
}
|
||||
|
||||
static struct kobj_attribute integer_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *integer_attrs[] = {
|
||||
&common_display_langcode.attr,
|
||||
&integer_display_name.attr,
|
||||
&integer_current_val.attr,
|
||||
&integer_lower_bound.attr,
|
||||
&integer_upper_bound.attr,
|
||||
&integer_scalar_increment.attr,
|
||||
&integer_type.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group integer_attr_group = {
|
||||
.attrs = integer_attrs,
|
||||
};
|
||||
|
||||
int hp_alloc_integer_data(void)
|
||||
{
|
||||
bioscfg_drv.integer_instances_count = hp_get_instance_count(HP_WMI_BIOS_INTEGER_GUID);
|
||||
bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count,
|
||||
sizeof(*bioscfg_drv.integer_data), GFP_KERNEL);
|
||||
|
||||
if (!bioscfg_drv.integer_data) {
|
||||
bioscfg_drv.integer_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expected Values types associated with each element */
|
||||
static const acpi_object_type expected_integer_types[] = {
|
||||
[NAME] = ACPI_TYPE_STRING,
|
||||
[VALUE] = ACPI_TYPE_STRING,
|
||||
[PATH] = ACPI_TYPE_STRING,
|
||||
[IS_READONLY] = ACPI_TYPE_INTEGER,
|
||||
[DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
|
||||
[REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
|
||||
[SEQUENCE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES] = ACPI_TYPE_STRING,
|
||||
[SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
|
||||
[INT_LOWER_BOUND] = ACPI_TYPE_INTEGER,
|
||||
[INT_UPPER_BOUND] = ACPI_TYPE_INTEGER,
|
||||
[INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER,
|
||||
};
|
||||
|
||||
static int hp_populate_integer_elements_from_package(union acpi_object *integer_obj,
|
||||
int integer_obj_count,
|
||||
int instance_id)
|
||||
{
|
||||
char *str_value = NULL;
|
||||
int value_len;
|
||||
int ret;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int size;
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
|
||||
if (!integer_obj)
|
||||
return -EINVAL;
|
||||
|
||||
for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
|
||||
/* ONLY look at the first INTEGER_ELEM_CNT elements */
|
||||
if (eloc == INT_ELEM_CNT)
|
||||
goto exit_integer_package;
|
||||
|
||||
switch (integer_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
if (elem != PREREQUISITES) {
|
||||
ret = hp_convert_hexstr_to_str(integer_obj[elem].string.pointer,
|
||||
integer_obj[elem].string.length,
|
||||
&str_value, &value_len);
|
||||
if (ret)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
int_value = (u32)integer_obj[elem].integer.value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type);
|
||||
continue;
|
||||
}
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_integer_types[eloc] != integer_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_integer_types[eloc], elem, integer_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
}
|
||||
/* Assign appropriate element value to corresponding field*/
|
||||
switch (eloc) {
|
||||
case VALUE:
|
||||
ret = kstrtoint(str_value, 10, &int_value);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
integer_data->current_value = int_value;
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(integer_data->common.path, str_value,
|
||||
sizeof(integer_data->common.path));
|
||||
break;
|
||||
case IS_READONLY:
|
||||
integer_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
integer_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
integer_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
integer_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
integer_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (integer_data->common.prerequisites_size == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem >= integer_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
|
||||
integer_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(integer_data->common.prerequisites[reqs],
|
||||
str_value,
|
||||
sizeof(integer_data->common.prerequisites[reqs]));
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
integer_data->common.security_level = int_value;
|
||||
break;
|
||||
case INT_LOWER_BOUND:
|
||||
integer_data->lower_bound = int_value;
|
||||
break;
|
||||
case INT_UPPER_BOUND:
|
||||
integer_data->upper_bound = int_value;
|
||||
break;
|
||||
case INT_SCALAR_INCREMENT:
|
||||
integer_data->scalar_increment = int_value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
exit_integer_package:
|
||||
kfree(str_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_integer_package_data() -
|
||||
* Populate all properties of an instance under integer attribute
|
||||
*
|
||||
* @integer_obj: ACPI object with integer data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_integer_package_data(union acpi_object *integer_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
|
||||
integer_data->attr_name_kobj = attr_name_kobj;
|
||||
hp_populate_integer_elements_from_package(integer_obj,
|
||||
integer_obj->package.count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(integer_data->common.is_readonly,
|
||||
&integer_current_val);
|
||||
hp_friendly_user_name_update(integer_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
integer_data->common.display_name,
|
||||
sizeof(integer_data->common.display_name));
|
||||
return sysfs_create_group(attr_name_kobj, &integer_attr_group);
|
||||
}
|
||||
|
||||
static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id)
|
||||
{
|
||||
char *dst = NULL;
|
||||
int dst_size = *buffer_size / sizeof(u16);
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
|
||||
if (!dst)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Only data relevant to this driver and its functionality is
|
||||
* read. BIOS defines the order in which each * element is
|
||||
* read. Element 0 data is not relevant to this
|
||||
* driver hence it is ignored. For clarity, all element names
|
||||
* (DISPLAY_IN_UI) which defines the order in which is read
|
||||
* and the name matches the variable where the data is stored.
|
||||
*
|
||||
* In earlier implementation, reported errors were ignored
|
||||
* causing the data to remain uninitialized. It is not
|
||||
* possible to determine if data read from BIOS is valid or
|
||||
* not. It is for this reason functions may return a error
|
||||
* without validating the data itself.
|
||||
*/
|
||||
|
||||
// VALUE:
|
||||
integer_data->current_value = 0;
|
||||
|
||||
hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
|
||||
ret = kstrtoint(dst, 10, &integer_data->current_value);
|
||||
if (ret)
|
||||
pr_warn("Unable to convert string to integer: %s\n", dst);
|
||||
kfree(dst);
|
||||
|
||||
// COMMON:
|
||||
ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &integer_data->common);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// INT_LOWER_BOUND:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&integer_data->lower_bound);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// INT_UPPER_BOUND:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&integer_data->upper_bound);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// INT_SCALAR_INCREMENT:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&integer_data->scalar_increment);
|
||||
|
||||
buffer_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_integer_buffer_data() -
|
||||
* Populate all properties of an instance under integer attribute
|
||||
*
|
||||
* @buffer_ptr: Buffer pointer
|
||||
* @buffer_size: Buffer size
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
integer_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
/* Populate integer elements */
|
||||
ret = hp_populate_integer_elements_from_buffer(buffer_ptr, buffer_size,
|
||||
instance_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hp_update_attribute_permissions(integer_data->common.is_readonly,
|
||||
&integer_current_val);
|
||||
hp_friendly_user_name_update(integer_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
integer_data->common.display_name,
|
||||
sizeof(integer_data->common.display_name));
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &integer_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_exit_integer_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void hp_exit_integer_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count;
|
||||
instance_id++) {
|
||||
struct kobject *attr_name_kobj =
|
||||
bioscfg_drv.integer_data[instance_id].attr_name_kobj;
|
||||
|
||||
if (attr_name_kobj)
|
||||
sysfs_remove_group(attr_name_kobj, &integer_attr_group);
|
||||
}
|
||||
bioscfg_drv.integer_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.integer_data);
|
||||
bioscfg_drv.integer_data = NULL;
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to ordered list type attributes under
|
||||
* BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
|
||||
GET_INSTANCE_ID(ordered_list);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_ordered_list_instance_id(kobj);
|
||||
|
||||
if (instance_id < 0)
|
||||
return -EIO;
|
||||
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
bioscfg_drv.ordered_list_data[instance_id].current_value);
|
||||
}
|
||||
|
||||
static int replace_char_str(u8 *buffer, char *repl_char, char *repl_with)
|
||||
{
|
||||
char *src = buffer;
|
||||
int buflen = strlen(buffer);
|
||||
int item;
|
||||
|
||||
if (buflen < 1)
|
||||
return -EINVAL;
|
||||
|
||||
for (item = 0; item < buflen; item++)
|
||||
if (src[item] == *repl_char)
|
||||
src[item] = *repl_with;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_ordered_list_input() -
|
||||
* Validate input of current_value against possible values
|
||||
*
|
||||
* @instance: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_ordered_list_input(int instance, char *buf)
|
||||
{
|
||||
/* validation is done by BIOS. This validation function will
|
||||
* convert semicolon to commas. BIOS uses commas as
|
||||
* separators when reporting ordered-list values.
|
||||
*/
|
||||
return replace_char_str(buf, SEMICOLON_SEP, COMMA_SEP);
|
||||
}
|
||||
|
||||
static void update_ordered_list_value(int instance, char *attr_value)
|
||||
{
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance];
|
||||
|
||||
strscpy(ordered_list_data->current_value,
|
||||
attr_value,
|
||||
sizeof(ordered_list_data->current_value));
|
||||
}
|
||||
|
||||
ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
|
||||
static struct kobj_attribute ordered_list_display_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
|
||||
static struct kobj_attribute ordered_list_current_val =
|
||||
__ATTR_RW_MODE(current_value, 0644);
|
||||
|
||||
ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list, SEMICOLON_SEP);
|
||||
static struct kobj_attribute ordered_list_elements_val =
|
||||
__ATTR_RO(elements);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "ordered-list\n");
|
||||
}
|
||||
|
||||
static struct kobj_attribute ordered_list_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *ordered_list_attrs[] = {
|
||||
&common_display_langcode.attr,
|
||||
&ordered_list_display_name.attr,
|
||||
&ordered_list_current_val.attr,
|
||||
&ordered_list_elements_val.attr,
|
||||
&ordered_list_type.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group ordered_list_attr_group = {
|
||||
.attrs = ordered_list_attrs,
|
||||
};
|
||||
|
||||
int hp_alloc_ordered_list_data(void)
|
||||
{
|
||||
bioscfg_drv.ordered_list_instances_count =
|
||||
hp_get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID);
|
||||
bioscfg_drv.ordered_list_data = kcalloc(bioscfg_drv.ordered_list_instances_count,
|
||||
sizeof(*bioscfg_drv.ordered_list_data),
|
||||
GFP_KERNEL);
|
||||
if (!bioscfg_drv.ordered_list_data) {
|
||||
bioscfg_drv.ordered_list_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expected Values types associated with each element */
|
||||
static const acpi_object_type expected_order_types[] = {
|
||||
[NAME] = ACPI_TYPE_STRING,
|
||||
[VALUE] = ACPI_TYPE_STRING,
|
||||
[PATH] = ACPI_TYPE_STRING,
|
||||
[IS_READONLY] = ACPI_TYPE_INTEGER,
|
||||
[DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
|
||||
[REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
|
||||
[SEQUENCE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES] = ACPI_TYPE_STRING,
|
||||
[SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
|
||||
[ORD_LIST_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[ORD_LIST_ELEMENTS] = ACPI_TYPE_STRING,
|
||||
};
|
||||
|
||||
static int hp_populate_ordered_list_elements_from_package(union acpi_object *order_obj,
|
||||
int order_obj_count,
|
||||
int instance_id)
|
||||
{
|
||||
char *str_value = NULL;
|
||||
int value_len = 0;
|
||||
int ret;
|
||||
u32 size;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int olist_elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
char *tmpstr = NULL;
|
||||
char *part_tmp = NULL;
|
||||
int tmp_len = 0;
|
||||
char *part = NULL;
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
|
||||
|
||||
if (!order_obj)
|
||||
return -EINVAL;
|
||||
|
||||
for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) {
|
||||
|
||||
switch (order_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
|
||||
ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer,
|
||||
order_obj[elem].string.length,
|
||||
&str_value, &value_len);
|
||||
if (ret)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
int_value = (u32)order_obj[elem].integer.value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported object type [%d]\n", order_obj[elem].type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_order_types[eloc] != order_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_order_types[eloc], elem, order_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Assign appropriate element value to corresponding field*/
|
||||
switch (eloc) {
|
||||
case VALUE:
|
||||
strscpy(ordered_list_data->current_value,
|
||||
str_value, sizeof(ordered_list_data->current_value));
|
||||
replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(ordered_list_data->common.path, str_value,
|
||||
sizeof(ordered_list_data->common.path));
|
||||
break;
|
||||
case IS_READONLY:
|
||||
ordered_list_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
ordered_list_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
ordered_list_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
ordered_list_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
ordered_list_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, ordered_list_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
|
||||
order_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(ordered_list_data->common.prerequisites[reqs],
|
||||
str_value,
|
||||
sizeof(ordered_list_data->common.prerequisites[reqs]));
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
ordered_list_data->common.security_level = int_value;
|
||||
break;
|
||||
|
||||
case ORD_LIST_SIZE:
|
||||
if (int_value > MAX_ELEMENTS_SIZE) {
|
||||
pr_warn("Order List size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_ELEMENTS_SIZE;
|
||||
}
|
||||
ordered_list_data->elements_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. ORD_LIST_ELEMENTS
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case ORD_LIST_ELEMENTS:
|
||||
|
||||
/*
|
||||
* Ordered list data is stored in hex and comma separated format
|
||||
* Convert the data and split it to show each element
|
||||
*/
|
||||
ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
|
||||
if (ret)
|
||||
goto exit_list;
|
||||
|
||||
part_tmp = tmpstr;
|
||||
part = strsep(&part_tmp, COMMA_SEP);
|
||||
|
||||
for (olist_elem = 0; olist_elem < MAX_ELEMENTS_SIZE && part; olist_elem++) {
|
||||
strscpy(ordered_list_data->elements[olist_elem],
|
||||
part,
|
||||
sizeof(ordered_list_data->elements[olist_elem]));
|
||||
part = strsep(&part_tmp, COMMA_SEP);
|
||||
}
|
||||
ordered_list_data->elements_size = olist_elem;
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in Ordered_List attribute or data may be malformed\n", elem);
|
||||
break;
|
||||
}
|
||||
kfree(tmpstr);
|
||||
tmpstr = NULL;
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
|
||||
exit_list:
|
||||
kfree(tmpstr);
|
||||
kfree(str_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_ordered_list_package_data() -
|
||||
* Populate all properties of an instance under ordered_list attribute
|
||||
*
|
||||
* @order_obj: ACPI object with ordered_list data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
|
||||
|
||||
ordered_list_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_ordered_list_elements_from_package(order_obj,
|
||||
order_obj->package.count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
|
||||
&ordered_list_current_val);
|
||||
hp_friendly_user_name_update(ordered_list_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
ordered_list_data->common.display_name,
|
||||
sizeof(ordered_list_data->common.display_name));
|
||||
return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
|
||||
}
|
||||
|
||||
static int hp_populate_ordered_list_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id)
|
||||
{
|
||||
int values;
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Only data relevant to this driver and its functionality is
|
||||
* read. BIOS defines the order in which each * element is
|
||||
* read. Element 0 data is not relevant to this
|
||||
* driver hence it is ignored. For clarity, all element names
|
||||
* (DISPLAY_IN_UI) which defines the order in which is read
|
||||
* and the name matches the variable where the data is stored.
|
||||
*
|
||||
* In earlier implementation, reported errors were ignored
|
||||
* causing the data to remain uninitialized. It is not
|
||||
* possible to determine if data read from BIOS is valid or
|
||||
* not. It is for this reason functions may return a error
|
||||
* without validating the data itself.
|
||||
*/
|
||||
|
||||
// VALUE:
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, ordered_list_data->current_value,
|
||||
sizeof(ordered_list_data->current_value));
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
|
||||
|
||||
// COMMON:
|
||||
ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size,
|
||||
&ordered_list_data->common);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// ORD_LIST_SIZE:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&ordered_list_data->elements_size);
|
||||
|
||||
if (ordered_list_data->elements_size > MAX_ELEMENTS_SIZE) {
|
||||
/* Report a message and limit elements size to maximum value */
|
||||
pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
ordered_list_data->elements_size = MAX_ELEMENTS_SIZE;
|
||||
}
|
||||
|
||||
// ORD_LIST_ELEMENTS:
|
||||
for (values = 0; values < ordered_list_data->elements_size; values++) {
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
|
||||
ordered_list_data->elements[values],
|
||||
sizeof(ordered_list_data->elements[values]));
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_ordered_list_buffer_data() - Populate all properties of an
|
||||
* instance under ordered list attribute
|
||||
*
|
||||
* @buffer_ptr: Buffer pointer
|
||||
* @buffer_size: Buffer size
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
ordered_list_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
/* Populate ordered list elements */
|
||||
ret = hp_populate_ordered_list_elements_from_buffer(buffer_ptr, buffer_size,
|
||||
instance_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
|
||||
&ordered_list_current_val);
|
||||
hp_friendly_user_name_update(ordered_list_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
ordered_list_data->common.display_name,
|
||||
sizeof(ordered_list_data->common.display_name));
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_exit_ordered_list_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void hp_exit_ordered_list_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < bioscfg_drv.ordered_list_instances_count;
|
||||
instance_id++) {
|
||||
struct kobject *attr_name_kobj =
|
||||
bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj;
|
||||
|
||||
if (attr_name_kobj)
|
||||
sysfs_remove_group(attr_name_kobj,
|
||||
&ordered_list_attr_group);
|
||||
}
|
||||
bioscfg_drv.ordered_list_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.ordered_list_data);
|
||||
bioscfg_drv.ordered_list_data = NULL;
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to password object type attributes under
|
||||
* BIOS PASSWORD for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
#include <asm-generic/posix_types.h>
|
||||
|
||||
GET_INSTANCE_ID(password);
|
||||
/*
|
||||
* Clear all passwords copied to memory for a particular
|
||||
* authentication instance
|
||||
*/
|
||||
static int clear_passwords(const int instance)
|
||||
{
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance];
|
||||
|
||||
if (!password_data->is_enabled)
|
||||
return 0;
|
||||
|
||||
memset(password_data->current_password,
|
||||
0, sizeof(password_data->current_password));
|
||||
memset(password_data->new_password,
|
||||
0, sizeof(password_data->new_password));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear all credentials copied to memory for both Power-ON and Setup
|
||||
* BIOS instances
|
||||
*/
|
||||
int hp_clear_all_credentials(void)
|
||||
{
|
||||
int count = bioscfg_drv.password_instances_count;
|
||||
int instance;
|
||||
|
||||
/* clear all passwords */
|
||||
for (instance = 0; instance < count; instance++)
|
||||
clear_passwords(instance);
|
||||
|
||||
/* clear auth_token */
|
||||
kfree(bioscfg_drv.spm_data.auth_token);
|
||||
bioscfg_drv.spm_data.auth_token = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hp_get_password_instance_for_type(const char *name)
|
||||
{
|
||||
int count = bioscfg_drv.password_instances_count;
|
||||
int instance;
|
||||
|
||||
for (instance = 0; instance < count; instance++)
|
||||
if (!strcmp(bioscfg_drv.password_data[instance].common.display_name, name))
|
||||
return instance;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int validate_password_input(int instance_id, const char *buf)
|
||||
{
|
||||
int length;
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
||||
length = strlen(buf);
|
||||
if (buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
if (length > MAX_PASSWD_SIZE)
|
||||
return INVALID_BIOS_AUTH;
|
||||
|
||||
if (password_data->min_password_length > length ||
|
||||
password_data->max_password_length < length)
|
||||
return INVALID_BIOS_AUTH;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
|
||||
static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
|
||||
|
||||
static int store_password_instance(struct kobject *kobj, const char *buf,
|
||||
size_t count, bool is_current)
|
||||
{
|
||||
char *buf_cp;
|
||||
int id, ret = 0;
|
||||
|
||||
buf_cp = kstrdup(buf, GFP_KERNEL);
|
||||
if (!buf_cp)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hp_enforce_single_line_input(buf_cp, count);
|
||||
if (!ret) {
|
||||
id = get_password_instance_id(kobj);
|
||||
|
||||
if (id >= 0)
|
||||
ret = validate_password_input(id, buf_cp);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
if (is_current)
|
||||
strscpy(bioscfg_drv.password_data[id].current_password,
|
||||
buf_cp,
|
||||
sizeof(bioscfg_drv.password_data[id].current_password));
|
||||
else
|
||||
strscpy(bioscfg_drv.password_data[id].new_password,
|
||||
buf_cp,
|
||||
sizeof(bioscfg_drv.password_data[id].new_password));
|
||||
}
|
||||
|
||||
kfree(buf_cp);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t current_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
return store_password_instance(kobj, buf, count, true);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
|
||||
|
||||
static ssize_t new_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
return store_password_instance(kobj, buf, count, true);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
|
||||
static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
|
||||
static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
|
||||
|
||||
static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
if (!strcmp(kobj->name, SETUP_PASSWD))
|
||||
return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
|
||||
|
||||
if (!strcmp(kobj->name, POWER_ON_PASSWD))
|
||||
return sysfs_emit(buf, "%s\n", POWER_ON);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_role = __ATTR_RO(role);
|
||||
|
||||
static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int i = get_password_instance_id(kobj);
|
||||
|
||||
if (i < 0)
|
||||
return i;
|
||||
|
||||
if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
|
||||
return -EINVAL;
|
||||
|
||||
return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
|
||||
|
||||
ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password, SEMICOLON_SEP);
|
||||
static struct kobj_attribute password_encodings_val = __ATTR_RO(encodings);
|
||||
|
||||
static struct attribute *password_attrs[] = {
|
||||
&password_is_password_set.attr,
|
||||
&password_min_password_length.attr,
|
||||
&password_max_password_length.attr,
|
||||
&password_current_password.attr,
|
||||
&password_new_password.attr,
|
||||
&password_role.attr,
|
||||
&password_mechanism.attr,
|
||||
&password_encodings_val.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group password_attr_group = {
|
||||
.attrs = password_attrs
|
||||
};
|
||||
|
||||
int hp_alloc_password_data(void)
|
||||
{
|
||||
bioscfg_drv.password_instances_count = hp_get_instance_count(HP_WMI_BIOS_PASSWORD_GUID);
|
||||
bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count,
|
||||
sizeof(*bioscfg_drv.password_data), GFP_KERNEL);
|
||||
if (!bioscfg_drv.password_data) {
|
||||
bioscfg_drv.password_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expected Values types associated with each element */
|
||||
static const acpi_object_type expected_password_types[] = {
|
||||
[NAME] = ACPI_TYPE_STRING,
|
||||
[VALUE] = ACPI_TYPE_STRING,
|
||||
[PATH] = ACPI_TYPE_STRING,
|
||||
[IS_READONLY] = ACPI_TYPE_INTEGER,
|
||||
[DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
|
||||
[REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
|
||||
[SEQUENCE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES] = ACPI_TYPE_STRING,
|
||||
[SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
|
||||
[PSWD_MIN_LENGTH] = ACPI_TYPE_INTEGER,
|
||||
[PSWD_MAX_LENGTH] = ACPI_TYPE_INTEGER,
|
||||
[PSWD_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PSWD_ENCODINGS] = ACPI_TYPE_STRING,
|
||||
[PSWD_IS_SET] = ACPI_TYPE_INTEGER,
|
||||
};
|
||||
|
||||
static int hp_populate_password_elements_from_package(union acpi_object *password_obj,
|
||||
int password_obj_count,
|
||||
int instance_id)
|
||||
{
|
||||
char *str_value = NULL;
|
||||
int value_len;
|
||||
int ret;
|
||||
u32 size;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int pos_values;
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
||||
if (!password_obj)
|
||||
return -EINVAL;
|
||||
|
||||
for (elem = 1, eloc = 1; elem < password_obj_count; elem++, eloc++) {
|
||||
/* ONLY look at the first PASSWORD_ELEM_CNT elements */
|
||||
if (eloc == PSWD_ELEM_CNT)
|
||||
goto exit_package;
|
||||
|
||||
switch (password_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
if (PREREQUISITES != elem && PSWD_ENCODINGS != elem) {
|
||||
ret = hp_convert_hexstr_to_str(password_obj[elem].string.pointer,
|
||||
password_obj[elem].string.length,
|
||||
&str_value, &value_len);
|
||||
if (ret)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
int_value = (u32)password_obj[elem].integer.value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported object type [%d]\n", password_obj[elem].type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_password_types[eloc] != password_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_password_types[eloc], elem, password_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Assign appropriate element value to corresponding field*/
|
||||
switch (eloc) {
|
||||
case VALUE:
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(password_data->common.path, str_value,
|
||||
sizeof(password_data->common.path));
|
||||
break;
|
||||
case IS_READONLY:
|
||||
password_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
password_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
password_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
password_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
password_data->common.prerequisites_size = int_value;
|
||||
|
||||
/* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, password_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
|
||||
password_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
strscpy(password_data->common.prerequisites[reqs],
|
||||
str_value,
|
||||
sizeof(password_data->common.prerequisites[reqs]));
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
|
||||
}
|
||||
break;
|
||||
case SECURITY_LEVEL:
|
||||
password_data->common.security_level = int_value;
|
||||
break;
|
||||
case PSWD_MIN_LENGTH:
|
||||
password_data->min_password_length = int_value;
|
||||
break;
|
||||
case PSWD_MAX_LENGTH:
|
||||
password_data->max_password_length = int_value;
|
||||
break;
|
||||
case PSWD_SIZE:
|
||||
|
||||
if (int_value > MAX_ENCODINGS_SIZE) {
|
||||
pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_ENCODINGS_SIZE;
|
||||
}
|
||||
password_data->encodings_size = int_value;
|
||||
|
||||
/* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PSWD_ENCODINGS
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PSWD_ENCODINGS:
|
||||
size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE);
|
||||
for (pos_values = 0; pos_values < size; pos_values++) {
|
||||
ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
|
||||
password_obj[elem + pos_values].string.length,
|
||||
&str_value, &value_len);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
strscpy(password_data->encodings[pos_values],
|
||||
str_value,
|
||||
sizeof(password_data->encodings[pos_values]));
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
|
||||
}
|
||||
break;
|
||||
case PSWD_IS_SET:
|
||||
password_data->is_enabled = int_value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
|
||||
exit_package:
|
||||
kfree(str_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_password_package_data()
|
||||
* Populate all properties for an instance under password attribute
|
||||
*
|
||||
* @password_obj: ACPI object with password data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
||||
password_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_password_elements_from_package(password_obj,
|
||||
password_obj->package.count,
|
||||
instance_id);
|
||||
|
||||
hp_friendly_user_name_update(password_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
password_data->common.display_name,
|
||||
sizeof(password_data->common.display_name));
|
||||
|
||||
if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
|
||||
return sysfs_create_group(attr_name_kobj, &password_attr_group);
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &password_attr_group);
|
||||
}
|
||||
|
||||
static int hp_populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id)
|
||||
{
|
||||
int values;
|
||||
int isreadonly;
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Only data relevant to this driver and its functionality is
|
||||
* read. BIOS defines the order in which each * element is
|
||||
* read. Element 0 data is not relevant to this
|
||||
* driver hence it is ignored. For clarity, all element names
|
||||
* (DISPLAY_IN_UI) which defines the order in which is read
|
||||
* and the name matches the variable where the data is stored.
|
||||
*
|
||||
* In earlier implementation, reported errors were ignored
|
||||
* causing the data to remain uninitialized. It is not
|
||||
* possible to determine if data read from BIOS is valid or
|
||||
* not. It is for this reason functions may return a error
|
||||
* without validating the data itself.
|
||||
*/
|
||||
|
||||
// VALUE:
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, password_data->current_password,
|
||||
sizeof(password_data->current_password));
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// COMMON:
|
||||
ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size,
|
||||
&password_data->common);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// PSWD_MIN_LENGTH:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&password_data->min_password_length);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// PSWD_MAX_LENGTH:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&password_data->max_password_length);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// PSWD_SIZE:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&password_data->encodings_size);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
if (password_data->encodings_size > MAX_ENCODINGS_SIZE) {
|
||||
/* Report a message and limit possible values size to maximum value */
|
||||
pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
password_data->encodings_size = MAX_ENCODINGS_SIZE;
|
||||
}
|
||||
|
||||
// PSWD_ENCODINGS:
|
||||
for (values = 0; values < password_data->encodings_size; values++) {
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
|
||||
password_data->encodings[values],
|
||||
sizeof(password_data->encodings[values]));
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// PSWD_IS_SET:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, &isreadonly);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
password_data->is_enabled = isreadonly ? true : false;
|
||||
|
||||
buffer_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_password_buffer_data()
|
||||
* Populate all properties for an instance under password object attribute
|
||||
*
|
||||
* @buffer_ptr: Buffer pointer
|
||||
* @buffer_size: Buffer size
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
password_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
/* Populate Password attributes */
|
||||
ret = hp_populate_password_elements_from_buffer(buffer_ptr, buffer_size,
|
||||
instance_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hp_friendly_user_name_update(password_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
password_data->common.display_name,
|
||||
sizeof(password_data->common.display_name));
|
||||
if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
|
||||
return sysfs_create_group(attr_name_kobj, &password_attr_group);
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &password_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_exit_password_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void hp_exit_password_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < bioscfg_drv.password_instances_count;
|
||||
instance_id++) {
|
||||
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))
|
||||
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);
|
||||
bioscfg_drv.password_data = NULL;
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to secure platform management object type
|
||||
* attributes under BIOS PASSWORD for use with hp-bioscfg driver
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
|
||||
static const char * const spm_state_types[] = {
|
||||
"not provisioned",
|
||||
"provisioned",
|
||||
"provisioning in progress",
|
||||
};
|
||||
|
||||
static const char * const spm_mechanism_types[] = {
|
||||
"not provisioned",
|
||||
"signing-key",
|
||||
"endorsement-key",
|
||||
};
|
||||
|
||||
struct secureplatform_provisioning_data {
|
||||
u8 state;
|
||||
u8 version[2];
|
||||
u8 reserved1;
|
||||
u32 features;
|
||||
u32 nonce;
|
||||
u8 reserved2[28];
|
||||
u8 sk_mod[MAX_KEY_MOD_SIZE];
|
||||
u8 kek_mod[MAX_KEY_MOD_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* hp_calculate_security_buffer() - determines size of security buffer
|
||||
* for authentication scheme
|
||||
*
|
||||
* @authentication: the authentication content
|
||||
*
|
||||
* Currently only supported type is Admin password
|
||||
*/
|
||||
size_t hp_calculate_security_buffer(const char *authentication)
|
||||
{
|
||||
size_t size, authlen;
|
||||
|
||||
if (!authentication)
|
||||
return sizeof(u16) * 2;
|
||||
|
||||
authlen = strlen(authentication);
|
||||
if (!authlen)
|
||||
return sizeof(u16) * 2;
|
||||
|
||||
size = sizeof(u16) + authlen * sizeof(u16);
|
||||
if (!strstarts(authentication, BEAM_PREFIX))
|
||||
size += strlen(UTF_PREFIX) * sizeof(u16);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_security_buffer() - builds a security buffer for
|
||||
* authentication scheme
|
||||
*
|
||||
* @authbuf: the security buffer
|
||||
* @authentication: the authentication content
|
||||
*
|
||||
* Currently only supported type is PLAIN TEXT
|
||||
*/
|
||||
int hp_populate_security_buffer(u16 *authbuf, const char *authentication)
|
||||
{
|
||||
u16 *auth = authbuf;
|
||||
char *strprefix = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (strstarts(authentication, BEAM_PREFIX)) {
|
||||
/*
|
||||
* BEAM_PREFIX is append to authbuf when a signature
|
||||
* is provided and Sure Admin is enabled in BIOS
|
||||
*/
|
||||
/* BEAM_PREFIX found, convert part to unicode */
|
||||
auth = hp_ascii_to_utf16_unicode(auth, authentication);
|
||||
if (!auth)
|
||||
return -EINVAL;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* UTF-16 prefix is append to the * authbuf when a BIOS
|
||||
* admin password is configured in BIOS
|
||||
*/
|
||||
|
||||
/* append UTF_PREFIX to part and then convert it to unicode */
|
||||
strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
|
||||
authentication);
|
||||
if (!strprefix)
|
||||
return -ENOMEM;
|
||||
|
||||
auth = hp_ascii_to_utf16_unicode(auth, strprefix);
|
||||
kfree(strprefix);
|
||||
|
||||
if (!auth) {
|
||||
ret = -EINVAL;
|
||||
goto out_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
out_buffer:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t update_spm_state(void)
|
||||
{
|
||||
struct secureplatform_provisioning_data data;
|
||||
int ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
|
||||
HPWMI_SECUREPLATFORM, &data, 0,
|
||||
sizeof(data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bioscfg_drv.spm_data.mechanism = data.state;
|
||||
if (bioscfg_drv.spm_data.mechanism)
|
||||
bioscfg_drv.spm_data.is_enabled = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t statusbin(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
struct secureplatform_provisioning_data *buf)
|
||||
{
|
||||
int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
|
||||
HPWMI_SECUREPLATFORM, buf, 0,
|
||||
sizeof(*buf));
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sizeof(struct secureplatform_provisioning_data);
|
||||
}
|
||||
|
||||
/*
|
||||
* status_show - Reads SPM status
|
||||
*/
|
||||
static ssize_t status_show(struct kobject *kobj, struct kobj_attribute
|
||||
*attr, char *buf)
|
||||
{
|
||||
int ret, i;
|
||||
int len = 0;
|
||||
struct secureplatform_provisioning_data data;
|
||||
|
||||
ret = statusbin(kobj, attr, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 'status' is a read-only file that returns ASCII text in
|
||||
* JSON format reporting the status information.
|
||||
*
|
||||
* "State": "not provisioned | provisioned | provisioning in progress ",
|
||||
* "Version": " Major. Minor ",
|
||||
* "Nonce": <16-bit unsigned number display in base 10>,
|
||||
* "FeaturesInUse": <16-bit unsigned number display in base 10>,
|
||||
* "EndorsementKeyMod": "<256 bytes in base64>",
|
||||
* "SigningKeyMod": "<256 bytes in base64>"
|
||||
*/
|
||||
|
||||
len += sysfs_emit_at(buf, len, "{\n");
|
||||
len += sysfs_emit_at(buf, len, "\t\"State\": \"%s\",\n",
|
||||
spm_state_types[data.state]);
|
||||
len += sysfs_emit_at(buf, len, "\t\"Version\": \"%d.%d\"",
|
||||
data.version[0], data.version[1]);
|
||||
|
||||
/*
|
||||
* state == 0 means secure platform management
|
||||
* feature is not configured in BIOS.
|
||||
*/
|
||||
if (data.state == 0) {
|
||||
len += sysfs_emit_at(buf, len, "\n");
|
||||
goto status_exit;
|
||||
} else {
|
||||
len += sysfs_emit_at(buf, len, ",\n");
|
||||
}
|
||||
|
||||
len += sysfs_emit_at(buf, len, "\t\"Nonce\": %d,\n", data.nonce);
|
||||
len += sysfs_emit_at(buf, len, "\t\"FeaturesInUse\": %d,\n", data.features);
|
||||
len += sysfs_emit_at(buf, len, "\t\"EndorsementKeyMod\": \"");
|
||||
|
||||
for (i = 255; i >= 0; i--)
|
||||
len += sysfs_emit_at(buf, len, " %u", data.kek_mod[i]);
|
||||
|
||||
len += sysfs_emit_at(buf, len, " \",\n");
|
||||
len += sysfs_emit_at(buf, len, "\t\"SigningKeyMod\": \"");
|
||||
|
||||
for (i = 255; i >= 0; i--)
|
||||
len += sysfs_emit_at(buf, len, " %u", data.sk_mod[i]);
|
||||
|
||||
/* Return buf contents */
|
||||
len += sysfs_emit_at(buf, len, " \"\n");
|
||||
|
||||
status_exit:
|
||||
len += sysfs_emit_at(buf, len, "}\n");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_status = __ATTR_RO(status);
|
||||
|
||||
ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm);
|
||||
static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled);
|
||||
|
||||
static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
spm_mechanism_types[bioscfg_drv.spm_data.mechanism]);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);
|
||||
|
||||
static ssize_t sk_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
int length;
|
||||
|
||||
length = count;
|
||||
if (buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
/* allocate space and copy current signing key */
|
||||
bioscfg_drv.spm_data.signing_key = kmemdup(buf, length, GFP_KERNEL);
|
||||
if (!bioscfg_drv.spm_data.signing_key)
|
||||
return -ENOMEM;
|
||||
|
||||
/* submit signing key payload */
|
||||
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
|
||||
HPWMI_SECUREPLATFORM,
|
||||
(void *)bioscfg_drv.spm_data.signing_key,
|
||||
count, 0);
|
||||
|
||||
if (!ret) {
|
||||
bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
|
||||
hp_set_reboot_and_signal_event();
|
||||
}
|
||||
|
||||
kfree(bioscfg_drv.spm_data.signing_key);
|
||||
bioscfg_drv.spm_data.signing_key = NULL;
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk);
|
||||
|
||||
static ssize_t kek_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
int length;
|
||||
|
||||
length = count;
|
||||
if (buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
/* allocate space and copy current signing key */
|
||||
bioscfg_drv.spm_data.endorsement_key = kmemdup(buf, length, GFP_KERNEL);
|
||||
if (!bioscfg_drv.spm_data.endorsement_key) {
|
||||
ret = -ENOMEM;
|
||||
goto exit_kek;
|
||||
}
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
|
||||
HPWMI_SECUREPLATFORM,
|
||||
(void *)bioscfg_drv.spm_data.endorsement_key,
|
||||
count, 0);
|
||||
|
||||
if (!ret) {
|
||||
bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
|
||||
hp_set_reboot_and_signal_event();
|
||||
}
|
||||
|
||||
exit_kek:
|
||||
kfree(bioscfg_drv.spm_data.endorsement_key);
|
||||
bioscfg_drv.spm_data.endorsement_key = NULL;
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek);
|
||||
|
||||
static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", BIOS_SPM);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_role = __ATTR_RO(role);
|
||||
|
||||
static ssize_t auth_token_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret = 0;
|
||||
int length;
|
||||
|
||||
length = count;
|
||||
if (buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
/* allocate space and copy current auth token */
|
||||
bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL);
|
||||
if (!bioscfg_drv.spm_data.auth_token) {
|
||||
ret = -ENOMEM;
|
||||
goto exit_token;
|
||||
}
|
||||
|
||||
return count;
|
||||
|
||||
exit_token:
|
||||
kfree(bioscfg_drv.spm_data.auth_token);
|
||||
bioscfg_drv.spm_data.auth_token = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token);
|
||||
|
||||
static struct attribute *secure_platform_attrs[] = {
|
||||
&password_spm_is_key_enabled.attr,
|
||||
&password_spm_signing_key.attr,
|
||||
&password_spm_endorsement_key.attr,
|
||||
&password_spm_key_mechanism.attr,
|
||||
&password_spm_status.attr,
|
||||
&password_spm_role.attr,
|
||||
&password_spm_auth_token.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group secure_platform_attr_group = {
|
||||
.attrs = secure_platform_attrs,
|
||||
};
|
||||
|
||||
void hp_exit_secure_platform_attributes(void)
|
||||
{
|
||||
/* remove secure platform sysfs entry and free key data*/
|
||||
|
||||
kfree(bioscfg_drv.spm_data.endorsement_key);
|
||||
bioscfg_drv.spm_data.endorsement_key = NULL;
|
||||
|
||||
kfree(bioscfg_drv.spm_data.signing_key);
|
||||
bioscfg_drv.spm_data.signing_key = NULL;
|
||||
|
||||
kfree(bioscfg_drv.spm_data.auth_token);
|
||||
bioscfg_drv.spm_data.auth_token = NULL;
|
||||
|
||||
if (bioscfg_drv.spm_data.attr_name_kobj)
|
||||
sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj,
|
||||
&secure_platform_attr_group);
|
||||
}
|
||||
|
||||
int hp_populate_secure_platform_data(struct kobject *attr_name_kobj)
|
||||
{
|
||||
/* Populate data for Secure Platform Management */
|
||||
bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj;
|
||||
|
||||
strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR,
|
||||
sizeof(bioscfg_drv.spm_data.attribute_name));
|
||||
|
||||
bioscfg_drv.spm_data.is_enabled = 0;
|
||||
bioscfg_drv.spm_data.mechanism = 0;
|
||||
bioscfg_drv.pending_reboot = false;
|
||||
update_spm_state();
|
||||
|
||||
bioscfg_drv.spm_data.endorsement_key = NULL;
|
||||
bioscfg_drv.spm_data.signing_key = NULL;
|
||||
bioscfg_drv.spm_data.auth_token = NULL;
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group);
|
||||
}
|
|
@ -0,0 +1,395 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to string type attributes under
|
||||
* HP_WMI_BIOS_STRING_GUID for use with hp-bioscfg driver.
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
|
||||
#define WMI_STRING_TYPE "HPBIOS_BIOSString"
|
||||
|
||||
GET_INSTANCE_ID(string);
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int instance_id = get_string_instance_id(kobj);
|
||||
|
||||
if (instance_id < 0)
|
||||
return -EIO;
|
||||
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
bioscfg_drv.string_data[instance_id].current_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_string_input() -
|
||||
* Validate input of current_value against min and max lengths
|
||||
*
|
||||
* @instance_id: The instance on which input is validated
|
||||
* @buf: Input value
|
||||
*/
|
||||
static int validate_string_input(int instance_id, const char *buf)
|
||||
{
|
||||
int in_len = strlen(buf);
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
/* BIOS treats it as a read only attribute */
|
||||
if (string_data->common.is_readonly)
|
||||
return -EIO;
|
||||
|
||||
if (in_len < string_data->min_length || in_len > string_data->max_length)
|
||||
return -ERANGE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_string_value(int instance_id, char *attr_value)
|
||||
{
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
/* Write settings to BIOS */
|
||||
strscpy(string_data->current_value, attr_value, sizeof(string_data->current_value));
|
||||
}
|
||||
|
||||
/*
|
||||
* ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, string);
|
||||
* static struct kobj_attribute string_display_langcode =
|
||||
* __ATTR_RO(display_name_language_code);
|
||||
*/
|
||||
|
||||
ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, string);
|
||||
static struct kobj_attribute string_display_name =
|
||||
__ATTR_RO(display_name);
|
||||
|
||||
ATTRIBUTE_PROPERTY_STORE(current_value, string);
|
||||
static struct kobj_attribute string_current_val =
|
||||
__ATTR_RW_MODE(current_value, 0644);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(min_length, string);
|
||||
static struct kobj_attribute string_min_length =
|
||||
__ATTR_RO(min_length);
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(max_length, string);
|
||||
static struct kobj_attribute string_max_length =
|
||||
__ATTR_RO(max_length);
|
||||
|
||||
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "string\n");
|
||||
}
|
||||
|
||||
static struct kobj_attribute string_type =
|
||||
__ATTR_RO(type);
|
||||
|
||||
static struct attribute *string_attrs[] = {
|
||||
&common_display_langcode.attr,
|
||||
&string_display_name.attr,
|
||||
&string_current_val.attr,
|
||||
&string_min_length.attr,
|
||||
&string_max_length.attr,
|
||||
&string_type.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group string_attr_group = {
|
||||
.attrs = string_attrs,
|
||||
};
|
||||
|
||||
int hp_alloc_string_data(void)
|
||||
{
|
||||
bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID);
|
||||
bioscfg_drv.string_data = kcalloc(bioscfg_drv.string_instances_count,
|
||||
sizeof(*bioscfg_drv.string_data), GFP_KERNEL);
|
||||
if (!bioscfg_drv.string_data) {
|
||||
bioscfg_drv.string_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expected Values types associated with each element */
|
||||
static const acpi_object_type expected_string_types[] = {
|
||||
[NAME] = ACPI_TYPE_STRING,
|
||||
[VALUE] = ACPI_TYPE_STRING,
|
||||
[PATH] = ACPI_TYPE_STRING,
|
||||
[IS_READONLY] = ACPI_TYPE_INTEGER,
|
||||
[DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
|
||||
[REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
|
||||
[SEQUENCE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
|
||||
[PREREQUISITES] = ACPI_TYPE_STRING,
|
||||
[SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
|
||||
[STR_MIN_LENGTH] = ACPI_TYPE_INTEGER,
|
||||
[STR_MAX_LENGTH] = ACPI_TYPE_INTEGER,
|
||||
};
|
||||
|
||||
static int hp_populate_string_elements_from_package(union acpi_object *string_obj,
|
||||
int string_obj_count,
|
||||
int instance_id)
|
||||
{
|
||||
char *str_value = NULL;
|
||||
int value_len;
|
||||
int ret = 0;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int size;
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
if (!string_obj)
|
||||
return -EINVAL;
|
||||
|
||||
for (elem = 1, eloc = 1; elem < string_obj_count; elem++, eloc++) {
|
||||
/* ONLY look at the first STRING_ELEM_CNT elements */
|
||||
if (eloc == STR_ELEM_CNT)
|
||||
goto exit_string_package;
|
||||
|
||||
switch (string_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
if (elem != PREREQUISITES) {
|
||||
ret = hp_convert_hexstr_to_str(string_obj[elem].string.pointer,
|
||||
string_obj[elem].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
int_value = (u32)string_obj[elem].integer.value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported object type [%d]\n", string_obj[elem].type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_string_types[eloc] != string_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_string_types[eloc], elem, string_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Assign appropriate element value to corresponding field*/
|
||||
switch (eloc) {
|
||||
case VALUE:
|
||||
strscpy(string_data->current_value,
|
||||
str_value, sizeof(string_data->current_value));
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(string_data->common.path, str_value,
|
||||
sizeof(string_data->common.path));
|
||||
break;
|
||||
case IS_READONLY:
|
||||
string_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
string_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
string_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
string_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
string_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (string_data->common.prerequisites_size == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, string_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem >= string_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(string_obj[elem + reqs].string.pointer,
|
||||
string_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(string_data->common.prerequisites[reqs],
|
||||
str_value,
|
||||
sizeof(string_data->common.prerequisites[reqs]));
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
string_data->common.security_level = int_value;
|
||||
break;
|
||||
case STR_MIN_LENGTH:
|
||||
string_data->min_length = int_value;
|
||||
break;
|
||||
case STR_MAX_LENGTH:
|
||||
string_data->max_length = int_value;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
|
||||
exit_string_package:
|
||||
kfree(str_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_string_package_data() -
|
||||
* Populate all properties of an instance under string attribute
|
||||
*
|
||||
* @string_obj: ACPI object with string data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_string_package_data(union acpi_object *string_obj,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
string_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_string_elements_from_package(string_obj,
|
||||
string_obj->package.count,
|
||||
instance_id);
|
||||
|
||||
hp_update_attribute_permissions(string_data->common.is_readonly,
|
||||
&string_current_val);
|
||||
hp_friendly_user_name_update(string_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
string_data->common.display_name,
|
||||
sizeof(string_data->common.display_name));
|
||||
return sysfs_create_group(attr_name_kobj, &string_attr_group);
|
||||
}
|
||||
|
||||
static int hp_populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
/*
|
||||
* Only data relevant to this driver and its functionality is
|
||||
* read. BIOS defines the order in which each * element is
|
||||
* read. Element 0 data is not relevant to this
|
||||
* driver hence it is ignored. For clarity, all element names
|
||||
* (DISPLAY_IN_UI) which defines the order in which is read
|
||||
* and the name matches the variable where the data is stored.
|
||||
*
|
||||
* In earlier implementation, reported errors were ignored
|
||||
* causing the data to remain uninitialized. It is not
|
||||
* possible to determine if data read from BIOS is valid or
|
||||
* not. It is for this reason functions may return a error
|
||||
* without validating the data itself.
|
||||
*/
|
||||
|
||||
// VALUE:
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, string_data->current_value,
|
||||
sizeof(string_data->current_value));
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// COMMON:
|
||||
ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &string_data->common);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// STR_MIN_LENGTH:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&string_data->min_length);
|
||||
if (ret < 0)
|
||||
goto buffer_exit;
|
||||
|
||||
// STR_MAX_LENGTH:
|
||||
ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
|
||||
&string_data->max_length);
|
||||
|
||||
buffer_exit:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_populate_string_buffer_data() -
|
||||
* Populate all properties of an instance under string attribute
|
||||
*
|
||||
* @buffer_ptr: Buffer pointer
|
||||
* @buffer_size: Buffer size
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
int ret = 0;
|
||||
|
||||
string_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
ret = hp_populate_string_elements_from_buffer(buffer_ptr, buffer_size,
|
||||
instance_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hp_update_attribute_permissions(string_data->common.is_readonly,
|
||||
&string_current_val);
|
||||
hp_friendly_user_name_update(string_data->common.path,
|
||||
attr_name_kobj->name,
|
||||
string_data->common.display_name,
|
||||
sizeof(string_data->common.display_name));
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &string_attr_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_exit_string_attributes() - Clear all attribute data
|
||||
*
|
||||
* Clears all data allocated for this group of attributes
|
||||
*/
|
||||
void hp_exit_string_attributes(void)
|
||||
{
|
||||
int instance_id;
|
||||
|
||||
for (instance_id = 0; instance_id < bioscfg_drv.string_instances_count;
|
||||
instance_id++) {
|
||||
struct kobject *attr_name_kobj =
|
||||
bioscfg_drv.string_data[instance_id].attr_name_kobj;
|
||||
|
||||
if (attr_name_kobj)
|
||||
sysfs_remove_group(attr_name_kobj, &string_attr_group);
|
||||
}
|
||||
bioscfg_drv.string_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.string_data);
|
||||
bioscfg_drv.string_data = NULL;
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Functions corresponding to sure start object type attributes under
|
||||
* BIOS for use with hp-bioscfg driver
|
||||
*
|
||||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include "bioscfg.h"
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Maximum number of log entries supported when log entry size is 16
|
||||
* bytes. This value is calculated by dividing 4096 (page size) by
|
||||
* log entry size.
|
||||
*/
|
||||
#define LOG_MAX_ENTRIES 254
|
||||
|
||||
/*
|
||||
* Current Log entry size. This value size will change in the
|
||||
* future. The driver reads a total of 128 bytes for each log entry
|
||||
* provided by BIOS but only the first 16 bytes are used/read.
|
||||
*/
|
||||
#define LOG_ENTRY_SIZE 16
|
||||
|
||||
/*
|
||||
* audit_log_entry_count_show - Reports the number of
|
||||
* existing audit log entries available
|
||||
* to be read
|
||||
*/
|
||||
static ssize_t audit_log_entry_count_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
u32 count = 0;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
|
||||
HPWMI_SURESTART,
|
||||
&count, 1, sizeof(count));
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE,
|
||||
LOG_MAX_ENTRIES);
|
||||
}
|
||||
|
||||
/*
|
||||
* audit_log_entries_show() - Return all entries found in log file
|
||||
*/
|
||||
static ssize_t audit_log_entries_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
u32 count = 0;
|
||||
u8 audit_log_buffer[128];
|
||||
|
||||
// Get the number of event logs
|
||||
ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
|
||||
HPWMI_SURESTART,
|
||||
&count, 1, sizeof(count));
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The show() api will not work if the audit logs ever go
|
||||
* beyond 4KB
|
||||
*/
|
||||
if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* We are guaranteed the buffer is 4KB so today all the event
|
||||
* logs will fit
|
||||
*/
|
||||
for (i = 0; i < count; i++) {
|
||||
audit_log_buffer[0] = i + 1;
|
||||
|
||||
/*
|
||||
* read audit log entry at a time. 'buf' input value
|
||||
* provides the audit log entry to be read. On
|
||||
* input, Byte 0 = Audit Log entry number from
|
||||
* beginning (1..254)
|
||||
* Entry number 1 is the newest entry whereas the
|
||||
* highest entry number (number of entries) is the
|
||||
* oldest entry.
|
||||
*/
|
||||
ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
|
||||
HPWMI_SURESTART,
|
||||
audit_log_buffer, 1, 128);
|
||||
|
||||
if (ret < 0 || (LOG_ENTRY_SIZE * i) > PAGE_SIZE) {
|
||||
/*
|
||||
* Encountered a failure while reading
|
||||
* individual logs. Only a partial list of
|
||||
* audit log will be returned.
|
||||
*/
|
||||
break;
|
||||
} else {
|
||||
memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
|
||||
buf += LOG_ENTRY_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
return i * LOG_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
static struct kobj_attribute sure_start_audit_log_entry_count = __ATTR_RO(audit_log_entry_count);
|
||||
static struct kobj_attribute sure_start_audit_log_entries = __ATTR_RO(audit_log_entries);
|
||||
|
||||
static struct attribute *sure_start_attrs[] = {
|
||||
&sure_start_audit_log_entry_count.attr,
|
||||
&sure_start_audit_log_entries.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group sure_start_attr_group = {
|
||||
.attrs = sure_start_attrs,
|
||||
};
|
||||
|
||||
void hp_exit_sure_start_attributes(void)
|
||||
{
|
||||
sysfs_remove_group(bioscfg_drv.sure_start_attr_kobj,
|
||||
&sure_start_attr_group);
|
||||
}
|
||||
|
||||
int hp_populate_sure_start_data(struct kobject *attr_name_kobj)
|
||||
{
|
||||
bioscfg_drv.sure_start_attr_kobj = attr_name_kobj;
|
||||
return sysfs_create_group(attr_name_kobj, &sure_start_attr_group);
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
@ -85,6 +86,31 @@ enum {
|
|||
SALS_FNLOCK_OFF = 0xf,
|
||||
};
|
||||
|
||||
/*
|
||||
* These correspond to the number of supported states - 1
|
||||
* Future keyboard types may need a new system, if there's a collision
|
||||
* KBD_BL_TRISTATE_AUTO has no way to report or set the auto state
|
||||
* so it effectively has 3 states, but needs to handle 4
|
||||
*/
|
||||
enum {
|
||||
KBD_BL_STANDARD = 1,
|
||||
KBD_BL_TRISTATE = 2,
|
||||
KBD_BL_TRISTATE_AUTO = 3,
|
||||
};
|
||||
|
||||
#define KBD_BL_QUERY_TYPE 0x1
|
||||
#define KBD_BL_TRISTATE_TYPE 0x5
|
||||
#define KBD_BL_TRISTATE_AUTO_TYPE 0x7
|
||||
|
||||
#define KBD_BL_COMMAND_GET 0x2
|
||||
#define KBD_BL_COMMAND_SET 0x3
|
||||
#define KBD_BL_COMMAND_TYPE GENMASK(7, 4)
|
||||
|
||||
#define KBD_BL_GET_BRIGHTNESS GENMASK(15, 1)
|
||||
#define KBD_BL_SET_BRIGHTNESS GENMASK(19, 16)
|
||||
|
||||
#define KBD_BL_KBLC_CHANGED_EVENT 12
|
||||
|
||||
struct ideapad_dytc_priv {
|
||||
enum platform_profile_option current_profile;
|
||||
struct platform_profile_handler pprof;
|
||||
|
@ -122,6 +148,7 @@ struct ideapad_private {
|
|||
} features;
|
||||
struct {
|
||||
bool initialized;
|
||||
int type;
|
||||
struct led_classdev led;
|
||||
unsigned int last_brightness;
|
||||
} kbd_bl;
|
||||
|
@ -242,6 +269,16 @@ static int exec_sals(acpi_handle handle, unsigned long arg)
|
|||
return exec_simple_method(handle, "SALS", arg);
|
||||
}
|
||||
|
||||
static int exec_kblc(acpi_handle handle, unsigned long arg)
|
||||
{
|
||||
return exec_simple_method(handle, "KBLC", arg);
|
||||
}
|
||||
|
||||
static int eval_kblc(acpi_handle handle, unsigned long cmd, unsigned long *res)
|
||||
{
|
||||
return eval_int_with_arg(handle, "KBLC", cmd, res);
|
||||
}
|
||||
|
||||
static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res)
|
||||
{
|
||||
return eval_int_with_arg(handle, "DYTC", cmd, res);
|
||||
|
@ -1275,16 +1312,47 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
|
|||
/*
|
||||
* keyboard backlight
|
||||
*/
|
||||
static int ideapad_kbd_bl_check_tristate(int type)
|
||||
{
|
||||
return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
|
||||
}
|
||||
|
||||
static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
|
||||
{
|
||||
unsigned long hals;
|
||||
unsigned long value;
|
||||
int err;
|
||||
|
||||
err = eval_hals(priv->adev->handle, &hals);
|
||||
if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
|
||||
err = eval_kblc(priv->adev->handle,
|
||||
FIELD_PREP(KBD_BL_COMMAND_TYPE, priv->kbd_bl.type) |
|
||||
KBD_BL_COMMAND_GET,
|
||||
&value);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Convert returned value to brightness level */
|
||||
value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value);
|
||||
|
||||
/* Off, low or high */
|
||||
if (value <= priv->kbd_bl.led.max_brightness)
|
||||
return value;
|
||||
|
||||
/* Auto, report as off */
|
||||
if (value == priv->kbd_bl.led.max_brightness + 1)
|
||||
return 0;
|
||||
|
||||
/* Unknown value */
|
||||
dev_warn(&priv->platform_device->dev,
|
||||
"Unknown keyboard backlight value: %lu", value);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = eval_hals(priv->adev->handle, &value);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
|
||||
return !!test_bit(HALS_KBD_BL_STATE_BIT, &value);
|
||||
}
|
||||
|
||||
static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
|
||||
|
@ -1296,7 +1364,21 @@ static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_cla
|
|||
|
||||
static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness)
|
||||
{
|
||||
int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
|
||||
int err;
|
||||
unsigned long value;
|
||||
int type = priv->kbd_bl.type;
|
||||
|
||||
if (ideapad_kbd_bl_check_tristate(type)) {
|
||||
if (brightness > priv->kbd_bl.led.max_brightness)
|
||||
return -EINVAL;
|
||||
|
||||
value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) |
|
||||
FIELD_PREP(KBD_BL_COMMAND_TYPE, type) |
|
||||
KBD_BL_COMMAND_SET;
|
||||
err = exec_kblc(priv->adev->handle, value);
|
||||
} else {
|
||||
err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
|
||||
}
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
@ -1349,8 +1431,13 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
|
|||
|
||||
priv->kbd_bl.last_brightness = brightness;
|
||||
|
||||
if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
|
||||
priv->kbd_bl.led.max_brightness = 2;
|
||||
} else {
|
||||
priv->kbd_bl.led.max_brightness = 1;
|
||||
}
|
||||
|
||||
priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
|
||||
priv->kbd_bl.led.max_brightness = 1;
|
||||
priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get;
|
||||
priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set;
|
||||
priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED;
|
||||
|
@ -1461,6 +1548,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
case 2:
|
||||
ideapad_backlight_notify_power(priv);
|
||||
break;
|
||||
case KBD_BL_KBLC_CHANGED_EVENT:
|
||||
case 1:
|
||||
/*
|
||||
* Some IdeaPads report event 1 every ~20
|
||||
|
@ -1562,13 +1650,31 @@ static void ideapad_check_features(struct ideapad_private *priv)
|
|||
if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val))
|
||||
priv->features.fn_lock = true;
|
||||
|
||||
if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val))
|
||||
if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) {
|
||||
priv->features.kbd_bl = true;
|
||||
priv->kbd_bl.type = KBD_BL_STANDARD;
|
||||
}
|
||||
|
||||
if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val))
|
||||
priv->features.usb_charging = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (acpi_has_method(handle, "KBLC")) {
|
||||
if (!eval_kblc(priv->adev->handle, KBD_BL_QUERY_TYPE, &val)) {
|
||||
if (val == KBD_BL_TRISTATE_TYPE) {
|
||||
priv->features.kbd_bl = true;
|
||||
priv->kbd_bl.type = KBD_BL_TRISTATE;
|
||||
} else if (val == KBD_BL_TRISTATE_AUTO_TYPE) {
|
||||
priv->features.kbd_bl = true;
|
||||
priv->kbd_bl.type = KBD_BL_TRISTATE_AUTO;
|
||||
} else {
|
||||
dev_warn(&priv->platform_device->dev,
|
||||
"Unknown keyboard type: %lu",
|
||||
val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI_WMI)
|
||||
|
|
|
@ -47,10 +47,17 @@
|
|||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/intel_tpmi.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/string_helpers.h>
|
||||
|
||||
#include "vsec.h"
|
||||
|
||||
|
@ -83,12 +90,14 @@ struct intel_tpmi_pfs_entry {
|
|||
* @vsec_offset: Starting MMIO address for this feature in bytes. Essentially
|
||||
* this offset = "Address" from VSEC header + PFS Capability
|
||||
* offset for this feature entry.
|
||||
* @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device
|
||||
*
|
||||
* Represents TPMI instance information for one TPMI ID.
|
||||
*/
|
||||
struct intel_tpmi_pm_feature {
|
||||
struct intel_tpmi_pfs_entry pfs_header;
|
||||
unsigned int vsec_offset;
|
||||
struct intel_vsec_device *vsec_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -98,6 +107,8 @@ struct intel_tpmi_pm_feature {
|
|||
* @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features
|
||||
* @pfs_start: Start of PFS offset for the TPMI instances in this device
|
||||
* @plat_info: Stores platform info which can be used by the client drivers
|
||||
* @tpmi_control_mem: Memory mapped IO for getting control information
|
||||
* @dbgfs_dir: debugfs entry pointer
|
||||
*
|
||||
* Stores the information for all TPMI devices enumerated from a single PCI device.
|
||||
*/
|
||||
|
@ -107,6 +118,8 @@ struct intel_tpmi_info {
|
|||
int feature_count;
|
||||
u64 pfs_start;
|
||||
struct intel_tpmi_plat_info plat_info;
|
||||
void __iomem *tpmi_control_mem;
|
||||
struct dentry *dbgfs_dir;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -139,9 +152,19 @@ enum intel_tpmi_id {
|
|||
TPMI_ID_PEM = 1, /* Power and Perf excursion Monitor */
|
||||
TPMI_ID_UNCORE = 2, /* Uncore Frequency Scaling */
|
||||
TPMI_ID_SST = 5, /* Speed Select Technology */
|
||||
TPMI_CONTROL_ID = 0x80, /* Special ID for getting feature status */
|
||||
TPMI_INFO_ID = 0x81, /* Special ID for PCI BDF and Package ID information */
|
||||
};
|
||||
|
||||
/*
|
||||
* The size from hardware is in u32 units. This size is from a trusted hardware,
|
||||
* but better to verify for pre silicon platforms. Set size to 0, when invalid.
|
||||
*/
|
||||
#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \
|
||||
({ \
|
||||
pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \
|
||||
})
|
||||
|
||||
/* Used during auxbus device creation */
|
||||
static DEFINE_IDA(intel_vsec_tpmi_ida);
|
||||
|
||||
|
@ -175,6 +198,349 @@ struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, INTEL_TPMI);
|
||||
|
||||
/* TPMI Control Interface */
|
||||
|
||||
#define TPMI_CONTROL_STATUS_OFFSET 0x00
|
||||
#define TPMI_COMMAND_OFFSET 0x08
|
||||
|
||||
/*
|
||||
* Spec is calling for max 1 seconds to get ownership at the worst
|
||||
* case. Read at 10 ms timeouts and repeat up to 1 second.
|
||||
*/
|
||||
#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC)
|
||||
#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC)
|
||||
|
||||
#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC)
|
||||
#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC
|
||||
|
||||
/* TPMI Control status register defines */
|
||||
|
||||
#define TPMI_CONTROL_STATUS_RB BIT_ULL(0)
|
||||
|
||||
#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4)
|
||||
#define TPMI_OWNER_NONE 0
|
||||
#define TPMI_OWNER_IN_BAND 1
|
||||
|
||||
#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6)
|
||||
#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8)
|
||||
#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16)
|
||||
|
||||
#define TPMI_CMD_PKT_LEN 2
|
||||
#define TPMI_CMD_STATUS_SUCCESS 0x40
|
||||
|
||||
/* TPMI command data registers */
|
||||
#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0)
|
||||
#define TMPI_CONTROL_DATA_VAL GENMASK_ULL(63, 32)
|
||||
#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40)
|
||||
|
||||
/* Command to send via control interface */
|
||||
#define TPMI_CONTROL_GET_STATE_CMD 0x10
|
||||
|
||||
#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40)
|
||||
|
||||
#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16)
|
||||
|
||||
#define TPMI_STATE_DISABLED BIT_ULL(0)
|
||||
#define TPMI_STATE_LOCKED BIT_ULL(31)
|
||||
|
||||
/* Mutex to complete get feature status without interruption */
|
||||
static DEFINE_MUTEX(tpmi_dev_lock);
|
||||
|
||||
static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner)
|
||||
{
|
||||
u64 control;
|
||||
|
||||
return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
|
||||
control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control),
|
||||
TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US);
|
||||
}
|
||||
|
||||
static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id,
|
||||
int *locked, int *disabled)
|
||||
{
|
||||
u64 control, data;
|
||||
int ret;
|
||||
|
||||
if (!tpmi_info->tpmi_control_mem)
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&tpmi_dev_lock);
|
||||
|
||||
/* Wait for owner bit set to 0 (none) */
|
||||
ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
/* set command id to 0x10 for TPMI_GET_STATE */
|
||||
data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD);
|
||||
|
||||
/* 32 bits for DATA offset and +8 for feature_id field */
|
||||
data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id);
|
||||
|
||||
/* Write at command offset for qword access */
|
||||
writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
|
||||
|
||||
/* Wait for owner bit set to in-band */
|
||||
ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
/* Set Run Busy and packet length of 2 dwords */
|
||||
control = TPMI_CONTROL_STATUS_RB;
|
||||
control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN);
|
||||
|
||||
/* Write at status offset for qword access */
|
||||
writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
|
||||
|
||||
/* Wait for Run Busy clear */
|
||||
ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
|
||||
control, !(control & TPMI_CONTROL_STATUS_RB),
|
||||
TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US);
|
||||
if (ret)
|
||||
goto done_proc;
|
||||
|
||||
control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control);
|
||||
if (control != TPMI_CMD_STATUS_SUCCESS) {
|
||||
ret = -EBUSY;
|
||||
goto done_proc;
|
||||
}
|
||||
|
||||
/* Response is ready */
|
||||
data = readq(tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
|
||||
data = FIELD_GET(TMPI_CONTROL_DATA_VAL, data);
|
||||
|
||||
*disabled = 0;
|
||||
*locked = 0;
|
||||
|
||||
if (!(data & TPMI_STATE_DISABLED))
|
||||
*disabled = 1;
|
||||
|
||||
if (data & TPMI_STATE_LOCKED)
|
||||
*locked = 1;
|
||||
|
||||
ret = 0;
|
||||
|
||||
done_proc:
|
||||
/* Set CPL "completion" bit */
|
||||
writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
|
||||
|
||||
err_unlock:
|
||||
mutex_unlock(&tpmi_dev_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id,
|
||||
int *locked, int *disabled)
|
||||
{
|
||||
struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent);
|
||||
struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev);
|
||||
|
||||
return tpmi_read_feature_status(tpmi_info, feature_id, locked, disabled);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, INTEL_TPMI);
|
||||
|
||||
static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct intel_tpmi_info *tpmi_info = s->private;
|
||||
struct intel_tpmi_pm_feature *pfs;
|
||||
int locked, disabled, ret, i;
|
||||
|
||||
seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start);
|
||||
seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\n");
|
||||
for (i = 0; i < tpmi_info->feature_count; ++i) {
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &locked,
|
||||
&disabled);
|
||||
if (ret) {
|
||||
locked = 'U';
|
||||
disabled = 'U';
|
||||
} else {
|
||||
disabled = disabled ? 'Y' : 'N';
|
||||
locked = locked ? 'Y' : 'N';
|
||||
}
|
||||
seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%08x\t%c\t%c\n",
|
||||
pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries,
|
||||
pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset,
|
||||
pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg);
|
||||
|
||||
#define MEM_DUMP_COLUMN_COUNT 8
|
||||
|
||||
static int tpmi_mem_dump_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32);
|
||||
struct intel_tpmi_pm_feature *pfs = s->private;
|
||||
int count, ret = 0;
|
||||
void __iomem *mem;
|
||||
u32 off, size;
|
||||
u8 *buffer;
|
||||
|
||||
size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
|
||||
if (!size)
|
||||
return -EIO;
|
||||
|
||||
buffer = kmalloc(size, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
off = pfs->vsec_offset;
|
||||
|
||||
mutex_lock(&tpmi_dev_lock);
|
||||
|
||||
for (count = 0; count < pfs->pfs_header.num_entries; ++count) {
|
||||
seq_printf(s, "TPMI Instance:%d offset:0x%x\n", count, off);
|
||||
|
||||
mem = ioremap(off, size);
|
||||
if (!mem) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy_fromio(buffer, mem, size);
|
||||
|
||||
seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size,
|
||||
false);
|
||||
|
||||
iounmap(mem);
|
||||
|
||||
off += size;
|
||||
}
|
||||
|
||||
mutex_unlock(&tpmi_dev_lock);
|
||||
|
||||
kfree(buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump);
|
||||
|
||||
static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *m = file->private_data;
|
||||
struct intel_tpmi_pm_feature *pfs = m->private;
|
||||
u32 addr, value, punit, size;
|
||||
u32 num_elems, *array;
|
||||
void __iomem *mem;
|
||||
int ret;
|
||||
|
||||
size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
|
||||
if (!size)
|
||||
return -EIO;
|
||||
|
||||
ret = parse_int_array_user(userbuf, len, (int **)&array);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
num_elems = *array;
|
||||
if (num_elems != 3) {
|
||||
ret = -EINVAL;
|
||||
goto exit_write;
|
||||
}
|
||||
|
||||
punit = array[1];
|
||||
addr = array[2];
|
||||
value = array[3];
|
||||
|
||||
if (punit >= pfs->pfs_header.num_entries) {
|
||||
ret = -EINVAL;
|
||||
goto exit_write;
|
||||
}
|
||||
|
||||
if (addr >= size) {
|
||||
ret = -EINVAL;
|
||||
goto exit_write;
|
||||
}
|
||||
|
||||
mutex_lock(&tpmi_dev_lock);
|
||||
|
||||
mem = ioremap(pfs->vsec_offset + punit * size, size);
|
||||
if (!mem) {
|
||||
ret = -ENOMEM;
|
||||
goto unlock_mem_write;
|
||||
}
|
||||
|
||||
writel(value, mem + addr);
|
||||
|
||||
iounmap(mem);
|
||||
|
||||
ret = len;
|
||||
|
||||
unlock_mem_write:
|
||||
mutex_unlock(&tpmi_dev_lock);
|
||||
|
||||
exit_write:
|
||||
kfree(array);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mem_write_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mem_write_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mem_write_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations mem_write_ops = {
|
||||
.open = mem_write_open,
|
||||
.read = seq_read,
|
||||
.write = mem_write,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
#define tpmi_to_dev(info) (&info->vsec_dev->pcidev->dev)
|
||||
|
||||
static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info)
|
||||
{
|
||||
char name[64];
|
||||
int i;
|
||||
|
||||
snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_to_dev(tpmi_info)));
|
||||
tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL);
|
||||
|
||||
debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops);
|
||||
|
||||
for (i = 0; i < tpmi_info->feature_count; ++i) {
|
||||
struct intel_tpmi_pm_feature *pfs;
|
||||
struct dentry *dir;
|
||||
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id);
|
||||
dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir);
|
||||
|
||||
debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops);
|
||||
debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops);
|
||||
}
|
||||
}
|
||||
|
||||
static void tpmi_set_control_base(struct auxiliary_device *auxdev,
|
||||
struct intel_tpmi_info *tpmi_info,
|
||||
struct intel_tpmi_pm_feature *pfs)
|
||||
{
|
||||
void __iomem *mem;
|
||||
u32 size;
|
||||
|
||||
size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
mem = devm_ioremap(&auxdev->dev, pfs->vsec_offset, size);
|
||||
if (!mem)
|
||||
return;
|
||||
|
||||
/* mem is pointing to TPMI CONTROL base */
|
||||
tpmi_info->tpmi_control_mem = mem;
|
||||
}
|
||||
|
||||
static const char *intel_tpmi_name(enum intel_tpmi_id id)
|
||||
{
|
||||
switch (id) {
|
||||
|
@ -316,7 +682,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
|
|||
struct pci_dev *pci_dev = vsec_dev->pcidev;
|
||||
struct intel_tpmi_info *tpmi_info;
|
||||
u64 pfs_start = 0;
|
||||
int i;
|
||||
int ret, i;
|
||||
|
||||
tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL);
|
||||
if (!tpmi_info)
|
||||
|
@ -339,6 +705,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
|
|||
int size, ret;
|
||||
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
pfs->vsec_dev = vsec_dev;
|
||||
|
||||
res = &vsec_dev->resource[i];
|
||||
if (!res)
|
||||
|
@ -367,13 +734,29 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
|
|||
*/
|
||||
if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID)
|
||||
tpmi_process_info(tpmi_info, pfs);
|
||||
|
||||
if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)
|
||||
tpmi_set_control_base(auxdev, tpmi_info, pfs);
|
||||
}
|
||||
|
||||
tpmi_info->pfs_start = pfs_start;
|
||||
|
||||
auxiliary_set_drvdata(auxdev, tpmi_info);
|
||||
|
||||
return tpmi_create_devices(tpmi_info);
|
||||
ret = tpmi_create_devices(tpmi_info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Allow debugfs when security policy allows. Everything this debugfs
|
||||
* interface provides, can also be done via /dev/mem access. If
|
||||
* /dev/mem interface is locked, don't allow debugfs to present any
|
||||
* information. Also check for CAP_SYS_RAWIO as /dev/mem interface.
|
||||
*/
|
||||
if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO))
|
||||
tpmi_dbgfs_register(tpmi_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpmi_probe(struct auxiliary_device *auxdev,
|
||||
|
@ -382,11 +765,12 @@ static int tpmi_probe(struct auxiliary_device *auxdev,
|
|||
return intel_vsec_tpmi_init(auxdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove callback is not needed currently as there is no
|
||||
* cleanup required. All memory allocs are device managed. All
|
||||
* devices created by this modules are also device managed.
|
||||
*/
|
||||
static void tpmi_remove(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev);
|
||||
|
||||
debugfs_remove_recursive(tpmi_info->dbgfs_dir);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id tpmi_id_table[] = {
|
||||
{ .name = "intel_vsec.tpmi" },
|
||||
|
@ -397,6 +781,7 @@ MODULE_DEVICE_TABLE(auxiliary, tpmi_id_table);
|
|||
static struct auxiliary_driver tpmi_aux_driver = {
|
||||
.id_table = tpmi_id_table,
|
||||
.probe = tpmi_probe,
|
||||
.remove = tpmi_remove,
|
||||
};
|
||||
|
||||
module_auxiliary_driver(tpmi_aux_driver);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/i2c-mux.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/i2c-mux-reg.h>
|
||||
#include <linux/platform_data/mlxreg.h>
|
||||
|
@ -35,6 +36,7 @@
|
|||
#define MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET 0x09
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET 0x0a
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET 0x0b
|
||||
#define MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET 0x17
|
||||
#define MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET 0x19
|
||||
#define MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET 0x1c
|
||||
#define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d
|
||||
|
@ -62,6 +64,7 @@
|
|||
#define MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET 0x37
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET 0x3a
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b
|
||||
#define MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET 0x3c
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42
|
||||
|
@ -94,6 +97,9 @@
|
|||
#define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88
|
||||
#define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89
|
||||
#define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET 0x8e
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET 0x8f
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET 0x90
|
||||
#define MLXPLAT_CPLD_LPC_REG_EROT_OFFSET 0x91
|
||||
#define MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET 0x92
|
||||
#define MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET 0x93
|
||||
|
@ -128,6 +134,7 @@
|
|||
#define MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET 0xb9
|
||||
#define MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET 0xc2
|
||||
#define MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT 0xc3
|
||||
#define MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET 0xc4
|
||||
#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET 0xc7
|
||||
#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET 0xc8
|
||||
#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET 0xc9
|
||||
|
@ -236,6 +243,7 @@
|
|||
#define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4)
|
||||
#define MLXPLAT_CPLD_GWP_MASK GENMASK(0, 0)
|
||||
#define MLXPLAT_CPLD_EROT_MASK GENMASK(1, 0)
|
||||
#define MLXPLAT_CPLD_FU_CAP_MASK GENMASK(1, 0)
|
||||
#define MLXPLAT_CPLD_PWR_BUTTON_MASK BIT(0)
|
||||
#define MLXPLAT_CPLD_LATCH_RST_MASK BIT(6)
|
||||
#define MLXPLAT_CPLD_THERMAL1_PDB_MASK BIT(3)
|
||||
|
@ -248,6 +256,7 @@
|
|||
MLXPLAT_CPLD_PWM_PG_MASK)
|
||||
#define MLXPLAT_CPLD_I2C_CAP_BIT 0x04
|
||||
#define MLXPLAT_CPLD_I2C_CAP_MASK GENMASK(5, MLXPLAT_CPLD_I2C_CAP_BIT)
|
||||
#define MLXPLAT_CPLD_SYS_RESET_MASK BIT(0)
|
||||
|
||||
/* Masks for aggregation for comex carriers */
|
||||
#define MLXPLAT_CPLD_AGGR_MASK_CARRIER BIT(1)
|
||||
|
@ -259,6 +268,7 @@
|
|||
#define MLXPLAT_CPLD_LPC_LC_MASK GENMASK(7, 0)
|
||||
|
||||
#define MLXPLAT_CPLD_HALT_MASK BIT(3)
|
||||
#define MLXPLAT_CPLD_RESET_MASK GENMASK(7, 1)
|
||||
|
||||
/* Default I2C parent bus number */
|
||||
#define MLXPLAT_CPLD_PHYS_ADAPTER_DEF_NR 1
|
||||
|
@ -322,6 +332,12 @@
|
|||
#define MLXPLAT_I2C_MAIN_BUS_NOTIFIED 0x01
|
||||
#define MLXPLAT_I2C_MAIN_BUS_HANDLE_CREATED 0x02
|
||||
|
||||
/* Lattice FPGA PCI configuration */
|
||||
#define PCI_VENDOR_ID_LATTICE 0x1204
|
||||
#define PCI_DEVICE_ID_LATTICE_I2C_BRIDGE 0x9c2f
|
||||
#define PCI_DEVICE_ID_LATTICE_JTAG_BRIDGE 0x9c30
|
||||
#define PCI_DEVICE_ID_LATTICE_LPC_BRIDGE 0x9c32
|
||||
|
||||
/* mlxplat_priv - platform private data
|
||||
* @pdev_i2c - i2c controller platform device
|
||||
* @pdev_mux - array of mux platform devices
|
||||
|
@ -334,6 +350,7 @@
|
|||
* @hotplug_resources: system hotplug resources
|
||||
* @hotplug_resources_size: size of system hotplug resources
|
||||
* @hi2c_main_init_status: init status of I2C main bus
|
||||
* @irq_fpga: FPGA IRQ number
|
||||
*/
|
||||
struct mlxplat_priv {
|
||||
struct platform_device *pdev_i2c;
|
||||
|
@ -347,10 +364,12 @@ struct mlxplat_priv {
|
|||
struct resource *hotplug_resources;
|
||||
unsigned int hotplug_resources_size;
|
||||
u8 i2c_main_init_status;
|
||||
int irq_fpga;
|
||||
};
|
||||
|
||||
static struct platform_device *mlxplat_dev;
|
||||
static int mlxplat_i2c_main_complition_notify(void *handle, int id);
|
||||
static void __iomem *i2c_bridge_addr, *jtag_bridge_addr;
|
||||
|
||||
/* Regions for LPC I2C controller and LPC base register space */
|
||||
static const struct resource mlxplat_lpc_resources[] = {
|
||||
|
@ -435,6 +454,7 @@ static struct i2c_mux_reg_platform_data mlxplat_default_mux_data[] = {
|
|||
static int mlxplat_max_adap_num;
|
||||
static int mlxplat_mux_num;
|
||||
static struct i2c_mux_reg_platform_data *mlxplat_mux_data;
|
||||
static struct notifier_block *mlxplat_reboot_nb;
|
||||
|
||||
/* Platform extended mux data */
|
||||
static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = {
|
||||
|
@ -2355,8 +2375,11 @@ static int
|
|||
mlxplat_mlxcpld_l1_switch_pwr_events_handler(void *handle, enum mlxreg_hotplug_kind kind,
|
||||
u8 action)
|
||||
{
|
||||
dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button");
|
||||
kernel_power_off();
|
||||
if (action) {
|
||||
dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button");
|
||||
kernel_power_off();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2371,6 +2394,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_pwr_events_items_data[]
|
|||
.reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_PWR_BUTTON_MASK,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
.hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION,
|
||||
.hpdev.notifier = &mlxplat_mlxcpld_l1_switch_pwr_events_notifier,
|
||||
},
|
||||
};
|
||||
|
@ -2431,6 +2455,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_health_events_items_dat
|
|||
.reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_INTRUSION_MASK,
|
||||
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
|
||||
.hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION,
|
||||
.hpdev.notifier = &mlxplat_mlxcpld_l1_switch_intrusion_events_notifier,
|
||||
},
|
||||
{
|
||||
|
@ -3427,6 +3452,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld5_version",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld1_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
|
||||
|
@ -3455,6 +3486,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld5_pn",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET,
|
||||
.bit = GENMASK(15, 0),
|
||||
.mode = 0444,
|
||||
.regnum = 2,
|
||||
},
|
||||
{
|
||||
.label = "cpld1_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
|
||||
|
@ -3479,6 +3517,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "cpld5_version_min",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET,
|
||||
.bit = GENMASK(7, 0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "asic_reset",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
|
||||
|
@ -3555,9 +3599,9 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_from_comex",
|
||||
.label = "reset_swb_dc_dc_pwr_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
|
@ -3578,6 +3622,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.mask = GENMASK(7, 0) & ~BIT(7),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_sw_reset",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(0),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_pwr_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
|
@ -3680,6 +3730,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0200,
|
||||
},
|
||||
{
|
||||
.label = "jtag_cap",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_FU_CAP_MASK,
|
||||
.bit = 1,
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "jtag_enable",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
|
||||
|
@ -3792,6 +3849,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
|||
.mask = GENMASK(7, 0) & ~BIT(1),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "lid_open",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(2),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "clk_brd1_boot_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET,
|
||||
|
@ -4431,6 +4494,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = {
|
|||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_long_pwr_pb",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(7),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "pwr_cycle",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
|
@ -4905,6 +4974,7 @@ static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type3[] = {
|
|||
static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET:
|
||||
|
@ -4923,6 +4993,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
|
||||
|
@ -5001,6 +5072,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET:
|
||||
|
@ -5009,6 +5081,9 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET:
|
||||
|
@ -5034,6 +5109,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
|
||||
|
@ -5119,6 +5195,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM3_OFFSET:
|
||||
|
@ -5160,6 +5237,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET:
|
||||
|
@ -5168,6 +5246,9 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET:
|
||||
|
@ -5191,6 +5272,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
|
||||
|
@ -5270,6 +5352,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
|||
case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM3_OFFSET:
|
||||
|
@ -5469,13 +5552,50 @@ static struct mlxreg_core_platform_data *mlxplat_fan;
|
|||
static struct mlxreg_core_platform_data
|
||||
*mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS];
|
||||
static const struct regmap_config *mlxplat_regmap_config;
|
||||
static struct pci_dev *lpc_bridge;
|
||||
static struct pci_dev *i2c_bridge;
|
||||
static struct pci_dev *jtag_bridge;
|
||||
|
||||
/* Platform default reset function */
|
||||
static int mlxplat_reboot_notifier(struct notifier_block *nb, unsigned long action, void *unused)
|
||||
{
|
||||
struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
|
||||
u32 regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(priv->regmap, MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET, ®val);
|
||||
|
||||
if (action == SYS_RESTART && !ret && regval & MLXPLAT_CPLD_SYS_RESET_MASK)
|
||||
regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET,
|
||||
MLXPLAT_CPLD_RESET_MASK);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block mlxplat_reboot_default_nb = {
|
||||
.notifier_call = mlxplat_reboot_notifier,
|
||||
};
|
||||
|
||||
/* Platform default poweroff function */
|
||||
static void mlxplat_poweroff(void)
|
||||
{
|
||||
struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
|
||||
|
||||
if (mlxplat_reboot_nb)
|
||||
unregister_reboot_notifier(mlxplat_reboot_nb);
|
||||
regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, MLXPLAT_CPLD_HALT_MASK);
|
||||
kernel_halt();
|
||||
}
|
||||
|
||||
static int __init mlxplat_register_platform_device(void)
|
||||
{
|
||||
mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, -1,
|
||||
mlxplat_lpc_resources,
|
||||
ARRAY_SIZE(mlxplat_lpc_resources));
|
||||
if (IS_ERR(mlxplat_dev))
|
||||
return PTR_ERR(mlxplat_dev);
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5498,7 +5618,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_default_wc_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5521,7 +5641,7 @@ static int __init mlxplat_dmi_default_wc_matched(const struct dmi_system_id *dmi
|
|||
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_default_eth_wc_blade_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5546,7 +5666,7 @@ static int __init mlxplat_dmi_default_eth_wc_blade_matched(const struct dmi_syst
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5569,7 +5689,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5592,7 +5712,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5615,7 +5735,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5641,7 +5761,7 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5666,7 +5786,7 @@ static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_comex;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5692,7 +5812,7 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5712,7 +5832,7 @@ static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_eth_modular;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_chassis_blade_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5734,7 +5854,7 @@ static int __init mlxplat_dmi_chassis_blade_matched(const struct dmi_system_id *
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_rack_switch_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5755,7 +5875,7 @@ static int __init mlxplat_dmi_rack_switch_matched(const struct dmi_system_id *dm
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_rack_switch;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5776,7 +5896,7 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
|
||||
|
@ -5797,8 +5917,9 @@ static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
|
|||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_rack_switch;
|
||||
pm_power_off = mlxplat_poweroff;
|
||||
mlxplat_reboot_nb = &mlxplat_reboot_default_nb;
|
||||
|
||||
return 1;
|
||||
return mlxplat_register_platform_device();
|
||||
}
|
||||
|
||||
static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
|
||||
|
@ -6042,12 +6163,6 @@ static int mlxplat_lpc_cpld_device_init(struct resource **hotplug_resources,
|
|||
{
|
||||
int err;
|
||||
|
||||
mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, PLATFORM_DEVID_NONE,
|
||||
mlxplat_lpc_resources,
|
||||
ARRAY_SIZE(mlxplat_lpc_resources));
|
||||
if (IS_ERR(mlxplat_dev))
|
||||
return PTR_ERR(mlxplat_dev);
|
||||
|
||||
mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev,
|
||||
mlxplat_lpc_resources[1].start, 1);
|
||||
if (!mlxplat_mlxcpld_regmap_ctx.base) {
|
||||
|
@ -6061,24 +6176,138 @@ static int mlxplat_lpc_cpld_device_init(struct resource **hotplug_resources,
|
|||
return 0;
|
||||
|
||||
fail_devm_ioport_map:
|
||||
platform_device_unregister(mlxplat_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mlxplat_lpc_cpld_device_exit(void)
|
||||
{
|
||||
platform_device_unregister(mlxplat_dev);
|
||||
}
|
||||
|
||||
static int
|
||||
mlxplat_pci_fpga_device_init(unsigned int device, const char *res_name, struct pci_dev **pci_bridge,
|
||||
void __iomem **pci_bridge_addr)
|
||||
{
|
||||
void __iomem *pci_mem_addr;
|
||||
struct pci_dev *pci_dev;
|
||||
int err;
|
||||
|
||||
pci_dev = pci_get_device(PCI_VENDOR_ID_LATTICE, device, NULL);
|
||||
if (!pci_dev)
|
||||
return -ENODEV;
|
||||
|
||||
err = pci_enable_device(pci_dev);
|
||||
if (err) {
|
||||
dev_err(&pci_dev->dev, "pci_enable_device failed with error %d\n", err);
|
||||
goto fail_pci_enable_device;
|
||||
}
|
||||
|
||||
err = pci_request_region(pci_dev, 0, res_name);
|
||||
if (err) {
|
||||
dev_err(&pci_dev->dev, "pci_request_regions failed with error %d\n", err);
|
||||
goto fail_pci_request_regions;
|
||||
}
|
||||
|
||||
err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
|
||||
if (err) {
|
||||
err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32));
|
||||
if (err) {
|
||||
dev_err(&pci_dev->dev, "dma_set_mask failed with error %d\n", err);
|
||||
goto fail_pci_set_dma_mask;
|
||||
}
|
||||
}
|
||||
|
||||
pci_set_master(pci_dev);
|
||||
|
||||
pci_mem_addr = devm_ioremap(&pci_dev->dev, pci_resource_start(pci_dev, 0),
|
||||
pci_resource_len(pci_dev, 0));
|
||||
if (!pci_mem_addr) {
|
||||
dev_err(&mlxplat_dev->dev, "ioremap failed\n");
|
||||
err = -EIO;
|
||||
goto fail_ioremap;
|
||||
}
|
||||
|
||||
*pci_bridge = pci_dev;
|
||||
*pci_bridge_addr = pci_mem_addr;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_ioremap:
|
||||
fail_pci_set_dma_mask:
|
||||
pci_release_regions(pci_dev);
|
||||
fail_pci_request_regions:
|
||||
pci_disable_device(pci_dev);
|
||||
fail_pci_enable_device:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
mlxplat_pci_fpga_device_exit(struct pci_dev *pci_bridge,
|
||||
void __iomem *pci_bridge_addr)
|
||||
{
|
||||
iounmap(pci_bridge_addr);
|
||||
pci_release_regions(pci_bridge);
|
||||
pci_disable_device(pci_bridge);
|
||||
}
|
||||
|
||||
static int
|
||||
mlxplat_pci_fpga_devices_init(struct resource **hotplug_resources,
|
||||
unsigned int *hotplug_resources_size)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_LPC_BRIDGE,
|
||||
"mlxplat_lpc_bridge", &lpc_bridge,
|
||||
&mlxplat_mlxcpld_regmap_ctx.base);
|
||||
if (err)
|
||||
goto mlxplat_pci_fpga_device_init_lpc_fail;
|
||||
|
||||
err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_I2C_BRIDGE,
|
||||
"mlxplat_i2c_bridge", &i2c_bridge,
|
||||
&i2c_bridge_addr);
|
||||
if (err)
|
||||
goto mlxplat_pci_fpga_device_init_i2c_fail;
|
||||
|
||||
err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_JTAG_BRIDGE,
|
||||
"mlxplat_jtag_bridge", &jtag_bridge,
|
||||
&jtag_bridge_addr);
|
||||
if (err)
|
||||
goto mlxplat_pci_fpga_device_init_jtag_fail;
|
||||
|
||||
return 0;
|
||||
|
||||
mlxplat_pci_fpga_device_init_jtag_fail:
|
||||
mlxplat_pci_fpga_device_exit(i2c_bridge, i2c_bridge_addr);
|
||||
mlxplat_pci_fpga_device_init_i2c_fail:
|
||||
mlxplat_pci_fpga_device_exit(lpc_bridge, mlxplat_mlxcpld_regmap_ctx.base);
|
||||
mlxplat_pci_fpga_device_init_lpc_fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mlxplat_pci_fpga_devices_exit(void)
|
||||
{
|
||||
mlxplat_pci_fpga_device_exit(jtag_bridge, jtag_bridge_addr);
|
||||
mlxplat_pci_fpga_device_exit(i2c_bridge, i2c_bridge_addr);
|
||||
mlxplat_pci_fpga_device_exit(lpc_bridge, mlxplat_mlxcpld_regmap_ctx.base);
|
||||
}
|
||||
|
||||
static int
|
||||
mlxplat_pre_init(struct resource **hotplug_resources, unsigned int *hotplug_resources_size)
|
||||
{
|
||||
return mlxplat_lpc_cpld_device_init(hotplug_resources, hotplug_resources_size);
|
||||
int err;
|
||||
|
||||
err = mlxplat_pci_fpga_devices_init(hotplug_resources, hotplug_resources_size);
|
||||
if (err == -ENODEV)
|
||||
return mlxplat_lpc_cpld_device_init(hotplug_resources, hotplug_resources_size);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mlxplat_post_exit(void)
|
||||
{
|
||||
mlxplat_lpc_cpld_device_exit();
|
||||
if (lpc_bridge)
|
||||
mlxplat_pci_fpga_devices_exit();
|
||||
else
|
||||
mlxplat_lpc_cpld_device_exit();
|
||||
}
|
||||
|
||||
static int mlxplat_post_init(struct mlxplat_priv *priv)
|
||||
|
@ -6088,6 +6317,8 @@ static int mlxplat_post_init(struct mlxplat_priv *priv)
|
|||
/* Add hotplug driver */
|
||||
if (mlxplat_hotplug) {
|
||||
mlxplat_hotplug->regmap = priv->regmap;
|
||||
if (priv->irq_fpga)
|
||||
mlxplat_hotplug->irq = priv->irq_fpga;
|
||||
priv->pdev_hotplug =
|
||||
platform_device_register_resndata(&mlxplat_dev->dev,
|
||||
"mlxreg-hotplug", PLATFORM_DEVID_NONE,
|
||||
|
@ -6201,7 +6432,7 @@ mlxplat_i2c_mux_complition_notify(void *handle, struct i2c_adapter *parent,
|
|||
return mlxplat_post_init(priv);
|
||||
}
|
||||
|
||||
static int mlxplat_i2c_mux_topolgy_init(struct mlxplat_priv *priv)
|
||||
static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
|
@ -6230,7 +6461,7 @@ fail_platform_mux_register:
|
|||
return err;
|
||||
}
|
||||
|
||||
static void mlxplat_i2c_mux_topolgy_exit(struct mlxplat_priv *priv)
|
||||
static void mlxplat_i2c_mux_topology_exit(struct mlxplat_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -6244,7 +6475,7 @@ static int mlxplat_i2c_main_complition_notify(void *handle, int id)
|
|||
{
|
||||
struct mlxplat_priv *priv = handle;
|
||||
|
||||
return mlxplat_i2c_mux_topolgy_init(priv);
|
||||
return mlxplat_i2c_mux_topology_init(priv);
|
||||
}
|
||||
|
||||
static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
|
||||
|
@ -6262,6 +6493,9 @@ static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
|
|||
mlxplat_i2c->regmap = priv->regmap;
|
||||
mlxplat_i2c->handle = priv;
|
||||
|
||||
/* Set mapped base address of I2C-LPC bridge over PCIe */
|
||||
if (lpc_bridge)
|
||||
mlxplat_i2c->addr = i2c_bridge_addr;
|
||||
priv->pdev_i2c = platform_device_register_resndata(&mlxplat_dev->dev, "i2c_mlxcpld",
|
||||
nr, priv->hotplug_resources,
|
||||
priv->hotplug_resources_size,
|
||||
|
@ -6272,14 +6506,14 @@ static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
|
|||
}
|
||||
|
||||
if (priv->i2c_main_init_status == MLXPLAT_I2C_MAIN_BUS_NOTIFIED) {
|
||||
err = mlxplat_i2c_mux_topolgy_init(priv);
|
||||
err = mlxplat_i2c_mux_topology_init(priv);
|
||||
if (err)
|
||||
goto fail_mlxplat_i2c_mux_topolgy_init;
|
||||
goto fail_mlxplat_i2c_mux_topology_init;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_mlxplat_i2c_mux_topolgy_init:
|
||||
fail_mlxplat_i2c_mux_topology_init:
|
||||
fail_platform_i2c_register:
|
||||
fail_mlxplat_mlxcpld_verify_bus_topology:
|
||||
return err;
|
||||
|
@ -6287,20 +6521,26 @@ fail_mlxplat_mlxcpld_verify_bus_topology:
|
|||
|
||||
static void mlxplat_i2c_main_exit(struct mlxplat_priv *priv)
|
||||
{
|
||||
mlxplat_i2c_mux_topolgy_exit(priv);
|
||||
mlxplat_i2c_mux_topology_exit(priv);
|
||||
if (priv->pdev_i2c)
|
||||
platform_device_unregister(priv->pdev_i2c);
|
||||
}
|
||||
|
||||
static int __init mlxplat_init(void)
|
||||
static int mlxplat_probe(struct platform_device *pdev)
|
||||
{
|
||||
unsigned int hotplug_resources_size;
|
||||
struct resource *hotplug_resources;
|
||||
unsigned int hotplug_resources_size = 0;
|
||||
struct resource *hotplug_resources = NULL;
|
||||
struct acpi_device *acpi_dev;
|
||||
struct mlxplat_priv *priv;
|
||||
int i, err;
|
||||
int irq_fpga = 0, i, err;
|
||||
|
||||
if (!dmi_check_system(mlxplat_dmi_table))
|
||||
return -ENODEV;
|
||||
acpi_dev = ACPI_COMPANION(&pdev->dev);
|
||||
if (acpi_dev) {
|
||||
irq_fpga = acpi_dev_gpio_irq_get(acpi_dev, 0);
|
||||
if (irq_fpga < 0)
|
||||
return -ENODEV;
|
||||
mlxplat_dev = pdev;
|
||||
}
|
||||
|
||||
err = mlxplat_pre_init(&hotplug_resources, &hotplug_resources_size);
|
||||
if (err)
|
||||
|
@ -6315,6 +6555,7 @@ static int __init mlxplat_init(void)
|
|||
platform_set_drvdata(mlxplat_dev, priv);
|
||||
priv->hotplug_resources = hotplug_resources;
|
||||
priv->hotplug_resources_size = hotplug_resources_size;
|
||||
priv->irq_fpga = irq_fpga;
|
||||
|
||||
if (!mlxplat_regmap_config)
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config;
|
||||
|
@ -6346,8 +6587,15 @@ static int __init mlxplat_init(void)
|
|||
if (err)
|
||||
goto fail_regcache_sync;
|
||||
|
||||
if (mlxplat_reboot_nb) {
|
||||
err = register_reboot_notifier(mlxplat_reboot_nb);
|
||||
if (err)
|
||||
goto fail_register_reboot_notifier;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_register_reboot_notifier:
|
||||
fail_regcache_sync:
|
||||
mlxplat_pre_exit(priv);
|
||||
fail_mlxplat_i2c_main_init:
|
||||
|
@ -6357,17 +6605,57 @@ fail_alloc:
|
|||
|
||||
return err;
|
||||
}
|
||||
module_init(mlxplat_init);
|
||||
|
||||
static void __exit mlxplat_exit(void)
|
||||
static int mlxplat_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
|
||||
|
||||
if (pm_power_off)
|
||||
pm_power_off = NULL;
|
||||
if (mlxplat_reboot_nb)
|
||||
unregister_reboot_notifier(mlxplat_reboot_nb);
|
||||
mlxplat_pre_exit(priv);
|
||||
mlxplat_i2c_main_exit(priv);
|
||||
mlxplat_post_exit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id mlxplat_acpi_table[] = {
|
||||
{ "MLNXBF49", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, mlxplat_acpi_table);
|
||||
|
||||
static struct platform_driver mlxplat_driver = {
|
||||
.driver = {
|
||||
.name = "mlxplat",
|
||||
.acpi_match_table = mlxplat_acpi_table,
|
||||
.probe_type = PROBE_FORCE_SYNCHRONOUS,
|
||||
},
|
||||
.probe = mlxplat_probe,
|
||||
.remove = mlxplat_remove,
|
||||
};
|
||||
|
||||
static int __init mlxplat_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!dmi_check_system(mlxplat_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
err = platform_driver_register(&mlxplat_driver);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
module_init(mlxplat_init);
|
||||
|
||||
static void __exit mlxplat_exit(void)
|
||||
{
|
||||
if (mlxplat_dev)
|
||||
platform_device_unregister(mlxplat_dev);
|
||||
|
||||
platform_driver_unregister(&mlxplat_driver);
|
||||
}
|
||||
module_exit(mlxplat_exit);
|
||||
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright 2023 Schweitzer Engineering Laboratories, Inc.
|
||||
* 2350 NE Hopkins Court, Pullman, WA 99163 USA
|
||||
*
|
||||
* Platform support for the b2093 mainboard used in SEL-3350 computers.
|
||||
* Consumes GPIO from the SoC to provide standard LED and power supply
|
||||
* devices.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
/* Broxton communities */
|
||||
#define BXT_NW "INT3452:01"
|
||||
#define BXT_W "INT3452:02"
|
||||
#define BXT_SW "INT3452:03"
|
||||
|
||||
#define B2093_GPIO_ACPI_ID "SEL0003"
|
||||
|
||||
#define SEL_PS_A "sel_ps_a"
|
||||
#define SEL_PS_A_DETECT "sel_ps_a_detect"
|
||||
#define SEL_PS_A_GOOD "sel_ps_a_good"
|
||||
#define SEL_PS_B "sel_ps_b"
|
||||
#define SEL_PS_B_DETECT "sel_ps_b_detect"
|
||||
#define SEL_PS_B_GOOD "sel_ps_b_good"
|
||||
|
||||
/* LEDs */
|
||||
static const struct gpio_led sel3350_leds[] = {
|
||||
{ .name = "sel:green:aux1" },
|
||||
{ .name = "sel:green:aux2" },
|
||||
{ .name = "sel:green:aux3" },
|
||||
{ .name = "sel:green:aux4" },
|
||||
{ .name = "sel:red:alarm" },
|
||||
{ .name = "sel:green:enabled",
|
||||
.default_state = LEDS_GPIO_DEFSTATE_ON },
|
||||
{ .name = "sel:red:aux1" },
|
||||
{ .name = "sel:red:aux2" },
|
||||
{ .name = "sel:red:aux3" },
|
||||
{ .name = "sel:red:aux4" },
|
||||
};
|
||||
|
||||
static const struct gpio_led_platform_data sel3350_leds_pdata = {
|
||||
.num_leds = ARRAY_SIZE(sel3350_leds),
|
||||
.leds = sel3350_leds,
|
||||
};
|
||||
|
||||
/* Map GPIOs to LEDs */
|
||||
static struct gpiod_lookup_table sel3350_leds_table = {
|
||||
.dev_id = "leds-gpio",
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_W, 20, NULL, 4, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_W, 21, NULL, 5, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH),
|
||||
{},
|
||||
}
|
||||
};
|
||||
|
||||
/* Map GPIOs to power supplies */
|
||||
static struct gpiod_lookup_table sel3350_gpios_table = {
|
||||
.dev_id = B2093_GPIO_ACPI_ID ":00",
|
||||
.table = {
|
||||
GPIO_LOOKUP(BXT_NW, 44, SEL_PS_A_DETECT, GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD, GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD, GPIO_ACTIVE_LOW),
|
||||
{},
|
||||
}
|
||||
};
|
||||
|
||||
/* Power Supplies */
|
||||
|
||||
struct sel3350_power_cfg_data {
|
||||
struct gpio_desc *ps_detect;
|
||||
struct gpio_desc *ps_good;
|
||||
};
|
||||
|
||||
static int sel3350_power_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct sel3350_power_cfg_data *data = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
if (gpiod_get_value(data->ps_detect)) {
|
||||
if (gpiod_get_value(data->ps_good))
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
} else {
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
|
||||
}
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = gpiod_get_value(data->ps_detect);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = gpiod_get_value(data->ps_good);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum power_supply_property sel3350_power_properties[] = {
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc sel3350_ps_a_desc = {
|
||||
.name = SEL_PS_A,
|
||||
.type = POWER_SUPPLY_TYPE_MAINS,
|
||||
.properties = sel3350_power_properties,
|
||||
.num_properties = ARRAY_SIZE(sel3350_power_properties),
|
||||
.get_property = sel3350_power_get_property,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc sel3350_ps_b_desc = {
|
||||
.name = SEL_PS_B,
|
||||
.type = POWER_SUPPLY_TYPE_MAINS,
|
||||
.properties = sel3350_power_properties,
|
||||
.num_properties = ARRAY_SIZE(sel3350_power_properties),
|
||||
.get_property = sel3350_power_get_property,
|
||||
};
|
||||
|
||||
struct sel3350_data {
|
||||
struct platform_device *leds_pdev;
|
||||
struct power_supply *ps_a;
|
||||
struct power_supply *ps_b;
|
||||
struct sel3350_power_cfg_data ps_a_cfg_data;
|
||||
struct sel3350_power_cfg_data ps_b_cfg_data;
|
||||
};
|
||||
|
||||
static int sel3350_probe(struct platform_device *pdev)
|
||||
{
|
||||
int rs;
|
||||
struct sel3350_data *sel3350;
|
||||
struct power_supply_config ps_cfg = {};
|
||||
|
||||
sel3350 = devm_kzalloc(&pdev->dev, sizeof(struct sel3350_data), GFP_KERNEL);
|
||||
if (!sel3350)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, sel3350);
|
||||
|
||||
gpiod_add_lookup_table(&sel3350_leds_table);
|
||||
gpiod_add_lookup_table(&sel3350_gpios_table);
|
||||
|
||||
sel3350->leds_pdev = platform_device_register_data(
|
||||
NULL,
|
||||
"leds-gpio",
|
||||
PLATFORM_DEVID_NONE,
|
||||
&sel3350_leds_pdata,
|
||||
sizeof(sel3350_leds_pdata));
|
||||
if (IS_ERR(sel3350->leds_pdev)) {
|
||||
rs = PTR_ERR(sel3350->leds_pdev);
|
||||
dev_err(&pdev->dev, "Failed registering platform device: %d\n", rs);
|
||||
goto err_platform;
|
||||
}
|
||||
|
||||
/* Power Supply A */
|
||||
sel3350->ps_a_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
|
||||
SEL_PS_A_DETECT,
|
||||
GPIOD_IN);
|
||||
sel3350->ps_a_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
|
||||
SEL_PS_A_GOOD,
|
||||
GPIOD_IN);
|
||||
ps_cfg.drv_data = &sel3350->ps_a_cfg_data;
|
||||
sel3350->ps_a = devm_power_supply_register(&pdev->dev,
|
||||
&sel3350_ps_a_desc,
|
||||
&ps_cfg);
|
||||
if (IS_ERR(sel3350->ps_a)) {
|
||||
rs = PTR_ERR(sel3350->ps_a);
|
||||
dev_err(&pdev->dev, "Failed registering power supply A: %d\n", rs);
|
||||
goto err_ps;
|
||||
}
|
||||
|
||||
/* Power Supply B */
|
||||
sel3350->ps_b_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
|
||||
SEL_PS_B_DETECT,
|
||||
GPIOD_IN);
|
||||
sel3350->ps_b_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
|
||||
SEL_PS_B_GOOD,
|
||||
GPIOD_IN);
|
||||
ps_cfg.drv_data = &sel3350->ps_b_cfg_data;
|
||||
sel3350->ps_b = devm_power_supply_register(&pdev->dev,
|
||||
&sel3350_ps_b_desc,
|
||||
&ps_cfg);
|
||||
if (IS_ERR(sel3350->ps_b)) {
|
||||
rs = PTR_ERR(sel3350->ps_b);
|
||||
dev_err(&pdev->dev, "Failed registering power supply B: %d\n", rs);
|
||||
goto err_ps;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_ps:
|
||||
platform_device_unregister(sel3350->leds_pdev);
|
||||
err_platform:
|
||||
gpiod_remove_lookup_table(&sel3350_gpios_table);
|
||||
gpiod_remove_lookup_table(&sel3350_leds_table);
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
static int sel3350_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sel3350_data *sel3350 = platform_get_drvdata(pdev);
|
||||
|
||||
platform_device_unregister(sel3350->leds_pdev);
|
||||
gpiod_remove_lookup_table(&sel3350_gpios_table);
|
||||
gpiod_remove_lookup_table(&sel3350_leds_table);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id sel3350_device_ids[] = {
|
||||
{ B2093_GPIO_ACPI_ID, 0 },
|
||||
{ "", 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, sel3350_device_ids);
|
||||
|
||||
static struct platform_driver sel3350_platform_driver = {
|
||||
.probe = sel3350_probe,
|
||||
.remove = sel3350_remove,
|
||||
.driver = {
|
||||
.name = "sel3350-platform",
|
||||
.acpi_match_table = sel3350_device_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(sel3350_platform_driver);
|
||||
|
||||
MODULE_AUTHOR("Schweitzer Engineering Laboratories");
|
||||
MODULE_DESCRIPTION("SEL-3350 platform driver");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_SOFTDEP("pre: pinctrl_broxton leds-gpio");
|
|
@ -0,0 +1,64 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Siemens X86 Platform Specific Drivers
|
||||
#
|
||||
|
||||
config SIEMENS_SIMATIC_IPC
|
||||
tristate "Siemens Simatic IPC Class driver"
|
||||
help
|
||||
This Simatic IPC class driver is the central of several drivers. It
|
||||
is mainly used for system identification, after which drivers in other
|
||||
classes will take care of driving specifics of those machines.
|
||||
i.e. LEDs and watchdog.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc.
|
||||
|
||||
config SIEMENS_SIMATIC_IPC_BATT
|
||||
tristate "CMOS battery driver for Siemens Simatic IPCs"
|
||||
default SIEMENS_SIMATIC_IPC
|
||||
depends on HWMON
|
||||
depends on SIEMENS_SIMATIC_IPC
|
||||
help
|
||||
This option enables support for monitoring the voltage of the CMOS
|
||||
batteries of several Industrial PCs from Siemens.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc-batt.
|
||||
|
||||
config SIEMENS_SIMATIC_IPC_BATT_APOLLOLAKE
|
||||
tristate "CMOS Battery monitoring for Simatic IPCs based on Apollo Lake GPIO"
|
||||
default SIEMENS_SIMATIC_IPC_BATT
|
||||
depends on PINCTRL_BROXTON
|
||||
depends on SIEMENS_SIMATIC_IPC_BATT
|
||||
help
|
||||
This option enables CMOS battery monitoring for Simatic Industrial PCs
|
||||
from Siemens based on Apollo Lake GPIO.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc-batt-apollolake.
|
||||
|
||||
config SIEMENS_SIMATIC_IPC_BATT_ELKHARTLAKE
|
||||
tristate "CMOS Battery monitoring for Simatic IPCs based on Elkhart Lake GPIO"
|
||||
default SIEMENS_SIMATIC_IPC_BATT
|
||||
depends on PINCTRL_ELKHARTLAKE
|
||||
depends on SIEMENS_SIMATIC_IPC_BATT
|
||||
help
|
||||
This option enables CMOS battery monitoring for Simatic Industrial PCs
|
||||
from Siemens based on Elkhart Lake GPIO.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc-batt-elkhartlake.
|
||||
|
||||
config SIEMENS_SIMATIC_IPC_BATT_F7188X
|
||||
tristate "CMOS Battery monitoring for Simatic IPCs based on Nuvoton GPIO"
|
||||
default SIEMENS_SIMATIC_IPC_BATT
|
||||
depends on GPIO_F7188X
|
||||
depends on PINCTRL_ALDERLAKE
|
||||
depends on SIEMENS_SIMATIC_IPC_BATT
|
||||
help
|
||||
This option enables CMOS battery monitoring for Simatic Industrial PCs
|
||||
from Siemens based on Nuvoton GPIO.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called simatic-ipc-batt-f7188x.
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for linux/drivers/platform/x86/siemens
|
||||
# Siemens x86 Platform-Specific Drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT) += simatic-ipc-batt.o
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_APOLLOLAKE) += simatic-ipc-batt-apollolake.o
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_ELKHARTLAKE) += simatic-ipc-batt-elkhartlake.o
|
||||
obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_F7188X) += simatic-ipc-batt-f7188x.o
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC driver for CMOS battery monitoring
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "simatic-ipc-batt.h"
|
||||
|
||||
static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_127e = {
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 55, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 61, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("apollolake-pinctrl.1", 41, NULL, 2, GPIO_ACTIVE_HIGH),
|
||||
{} /* Terminating entry */
|
||||
},
|
||||
};
|
||||
|
||||
static int simatic_ipc_batt_apollolake_remove(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_remove(pdev, &simatic_ipc_batt_gpio_table_127e);
|
||||
}
|
||||
|
||||
static int simatic_ipc_batt_apollolake_probe(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_probe(pdev, &simatic_ipc_batt_gpio_table_127e);
|
||||
}
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_apollolake_probe,
|
||||
.remove = simatic_ipc_batt_apollolake_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(simatic_ipc_batt_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" KBUILD_MODNAME);
|
||||
MODULE_SOFTDEP("pre: simatic-ipc-batt platform:apollolake-pinctrl");
|
||||
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC driver for CMOS battery monitoring
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "simatic-ipc-batt.h"
|
||||
|
||||
static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_21a = {
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX("INTC1020:04", 18, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("INTC1020:04", 19, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("INTC1020:01", 66, NULL, 2, GPIO_ACTIVE_HIGH),
|
||||
{} /* Terminating entry */
|
||||
},
|
||||
};
|
||||
|
||||
static int simatic_ipc_batt_elkhartlake_remove(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_remove(pdev, &simatic_ipc_batt_gpio_table_bx_21a);
|
||||
}
|
||||
|
||||
static int simatic_ipc_batt_elkhartlake_probe(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_probe(pdev, &simatic_ipc_batt_gpio_table_bx_21a);
|
||||
}
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_elkhartlake_probe,
|
||||
.remove = simatic_ipc_batt_elkhartlake_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(simatic_ipc_batt_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" KBUILD_MODNAME);
|
||||
MODULE_SOFTDEP("pre: simatic-ipc-batt platform:elkhartlake-pinctrl");
|
||||
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
|
|
@ -0,0 +1,87 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC driver for CMOS battery monitoring
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/x86/simatic-ipc-base.h>
|
||||
|
||||
#include "simatic-ipc-batt.h"
|
||||
|
||||
static struct gpiod_lookup_table *batt_lookup_table;
|
||||
|
||||
static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_227g = {
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-7", 6, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-7", 5, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("INTC1020:01", 66, NULL, 2, GPIO_ACTIVE_HIGH),
|
||||
{} /* Terminating entry */
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_39a = {
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-6", 4, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-6", 3, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
{} /* Terminating entry */
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_59a = {
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-7", 6, NULL, 0, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("gpio-f7188x-7", 5, NULL, 1, GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP_IDX("INTC1056:00", 438, NULL, 2, GPIO_ACTIVE_HIGH),
|
||||
{} /* Terminating entry */
|
||||
}
|
||||
};
|
||||
|
||||
static int simatic_ipc_batt_f7188x_remove(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_remove(pdev, batt_lookup_table);
|
||||
}
|
||||
|
||||
static int simatic_ipc_batt_f7188x_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
|
||||
|
||||
switch (plat->devmode) {
|
||||
case SIMATIC_IPC_DEVICE_227G:
|
||||
batt_lookup_table = &simatic_ipc_batt_gpio_table_227g;
|
||||
break;
|
||||
case SIMATIC_IPC_DEVICE_BX_39A:
|
||||
batt_lookup_table = &simatic_ipc_batt_gpio_table_bx_39a;
|
||||
break;
|
||||
case SIMATIC_IPC_DEVICE_BX_59A:
|
||||
batt_lookup_table = &simatic_ipc_batt_gpio_table_bx_59a;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return simatic_ipc_batt_probe(pdev, batt_lookup_table);
|
||||
}
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_f7188x_probe,
|
||||
.remove = simatic_ipc_batt_f7188x_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(simatic_ipc_batt_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" KBUILD_MODNAME);
|
||||
MODULE_SOFTDEP("pre: simatic-ipc-batt gpio_f7188x platform:elkhartlake-pinctrl platform:alderlake-pinctrl");
|
||||
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
|
|
@ -0,0 +1,253 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC driver for CMOS battery monitoring
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2023
|
||||
*
|
||||
* Authors:
|
||||
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/x86/simatic-ipc-base.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include "simatic-ipc-batt.h"
|
||||
|
||||
#define BATT_DELAY_MS (1000 * 60 * 60 * 24) /* 24 h delay */
|
||||
|
||||
#define SIMATIC_IPC_BATT_LEVEL_FULL 3000
|
||||
#define SIMATIC_IPC_BATT_LEVEL_CRIT 2750
|
||||
#define SIMATIC_IPC_BATT_LEVEL_EMPTY 0
|
||||
|
||||
static struct simatic_ipc_batt {
|
||||
u8 devmode;
|
||||
long current_state;
|
||||
struct gpio_desc *gpios[3];
|
||||
unsigned long last_updated_jiffies;
|
||||
} priv;
|
||||
|
||||
static long simatic_ipc_batt_read_gpio(void)
|
||||
{
|
||||
long r = SIMATIC_IPC_BATT_LEVEL_FULL;
|
||||
|
||||
if (priv.gpios[2]) {
|
||||
gpiod_set_value(priv.gpios[2], 1);
|
||||
msleep(150);
|
||||
}
|
||||
|
||||
if (gpiod_get_value_cansleep(priv.gpios[0]))
|
||||
r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
|
||||
else if (gpiod_get_value_cansleep(priv.gpios[1]))
|
||||
r = SIMATIC_IPC_BATT_LEVEL_CRIT;
|
||||
|
||||
if (priv.gpios[2])
|
||||
gpiod_set_value(priv.gpios[2], 0);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
#define SIMATIC_IPC_BATT_PORT_BASE 0x404D
|
||||
static struct resource simatic_ipc_batt_io_res =
|
||||
DEFINE_RES_IO_NAMED(SIMATIC_IPC_BATT_PORT_BASE, SZ_1, KBUILD_MODNAME);
|
||||
|
||||
static long simatic_ipc_batt_read_io(struct device *dev)
|
||||
{
|
||||
long r = SIMATIC_IPC_BATT_LEVEL_FULL;
|
||||
struct resource *res = &simatic_ipc_batt_io_res;
|
||||
u8 val;
|
||||
|
||||
if (!request_muxed_region(res->start, resource_size(res), res->name)) {
|
||||
dev_err(dev, "Unable to register IO resource at %pR\n", res);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
val = inb(SIMATIC_IPC_BATT_PORT_BASE);
|
||||
release_region(simatic_ipc_batt_io_res.start, resource_size(&simatic_ipc_batt_io_res));
|
||||
|
||||
if (val & (1 << 7))
|
||||
r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
|
||||
else if (val & (1 << 6))
|
||||
r = SIMATIC_IPC_BATT_LEVEL_CRIT;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static long simatic_ipc_batt_read_value(struct device *dev)
|
||||
{
|
||||
unsigned long next_update;
|
||||
|
||||
next_update = priv.last_updated_jiffies + msecs_to_jiffies(BATT_DELAY_MS);
|
||||
if (time_after(jiffies, next_update) || !priv.last_updated_jiffies) {
|
||||
if (priv.devmode == SIMATIC_IPC_DEVICE_227E)
|
||||
priv.current_state = simatic_ipc_batt_read_io(dev);
|
||||
else
|
||||
priv.current_state = simatic_ipc_batt_read_gpio();
|
||||
|
||||
priv.last_updated_jiffies = jiffies;
|
||||
if (priv.current_state < SIMATIC_IPC_BATT_LEVEL_FULL)
|
||||
dev_warn(dev, "CMOS battery needs to be replaced.\n");
|
||||
}
|
||||
|
||||
return priv.current_state;
|
||||
}
|
||||
|
||||
static int simatic_ipc_batt_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
*val = simatic_ipc_batt_read_value(dev);
|
||||
break;
|
||||
case hwmon_in_lcrit:
|
||||
*val = SIMATIC_IPC_BATT_LEVEL_CRIT;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t simatic_ipc_batt_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
if (attr == hwmon_in_input || attr == hwmon_in_lcrit)
|
||||
return 0444;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops simatic_ipc_batt_ops = {
|
||||
.is_visible = simatic_ipc_batt_is_visible,
|
||||
.read = simatic_ipc_batt_read,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
|
||||
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
|
||||
.ops = &simatic_ipc_batt_ops,
|
||||
.info = simatic_ipc_batt_info,
|
||||
};
|
||||
|
||||
int simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
|
||||
{
|
||||
gpiod_remove_lookup_table(table);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
|
||||
|
||||
int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
|
||||
{
|
||||
struct simatic_ipc_platform *plat;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device *hwmon_dev;
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
plat = pdev->dev.platform_data;
|
||||
priv.devmode = plat->devmode;
|
||||
|
||||
switch (priv.devmode) {
|
||||
case SIMATIC_IPC_DEVICE_127E:
|
||||
case SIMATIC_IPC_DEVICE_227G:
|
||||
case SIMATIC_IPC_DEVICE_BX_39A:
|
||||
case SIMATIC_IPC_DEVICE_BX_21A:
|
||||
case SIMATIC_IPC_DEVICE_BX_59A:
|
||||
table->dev_id = dev_name(dev);
|
||||
gpiod_add_lookup_table(table);
|
||||
break;
|
||||
case SIMATIC_IPC_DEVICE_227E:
|
||||
goto nogpio;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
priv.gpios[0] = devm_gpiod_get_index(dev, "CMOSBattery empty", 0, GPIOD_IN);
|
||||
if (IS_ERR(priv.gpios[0])) {
|
||||
err = PTR_ERR(priv.gpios[0]);
|
||||
priv.gpios[0] = NULL;
|
||||
goto out;
|
||||
}
|
||||
priv.gpios[1] = devm_gpiod_get_index(dev, "CMOSBattery low", 1, GPIOD_IN);
|
||||
if (IS_ERR(priv.gpios[1])) {
|
||||
err = PTR_ERR(priv.gpios[1]);
|
||||
priv.gpios[1] = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (table->table[2].key) {
|
||||
flags = GPIOD_OUT_HIGH;
|
||||
if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
|
||||
priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
|
||||
flags = GPIOD_OUT_LOW;
|
||||
priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
|
||||
if (IS_ERR(priv.gpios[2])) {
|
||||
err = PTR_ERR(priv.gpios[2]);
|
||||
priv.gpios[2] = NULL;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
priv.gpios[2] = NULL;
|
||||
}
|
||||
|
||||
nogpio:
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
|
||||
&priv,
|
||||
&simatic_ipc_batt_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev)) {
|
||||
err = PTR_ERR(hwmon_dev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* warn about aging battery even if userspace never reads hwmon */
|
||||
simatic_ipc_batt_read_value(dev);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
simatic_ipc_batt_remove(pdev, table);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
|
||||
|
||||
static int simatic_ipc_batt_io_remove(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_remove(pdev, NULL);
|
||||
}
|
||||
|
||||
static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
|
||||
{
|
||||
return simatic_ipc_batt_probe(pdev, NULL);
|
||||
}
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_io_probe,
|
||||
.remove = simatic_ipc_batt_io_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(simatic_ipc_batt_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" KBUILD_MODNAME);
|
||||
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Siemens SIMATIC IPC driver for CMOS battery monitoring
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2023
|
||||
*
|
||||
* Author:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
*/
|
||||
|
||||
#ifndef _SIMATIC_IPC_BATT_H
|
||||
#define _SIMATIC_IPC_BATT_H
|
||||
|
||||
int simatic_ipc_batt_probe(struct platform_device *pdev,
|
||||
struct gpiod_lookup_table *table);
|
||||
|
||||
int simatic_ipc_batt_remove(struct platform_device *pdev,
|
||||
struct gpiod_lookup_table *table);
|
||||
|
||||
#endif /* _SIMATIC_IPC_BATT_H */
|
|
@ -0,0 +1,236 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC platform driver
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2018-2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
* Jan Kiszka <jan.kiszka@siemens.com>
|
||||
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/x86/simatic-ipc.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
static struct platform_device *ipc_led_platform_device;
|
||||
static struct platform_device *ipc_wdt_platform_device;
|
||||
static struct platform_device *ipc_batt_platform_device;
|
||||
|
||||
static const struct dmi_system_id simatic_ipc_whitelist[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct simatic_ipc_platform platform_data;
|
||||
|
||||
#define SIMATIC_IPC_MAX_EXTRA_MODULES 2
|
||||
|
||||
static struct {
|
||||
u32 station_id;
|
||||
u8 led_mode;
|
||||
u8 wdt_mode;
|
||||
u8 batt_mode;
|
||||
char *extra_modules[SIMATIC_IPC_MAX_EXTRA_MODULES];
|
||||
} device_modes[] = {
|
||||
{SIMATIC_IPC_IPC127E,
|
||||
SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_127E,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC227D,
|
||||
SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC227E,
|
||||
SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC227G,
|
||||
SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
|
||||
{ "nct6775", "w83627hf_wdt" }},
|
||||
{SIMATIC_IPC_IPC277G,
|
||||
SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
|
||||
{ "nct6775", "w83627hf_wdt" }},
|
||||
{SIMATIC_IPC_IPC277E,
|
||||
SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC427D,
|
||||
SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC427E,
|
||||
SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPC477E,
|
||||
SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPCBX_39A,
|
||||
SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
|
||||
{ "nct6775", "w83627hf_wdt" }},
|
||||
{SIMATIC_IPC_IPCPX_39A,
|
||||
SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
|
||||
{ "nct6775", "w83627hf_wdt" }},
|
||||
{SIMATIC_IPC_IPCBX_21A,
|
||||
SIMATIC_IPC_DEVICE_BX_21A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_21A,
|
||||
{ "emc1403", NULL }},
|
||||
{SIMATIC_IPC_IPCBX_56A,
|
||||
SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
|
||||
{ "emc1403", "w83627hf_wdt" }},
|
||||
{SIMATIC_IPC_IPCBX_59A,
|
||||
SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
|
||||
{ "emc1403", "w83627hf_wdt" }},
|
||||
};
|
||||
|
||||
static int register_platform_devices(u32 station_id)
|
||||
{
|
||||
u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
u8 battmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
char *pdevname;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
|
||||
if (device_modes[i].station_id == station_id) {
|
||||
ledmode = device_modes[i].led_mode;
|
||||
wdtmode = device_modes[i].wdt_mode;
|
||||
battmode = device_modes[i].batt_mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (battmode != SIMATIC_IPC_DEVICE_NONE) {
|
||||
pdevname = KBUILD_MODNAME "_batt";
|
||||
if (battmode == SIMATIC_IPC_DEVICE_127E)
|
||||
pdevname = KBUILD_MODNAME "_batt_apollolake";
|
||||
if (battmode == SIMATIC_IPC_DEVICE_BX_21A)
|
||||
pdevname = KBUILD_MODNAME "_batt_elkhartlake";
|
||||
if (battmode == SIMATIC_IPC_DEVICE_227G ||
|
||||
battmode == SIMATIC_IPC_DEVICE_BX_39A ||
|
||||
battmode == SIMATIC_IPC_DEVICE_BX_59A)
|
||||
pdevname = KBUILD_MODNAME "_batt_f7188x";
|
||||
platform_data.devmode = battmode;
|
||||
ipc_batt_platform_device =
|
||||
platform_device_register_data(NULL, pdevname,
|
||||
PLATFORM_DEVID_NONE, &platform_data,
|
||||
sizeof(struct simatic_ipc_platform));
|
||||
if (IS_ERR(ipc_batt_platform_device))
|
||||
return PTR_ERR(ipc_batt_platform_device);
|
||||
|
||||
pr_debug("device=%s created\n",
|
||||
ipc_batt_platform_device->name);
|
||||
}
|
||||
|
||||
if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
|
||||
pdevname = KBUILD_MODNAME "_leds";
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_127E)
|
||||
pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_227G || ledmode == SIMATIC_IPC_DEVICE_BX_59A)
|
||||
pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_BX_21A)
|
||||
pdevname = KBUILD_MODNAME "_leds_gpio_elkhartlake";
|
||||
platform_data.devmode = ledmode;
|
||||
ipc_led_platform_device =
|
||||
platform_device_register_data(NULL,
|
||||
pdevname, PLATFORM_DEVID_NONE,
|
||||
&platform_data,
|
||||
sizeof(struct simatic_ipc_platform));
|
||||
if (IS_ERR(ipc_led_platform_device))
|
||||
return PTR_ERR(ipc_led_platform_device);
|
||||
|
||||
pr_debug("device=%s created\n",
|
||||
ipc_led_platform_device->name);
|
||||
}
|
||||
|
||||
if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
|
||||
platform_data.devmode = wdtmode;
|
||||
ipc_wdt_platform_device =
|
||||
platform_device_register_data(NULL,
|
||||
KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE,
|
||||
&platform_data,
|
||||
sizeof(struct simatic_ipc_platform));
|
||||
if (IS_ERR(ipc_wdt_platform_device))
|
||||
return PTR_ERR(ipc_wdt_platform_device);
|
||||
|
||||
pr_debug("device=%s created\n",
|
||||
ipc_wdt_platform_device->name);
|
||||
}
|
||||
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_NONE &&
|
||||
wdtmode == SIMATIC_IPC_DEVICE_NONE &&
|
||||
battmode == SIMATIC_IPC_DEVICE_NONE) {
|
||||
pr_warn("unsupported IPC detected, station id=%08x\n",
|
||||
station_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void request_additional_modules(u32 station_id)
|
||||
{
|
||||
char **extra_modules = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
|
||||
if (device_modes[i].station_id == station_id) {
|
||||
extra_modules = device_modes[i].extra_modules;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!extra_modules)
|
||||
return;
|
||||
|
||||
for (i = 0; i < SIMATIC_IPC_MAX_EXTRA_MODULES; i++) {
|
||||
if (extra_modules[i])
|
||||
request_module(extra_modules[i]);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int __init simatic_ipc_init_module(void)
|
||||
{
|
||||
const struct dmi_system_id *match;
|
||||
u32 station_id;
|
||||
int err;
|
||||
|
||||
match = dmi_first_match(simatic_ipc_whitelist);
|
||||
if (!match)
|
||||
return 0;
|
||||
|
||||
err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id);
|
||||
|
||||
if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) {
|
||||
pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
request_additional_modules(station_id);
|
||||
|
||||
return register_platform_devices(station_id);
|
||||
}
|
||||
|
||||
static void __exit simatic_ipc_exit_module(void)
|
||||
{
|
||||
platform_device_unregister(ipc_led_platform_device);
|
||||
ipc_led_platform_device = NULL;
|
||||
|
||||
platform_device_unregister(ipc_wdt_platform_device);
|
||||
ipc_wdt_platform_device = NULL;
|
||||
|
||||
platform_device_unregister(ipc_batt_platform_device);
|
||||
ipc_batt_platform_device = NULL;
|
||||
}
|
||||
|
||||
module_init(simatic_ipc_init_module);
|
||||
module_exit(simatic_ipc_exit_module);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
|
||||
MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");
|
|
@ -1,151 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Siemens SIMATIC IPC platform driver
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2018-2021
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
* Jan Kiszka <jan.kiszka@siemens.com>
|
||||
* Gerd Haeussler <gerd.haeussler.ext@siemens.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_data/x86/simatic-ipc.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
static struct platform_device *ipc_led_platform_device;
|
||||
static struct platform_device *ipc_wdt_platform_device;
|
||||
|
||||
static const struct dmi_system_id simatic_ipc_whitelist[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct simatic_ipc_platform platform_data;
|
||||
|
||||
static struct {
|
||||
u32 station_id;
|
||||
u8 led_mode;
|
||||
u8 wdt_mode;
|
||||
} device_modes[] = {
|
||||
{SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE},
|
||||
{SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE},
|
||||
{SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E},
|
||||
{SIMATIC_IPC_IPC227G, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
|
||||
{SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E},
|
||||
{SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE},
|
||||
{SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E},
|
||||
{SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E},
|
||||
{SIMATIC_IPC_IPCBX_39A, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
|
||||
{SIMATIC_IPC_IPCPX_39A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G},
|
||||
};
|
||||
|
||||
static int register_platform_devices(u32 station_id)
|
||||
{
|
||||
u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
char *pdevname = KBUILD_MODNAME "_leds";
|
||||
int i;
|
||||
|
||||
platform_data.devmode = SIMATIC_IPC_DEVICE_NONE;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
|
||||
if (device_modes[i].station_id == station_id) {
|
||||
ledmode = device_modes[i].led_mode;
|
||||
wdtmode = device_modes[i].wdt_mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_127E)
|
||||
pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_227G)
|
||||
pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
|
||||
platform_data.devmode = ledmode;
|
||||
ipc_led_platform_device =
|
||||
platform_device_register_data(NULL,
|
||||
pdevname, PLATFORM_DEVID_NONE,
|
||||
&platform_data,
|
||||
sizeof(struct simatic_ipc_platform));
|
||||
if (IS_ERR(ipc_led_platform_device))
|
||||
return PTR_ERR(ipc_led_platform_device);
|
||||
|
||||
pr_debug("device=%s created\n",
|
||||
ipc_led_platform_device->name);
|
||||
}
|
||||
|
||||
if (wdtmode == SIMATIC_IPC_DEVICE_227G) {
|
||||
request_module("w83627hf_wdt");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
|
||||
platform_data.devmode = wdtmode;
|
||||
ipc_wdt_platform_device =
|
||||
platform_device_register_data(NULL,
|
||||
KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE,
|
||||
&platform_data,
|
||||
sizeof(struct simatic_ipc_platform));
|
||||
if (IS_ERR(ipc_wdt_platform_device))
|
||||
return PTR_ERR(ipc_wdt_platform_device);
|
||||
|
||||
pr_debug("device=%s created\n",
|
||||
ipc_wdt_platform_device->name);
|
||||
}
|
||||
|
||||
if (ledmode == SIMATIC_IPC_DEVICE_NONE &&
|
||||
wdtmode == SIMATIC_IPC_DEVICE_NONE) {
|
||||
pr_warn("unsupported IPC detected, station id=%08x\n",
|
||||
station_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init simatic_ipc_init_module(void)
|
||||
{
|
||||
const struct dmi_system_id *match;
|
||||
u32 station_id;
|
||||
int err;
|
||||
|
||||
match = dmi_first_match(simatic_ipc_whitelist);
|
||||
if (!match)
|
||||
return 0;
|
||||
|
||||
err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id);
|
||||
|
||||
if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) {
|
||||
pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return register_platform_devices(station_id);
|
||||
}
|
||||
|
||||
static void __exit simatic_ipc_exit_module(void)
|
||||
{
|
||||
platform_device_unregister(ipc_led_platform_device);
|
||||
ipc_led_platform_device = NULL;
|
||||
|
||||
platform_device_unregister(ipc_wdt_platform_device);
|
||||
ipc_wdt_platform_device = NULL;
|
||||
}
|
||||
|
||||
module_init(simatic_ipc_init_module);
|
||||
module_exit(simatic_ipc_exit_module);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
|
||||
MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* System76 ACPI Driver
|
||||
*
|
||||
* Copyright (C) 2019 System76
|
||||
* Copyright (C) 2023 System76
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
|
@ -24,6 +24,12 @@
|
|||
|
||||
#include <acpi/battery.h>
|
||||
|
||||
enum kbled_type {
|
||||
KBLED_NONE,
|
||||
KBLED_WHITE,
|
||||
KBLED_RGB,
|
||||
};
|
||||
|
||||
struct system76_data {
|
||||
struct acpi_device *acpi_dev;
|
||||
struct led_classdev ap_led;
|
||||
|
@ -36,6 +42,7 @@ struct system76_data {
|
|||
union acpi_object *ntmp;
|
||||
struct input_dev *input;
|
||||
bool has_open_ec;
|
||||
enum kbled_type kbled_type;
|
||||
};
|
||||
|
||||
static const struct acpi_device_id device_ids[] = {
|
||||
|
@ -327,7 +334,11 @@ static int kb_led_set(struct led_classdev *led, enum led_brightness value)
|
|||
|
||||
data = container_of(led, struct system76_data, kb_led);
|
||||
data->kb_brightness = value;
|
||||
return system76_set(data, "SKBL", (int)data->kb_brightness);
|
||||
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
|
||||
return system76_set(data, "SKBB", (int)data->kb_brightness);
|
||||
} else {
|
||||
return system76_set(data, "SKBL", (int)data->kb_brightness);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the last set keyboard LED color
|
||||
|
@ -399,7 +410,12 @@ static void kb_led_hotkey_hardware(struct system76_data *data)
|
|||
{
|
||||
int value;
|
||||
|
||||
value = system76_get(data, "GKBL");
|
||||
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
|
||||
value = system76_get(data, "GKBB");
|
||||
} else {
|
||||
value = system76_get(data, "GKBL");
|
||||
}
|
||||
|
||||
if (value < 0)
|
||||
return;
|
||||
data->kb_brightness = value;
|
||||
|
@ -459,8 +475,9 @@ static void kb_led_hotkey_color(struct system76_data *data)
|
|||
{
|
||||
int i;
|
||||
|
||||
if (data->kb_color < 0)
|
||||
if (data->kbled_type != KBLED_RGB)
|
||||
return;
|
||||
|
||||
if (data->kb_brightness > 0) {
|
||||
for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
|
||||
if (kb_colors[i] == data->kb_color)
|
||||
|
@ -687,19 +704,46 @@ static int system76_add(struct acpi_device *acpi_dev)
|
|||
data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME;
|
||||
data->kb_led.brightness_get = kb_led_get;
|
||||
data->kb_led.brightness_set_blocking = kb_led_set;
|
||||
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
|
||||
data->kb_led.max_brightness = 255;
|
||||
data->kb_led.groups = system76_kb_led_color_groups;
|
||||
data->kb_toggle_brightness = 72;
|
||||
data->kb_color = 0xffffff;
|
||||
system76_set(data, "SKBC", data->kb_color);
|
||||
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
|
||||
// Use the new ACPI methods
|
||||
data->kbled_type = system76_get(data, "GKBK");
|
||||
|
||||
switch (data->kbled_type) {
|
||||
case KBLED_NONE:
|
||||
// Nothing to do: Device will not be registered.
|
||||
break;
|
||||
case KBLED_WHITE:
|
||||
data->kb_led.max_brightness = 255;
|
||||
data->kb_toggle_brightness = 72;
|
||||
break;
|
||||
case KBLED_RGB:
|
||||
data->kb_led.max_brightness = 255;
|
||||
data->kb_led.groups = system76_kb_led_color_groups;
|
||||
data->kb_toggle_brightness = 72;
|
||||
data->kb_color = 0xffffff;
|
||||
system76_set(data, "SKBC", data->kb_color);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
data->kb_led.max_brightness = 5;
|
||||
data->kb_color = -1;
|
||||
// Use the old ACPI methods
|
||||
if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
|
||||
data->kbled_type = KBLED_RGB;
|
||||
data->kb_led.max_brightness = 255;
|
||||
data->kb_led.groups = system76_kb_led_color_groups;
|
||||
data->kb_toggle_brightness = 72;
|
||||
data->kb_color = 0xffffff;
|
||||
system76_set(data, "SKBC", data->kb_color);
|
||||
} else {
|
||||
data->kbled_type = KBLED_WHITE;
|
||||
data->kb_led.max_brightness = 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->kbled_type != KBLED_NONE) {
|
||||
err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
data->input = devm_input_allocate_device(&acpi_dev->dev);
|
||||
if (!data->input)
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#include <linux/kthread.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/nvram.h>
|
||||
|
@ -907,16 +908,9 @@ static ssize_t dispatch_proc_write(struct file *file,
|
|||
if (count > PAGE_SIZE - 1)
|
||||
return -EINVAL;
|
||||
|
||||
kernbuf = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!kernbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (copy_from_user(kernbuf, userbuf, count)) {
|
||||
kfree(kernbuf);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
kernbuf[count] = 0;
|
||||
kernbuf = memdup_user_nul(userbuf, count);
|
||||
if (IS_ERR(kernbuf))
|
||||
return PTR_ERR(kernbuf);
|
||||
ret = ibm->write(kernbuf);
|
||||
if (ret == 0)
|
||||
ret = count;
|
||||
|
@ -2066,11 +2060,11 @@ static int hotkey_get_tablet_mode(int *status)
|
|||
* hotkey_acpi_mask accordingly. Also resets any bits
|
||||
* from hotkey_user_mask that are unavailable to be
|
||||
* delivered (shadow requirement of the userspace ABI).
|
||||
*
|
||||
* Call with hotkey_mutex held
|
||||
*/
|
||||
static int hotkey_mask_get(void)
|
||||
{
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
if (tp_features.hotkey_mask) {
|
||||
u32 m = 0;
|
||||
|
||||
|
@ -2106,8 +2100,6 @@ static void hotkey_mask_warn_incomplete_mask(void)
|
|||
* Also calls hotkey_mask_get to update hotkey_acpi_mask.
|
||||
*
|
||||
* NOTE: does not set bits in hotkey_user_mask, but may reset them.
|
||||
*
|
||||
* Call with hotkey_mutex held
|
||||
*/
|
||||
static int hotkey_mask_set(u32 mask)
|
||||
{
|
||||
|
@ -2116,6 +2108,8 @@ static int hotkey_mask_set(u32 mask)
|
|||
|
||||
const u32 fwmask = mask & ~hotkey_source_mask;
|
||||
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
if (tp_features.hotkey_mask) {
|
||||
for (i = 0; i < 32; i++) {
|
||||
if (!acpi_evalf(hkey_handle,
|
||||
|
@ -2147,13 +2141,13 @@ static int hotkey_mask_set(u32 mask)
|
|||
|
||||
/*
|
||||
* Sets hotkey_user_mask and tries to set the firmware mask
|
||||
*
|
||||
* Call with hotkey_mutex held
|
||||
*/
|
||||
static int hotkey_user_mask_set(const u32 mask)
|
||||
{
|
||||
int rc;
|
||||
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
/* Give people a chance to notice they are doing something that
|
||||
* is bound to go boom on their users sooner or later */
|
||||
if (!tp_warned.hotkey_mask_ff &&
|
||||
|
@ -2514,21 +2508,23 @@ exit:
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* call with hotkey_mutex held */
|
||||
static void hotkey_poll_stop_sync(void)
|
||||
{
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
if (tpacpi_hotkey_task) {
|
||||
kthread_stop(tpacpi_hotkey_task);
|
||||
tpacpi_hotkey_task = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* call with hotkey_mutex held */
|
||||
static void hotkey_poll_setup(const bool may_warn)
|
||||
{
|
||||
const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
|
||||
const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
|
||||
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
if (hotkey_poll_freq > 0 &&
|
||||
(poll_driver_mask ||
|
||||
(poll_user_mask && tpacpi_inputdev->users > 0))) {
|
||||
|
@ -2557,9 +2553,10 @@ static void hotkey_poll_setup_safe(const bool may_warn)
|
|||
mutex_unlock(&hotkey_mutex);
|
||||
}
|
||||
|
||||
/* call with hotkey_mutex held */
|
||||
static void hotkey_poll_set_freq(unsigned int freq)
|
||||
{
|
||||
lockdep_assert_held(&hotkey_mutex);
|
||||
|
||||
if (!freq)
|
||||
hotkey_poll_stop_sync();
|
||||
|
||||
|
@ -3473,7 +3470,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
|
|||
if (tp_features.hotkey_mask) {
|
||||
/* hotkey_source_mask *must* be zero for
|
||||
* the first hotkey_mask_get to return hotkey_orig_mask */
|
||||
mutex_lock(&hotkey_mutex);
|
||||
res = hotkey_mask_get();
|
||||
mutex_unlock(&hotkey_mutex);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
|
@ -3572,9 +3571,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
|
|||
hotkey_exit();
|
||||
return res;
|
||||
}
|
||||
mutex_lock(&hotkey_mutex);
|
||||
res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
|
||||
| hotkey_driver_mask)
|
||||
& ~hotkey_source_mask);
|
||||
mutex_unlock(&hotkey_mutex);
|
||||
if (res < 0 && res != -ENXIO) {
|
||||
hotkey_exit();
|
||||
return res;
|
||||
|
@ -6528,12 +6529,13 @@ static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
|
|||
|
||||
static struct mutex brightness_mutex;
|
||||
|
||||
/* NVRAM brightness access,
|
||||
* call with brightness_mutex held! */
|
||||
/* NVRAM brightness access */
|
||||
static unsigned int tpacpi_brightness_nvram_get(void)
|
||||
{
|
||||
u8 lnvram;
|
||||
|
||||
lockdep_assert_held(&brightness_mutex);
|
||||
|
||||
lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
|
||||
& TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
||||
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
|
||||
|
@ -6581,11 +6583,12 @@ unlock:
|
|||
}
|
||||
|
||||
|
||||
/* call with brightness_mutex held! */
|
||||
static int tpacpi_brightness_get_raw(int *status)
|
||||
{
|
||||
u8 lec = 0;
|
||||
|
||||
lockdep_assert_held(&brightness_mutex);
|
||||
|
||||
switch (brightness_mode) {
|
||||
case TPACPI_BRGHT_MODE_UCMS_STEP:
|
||||
*status = tpacpi_brightness_nvram_get();
|
||||
|
@ -6601,12 +6604,13 @@ static int tpacpi_brightness_get_raw(int *status)
|
|||
}
|
||||
}
|
||||
|
||||
/* call with brightness_mutex held! */
|
||||
/* do NOT call with illegal backlight level value */
|
||||
static int tpacpi_brightness_set_ec(unsigned int value)
|
||||
{
|
||||
u8 lec = 0;
|
||||
|
||||
lockdep_assert_held(&brightness_mutex);
|
||||
|
||||
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
|
||||
return -EIO;
|
||||
|
||||
|
@ -6618,12 +6622,13 @@ static int tpacpi_brightness_set_ec(unsigned int value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* call with brightness_mutex held! */
|
||||
static int tpacpi_brightness_set_ucmsstep(unsigned int value)
|
||||
{
|
||||
int cmos_cmd, inc;
|
||||
unsigned int current_value, i;
|
||||
|
||||
lockdep_assert_held(&brightness_mutex);
|
||||
|
||||
current_value = tpacpi_brightness_nvram_get();
|
||||
|
||||
if (value == current_value)
|
||||
|
@ -8072,11 +8077,10 @@ static bool fan_select_fan2(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call with fan_mutex held
|
||||
*/
|
||||
static void fan_update_desired_level(u8 status)
|
||||
{
|
||||
lockdep_assert_held(&fan_mutex);
|
||||
|
||||
if ((status &
|
||||
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
|
||||
if (status > 7)
|
||||
|
|
|
@ -25,25 +25,13 @@ struct bmof_priv {
|
|||
struct bin_attribute bmof_bin_attr;
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
read_bmof(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static ssize_t read_bmof(struct file *filp, struct kobject *kobj, struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct bmof_priv *priv =
|
||||
container_of(attr, struct bmof_priv, bmof_bin_attr);
|
||||
struct bmof_priv *priv = container_of(attr, struct bmof_priv, bmof_bin_attr);
|
||||
|
||||
if (off < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (off >= priv->bmofdata->buffer.length)
|
||||
return 0;
|
||||
|
||||
if (count > priv->bmofdata->buffer.length - off)
|
||||
count = priv->bmofdata->buffer.length - off;
|
||||
|
||||
memcpy(buf, priv->bmofdata->buffer.pointer + off, count);
|
||||
return count;
|
||||
return memory_read_from_buffer(buf, count, &off, priv->bmofdata->buffer.pointer,
|
||||
priv->bmofdata->buffer.length);
|
||||
}
|
||||
|
||||
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
|
||||
|
@ -75,7 +63,7 @@ static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
|
|||
priv->bmof_bin_attr.read = read_bmof;
|
||||
priv->bmof_bin_attr.size = priv->bmofdata->buffer.length;
|
||||
|
||||
ret = sysfs_create_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);
|
||||
ret = device_create_bin_file(&wdev->dev, &priv->bmof_bin_attr);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
|
@ -90,7 +78,7 @@ static void wmi_bmof_remove(struct wmi_device *wdev)
|
|||
{
|
||||
struct bmof_priv *priv = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
sysfs_remove_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);
|
||||
device_remove_bin_file(&wdev->dev, &priv->bmof_bin_attr);
|
||||
kfree(priv->bmofdata);
|
||||
}
|
||||
|
||||
|
|
|
@ -1680,9 +1680,10 @@ config NIC7018_WDT
|
|||
|
||||
config SIEMENS_SIMATIC_IPC_WDT
|
||||
tristate "Siemens Simatic IPC Watchdog"
|
||||
depends on SIEMENS_SIMATIC_IPC
|
||||
depends on SIEMENS_SIMATIC_IPC && PCI
|
||||
default y
|
||||
select WATCHDOG_CORE
|
||||
select P2SB
|
||||
select P2SB if X86
|
||||
help
|
||||
This driver adds support for several watchdogs found in Industrial
|
||||
PCs from Siemens.
|
||||
|
|
|
@ -155,9 +155,8 @@ static int simatic_ipc_wdt_probe(struct platform_device *pdev)
|
|||
|
||||
switch (plat->devmode) {
|
||||
case SIMATIC_IPC_DEVICE_227E:
|
||||
if (!devm_request_region(dev, gp_status_reg_227e_res.start,
|
||||
resource_size(&gp_status_reg_227e_res),
|
||||
KBUILD_MODNAME)) {
|
||||
res = &gp_status_reg_227e_res;
|
||||
if (!request_muxed_region(res->start, resource_size(res), res->name)) {
|
||||
dev_err(dev,
|
||||
"Unable to register IO resource at %pR\n",
|
||||
&gp_status_reg_227e_res);
|
||||
|
@ -210,6 +209,10 @@ static int simatic_ipc_wdt_probe(struct platform_device *pdev)
|
|||
if (wdd_data.bootstatus)
|
||||
dev_warn(dev, "last reboot caused by watchdog reset\n");
|
||||
|
||||
if (plat->devmode == SIMATIC_IPC_DEVICE_227E)
|
||||
release_region(gp_status_reg_227e_res.start,
|
||||
resource_size(&gp_status_reg_227e_res));
|
||||
|
||||
watchdog_set_nowayout(&wdd_data, nowayout);
|
||||
watchdog_stop_on_reboot(&wdd_data);
|
||||
return devm_watchdog_register_device(dev, &wdd_data);
|
||||
|
|
|
@ -27,4 +27,6 @@ struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *aux
|
|||
struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index);
|
||||
int tpmi_get_resource_count(struct auxiliary_device *auxdev);
|
||||
|
||||
int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, int *locked,
|
||||
int *disabled);
|
||||
#endif
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
#define ASUS_WMI_DEVID_CAMERA 0x00060013
|
||||
#define ASUS_WMI_DEVID_LID_FLIP 0x00060062
|
||||
#define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077
|
||||
#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E
|
||||
|
||||
/* Storage */
|
||||
#define ASUS_WMI_DEVID_CARDREADER 0x00080013
|
||||
|
@ -80,8 +81,19 @@
|
|||
#define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */
|
||||
#define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013
|
||||
#define ASUS_WMI_DEVID_GPU_FAN_CTRL 0x00110014
|
||||
#define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031
|
||||
#define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
|
||||
#define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
|
||||
#define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032
|
||||
|
||||
/* Tunables for AUS ROG laptops */
|
||||
#define ASUS_WMI_DEVID_PPT_PL2_SPPT 0x001200A0
|
||||
#define ASUS_WMI_DEVID_PPT_PL1_SPL 0x001200A3
|
||||
#define ASUS_WMI_DEVID_PPT_APU_SPPT 0x001200B0
|
||||
#define ASUS_WMI_DEVID_PPT_PLAT_SPPT 0x001200B1
|
||||
#define ASUS_WMI_DEVID_PPT_FPPT 0x001200C1
|
||||
#define ASUS_WMI_DEVID_NV_DYN_BOOST 0x001200C0
|
||||
#define ASUS_WMI_DEVID_NV_THERM_TARGET 0x001200C2
|
||||
|
||||
/* Power */
|
||||
#define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
|
||||
|
@ -95,7 +107,12 @@
|
|||
/* Keyboard dock */
|
||||
#define ASUS_WMI_DEVID_KBD_DOCK 0x00120063
|
||||
|
||||
/* dgpu on/off */
|
||||
/* Charging mode - 1=Barrel, 2=USB */
|
||||
#define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C
|
||||
|
||||
/* epu is connected? 1 == true */
|
||||
#define ASUS_WMI_DEVID_EGPU_CONNECTED 0x00090018
|
||||
/* egpu on/off */
|
||||
#define ASUS_WMI_DEVID_EGPU 0x00090019
|
||||
|
||||
/* dgpu on/off */
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Siemens SIMATIC IPC drivers
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2018-2021
|
||||
* Copyright (c) Siemens AG, 2018-2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
|
@ -20,6 +20,9 @@
|
|||
#define SIMATIC_IPC_DEVICE_127E 3
|
||||
#define SIMATIC_IPC_DEVICE_227E 4
|
||||
#define SIMATIC_IPC_DEVICE_227G 5
|
||||
#define SIMATIC_IPC_DEVICE_BX_21A 6
|
||||
#define SIMATIC_IPC_DEVICE_BX_39A 7
|
||||
#define SIMATIC_IPC_DEVICE_BX_59A 8
|
||||
|
||||
struct simatic_ipc_platform {
|
||||
u8 devmode;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Siemens SIMATIC IPC drivers
|
||||
*
|
||||
* Copyright (c) Siemens AG, 2018-2021
|
||||
* Copyright (c) Siemens AG, 2018-2023
|
||||
*
|
||||
* Authors:
|
||||
* Henning Schild <henning.schild@siemens.com>
|
||||
|
@ -32,8 +32,12 @@ enum simatic_ipc_station_ids {
|
|||
SIMATIC_IPC_IPC477E = 0x00000A02,
|
||||
SIMATIC_IPC_IPC127E = 0x00000D01,
|
||||
SIMATIC_IPC_IPC227G = 0x00000F01,
|
||||
SIMATIC_IPC_IPC277G = 0x00000F02,
|
||||
SIMATIC_IPC_IPCBX_39A = 0x00001001,
|
||||
SIMATIC_IPC_IPCPX_39A = 0x00001002,
|
||||
SIMATIC_IPC_IPCBX_21A = 0x00001101,
|
||||
SIMATIC_IPC_IPCBX_56A = 0x00001201,
|
||||
SIMATIC_IPC_IPCBX_59A = 0x00001202,
|
||||
};
|
||||
|
||||
static inline u32 simatic_ipc_get_station_id(u8 *data, int max_len)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/isst_if.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "isst.h"
|
||||
|
||||
|
@ -15,7 +16,7 @@ struct process_cmd_struct {
|
|||
int arg;
|
||||
};
|
||||
|
||||
static const char *version_str = "v1.16";
|
||||
static const char *version_str = "v1.17";
|
||||
|
||||
static const int supported_api_ver = 2;
|
||||
static struct isst_if_platform_info isst_platform_info;
|
||||
|
@ -473,11 +474,44 @@ static unsigned int is_cpu_online(int cpu)
|
|||
return online;
|
||||
}
|
||||
|
||||
static int get_kernel_version(int *major, int *minor)
|
||||
{
|
||||
struct utsname buf;
|
||||
int ret;
|
||||
|
||||
ret = uname(&buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = sscanf(buf.release, "%d.%d", major, minor);
|
||||
if (ret != 2)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CPU0_HOTPLUG_DEPRECATE_MAJOR_VER 6
|
||||
#define CPU0_HOTPLUG_DEPRECATE_MINOR_VER 5
|
||||
|
||||
void set_cpu_online_offline(int cpu, int state)
|
||||
{
|
||||
char buffer[128];
|
||||
int fd, ret;
|
||||
|
||||
if (!cpu) {
|
||||
int major, minor;
|
||||
|
||||
ret = get_kernel_version(&major, &minor);
|
||||
if (!ret) {
|
||||
if (major > CPU0_HOTPLUG_DEPRECATE_MAJOR_VER || (major == CPU0_HOTPLUG_DEPRECATE_MAJOR_VER &&
|
||||
minor >= CPU0_HOTPLUG_DEPRECATE_MINOR_VER)) {
|
||||
debug_printf("Ignore CPU 0 offline/online for kernel version >= %d.%d\n", major, minor);
|
||||
debug_printf("Use cgroups to isolate CPU 0\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"/sys/devices/system/cpu/cpu%d/online", cpu);
|
||||
|
||||
|
@ -778,6 +812,7 @@ static void create_cpu_map(void)
|
|||
map.cpu_map[0].logical_cpu);
|
||||
} else {
|
||||
update_punit_cpu_info(map.cpu_map[0].physical_cpu, &cpu_map[i]);
|
||||
punit_id = cpu_map[i].punit_id;
|
||||
}
|
||||
}
|
||||
cpu_map[i].initialized = 1;
|
||||
|
@ -2621,10 +2656,11 @@ static struct process_cmd_struct isst_cmds[] = {
|
|||
*/
|
||||
void parse_cpu_command(char *optarg)
|
||||
{
|
||||
unsigned int start, end;
|
||||
unsigned int start, end, invalid_count;
|
||||
char *next;
|
||||
|
||||
next = optarg;
|
||||
invalid_count = 0;
|
||||
|
||||
while (next && *next) {
|
||||
if (*next == '-') /* no negative cpu numbers */
|
||||
|
@ -2634,6 +2670,8 @@ void parse_cpu_command(char *optarg)
|
|||
|
||||
if (max_target_cpus < MAX_CPUS_IN_ONE_REQ)
|
||||
target_cpus[max_target_cpus++] = start;
|
||||
else
|
||||
invalid_count = 1;
|
||||
|
||||
if (*next == '\0')
|
||||
break;
|
||||
|
@ -2660,6 +2698,8 @@ void parse_cpu_command(char *optarg)
|
|||
while (++start <= end) {
|
||||
if (max_target_cpus < MAX_CPUS_IN_ONE_REQ)
|
||||
target_cpus[max_target_cpus++] = start;
|
||||
else
|
||||
invalid_count = 1;
|
||||
}
|
||||
|
||||
if (*next == ',')
|
||||
|
@ -2668,6 +2708,13 @@ void parse_cpu_command(char *optarg)
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (invalid_count) {
|
||||
isst_ctdp_display_information_start(outf);
|
||||
isst_display_error_info_message(1, "Too many CPUs in one request: max is", 1, MAX_CPUS_IN_ONE_REQ - 1);
|
||||
isst_ctdp_display_information_end(outf);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
int i;
|
||||
|
|
|
@ -442,7 +442,7 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level
|
|||
}
|
||||
|
||||
if (ctdp_level->mem_freq) {
|
||||
snprintf(header, sizeof(header), "mem-frequency(MHz)");
|
||||
snprintf(header, sizeof(header), "max-mem-frequency(MHz)");
|
||||
snprintf(value, sizeof(value), "%d",
|
||||
ctdp_level->mem_freq);
|
||||
format_and_print(outf, level + 2, header, value);
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
|
||||
#define DISP_FREQ_MULTIPLIER 100
|
||||
|
||||
#define MAX_PACKAGE_COUNT 8
|
||||
#define MAX_PACKAGE_COUNT 32
|
||||
#define MAX_DIE_PER_PACKAGE 2
|
||||
#define MAX_PUNIT_PER_DIE 8
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче