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:
Linus Torvalds 2023-09-01 09:16:47 -07:00
commit e2c874f999
64 changed files with 8318 additions and 353 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &regval);
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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:*");

View File

@ -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:*");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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