diff --git a/Documentation/ABI/removed/sysfs-mce b/Documentation/ABI/removed/sysfs-mce new file mode 100644 index 000000000000..ef5dd2a80918 --- /dev/null +++ b/Documentation/ABI/removed/sysfs-mce @@ -0,0 +1,37 @@ +What: /sys/devices/system/machinecheck/machinecheckX/tolerant +Contact: Borislav Petkov +Date: Dec, 2021 +Description: + Unused and obsolete after the advent of recoverable machine + checks (see last sentence below) and those are present since + 2010 (Nehalem). + + Original description: + + The entries appear for each CPU, but they are truly shared + between all CPUs. + + Tolerance level. When a machine check exception occurs for a + non corrected machine check the kernel can take different + actions. + + Since machine check exceptions can happen any time it is + sometimes risky for the kernel to kill a process because it + defies normal kernel locking rules. The tolerance level + configures how hard the kernel tries to recover even at some + risk of deadlock. Higher tolerant values trade potentially + better uptime with the risk of a crash or even corruption + (for tolerant >= 3). + + == =========================================================== + 0 always panic on uncorrected errors, log corrected errors + 1 panic or SIGBUS on uncorrected errors, log corrected errors + 2 SIGBUS or log uncorrected errors, log corrected errors + 3 never panic or SIGBUS, log all errors (for testing only) + == =========================================================== + + Default: 1 + + Note this only makes a difference if the CPU allows recovery + from a machine check exception. Current x86 CPUs generally + do not. diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 13e31c6a0e9c..05820365f1ec 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -246,6 +246,51 @@ Description: that is being referenced (e.g hdd0, hdd1 etc) This attribute defaults to device 0. + certificate: + signature: + save_signature: + These attributes are used for certificate based authentication. This is + used in conjunction with a signing server as an alternative to password + based authentication. + The user writes to the attribute(s) with a BASE64 encoded string obtained + from the signing server. + The attributes can be displayed to check the stored value. + + Some usage examples: + Installing a certificate to enable feature: + echo authentication/Admin/current_password + echo > authentication/Admin/certificate + + Updating the installed certificate: + echo > authentication/Admin/signature + echo > authentication/Admin/certificate + + Removing the installed certificate: + echo > authentication/Admin/signature + echo '' > authentication/Admin/certificate + + Changing a BIOS setting: + echo > authentication/Admin/signature + echo > authentication/Admin/save_signature + echo Enable > attribute/PasswordBeep/current_value + + You cannot enable certificate authentication if a supervisor password + has not been set. + Clearing the certificate results in no bios-admin authentication method + being configured allowing anyone to make changes. + After any of these operations the system must reboot for the changes to + take effect. + + certificate_thumbprint: + Read only attribute used to display the MD5, SHA1 and SHA256 thumbprints + for the certificate installed in the BIOS. + + certificate_to_password: + Write only attribute used to switch from certificate based authentication + back to password based. + Usage: + echo > authentication/Admin/signature + echo > authentication/Admin/certificate_to_password What: /sys/class/firmware-attributes/*/attributes/pending_reboot diff --git a/Documentation/ABI/testing/sysfs-driver-intel_sdsi b/Documentation/ABI/testing/sysfs-driver-intel_sdsi new file mode 100644 index 000000000000..ab122125ff9a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intel_sdsi @@ -0,0 +1,77 @@ +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + This directory contains interface files for accessing Intel + Software Defined Silicon (SDSi) features on a CPU. X + represents the socket instance (though not the socket ID). + The socket ID is determined by reading the registers file + and decoding it per the specification. + + Some files communicate with SDSi hardware through a mailbox. + Should the operation fail, one of the following error codes + may be returned: + + Error Code Cause + ---------- ----- + EIO General mailbox failure. Log may indicate cause. + EBUSY Mailbox is owned by another agent. + EPERM SDSI capability is not enabled in hardware. + EPROTO Failure in mailbox protocol detected by driver. + See log for details. + EOVERFLOW For provision commands, the size of the data + exceeds what may be written. + ESPIPE Seeking is not allowed. + ETIMEDOUT Failure to complete mailbox transaction in time. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/guid +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) The GUID for the registers file. The GUID identifies + the layout of the registers file in this directory. + Information about the register layouts for a particular GUID + is available at http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/registers +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) Contains information needed by applications to provision + a CPU and monitor status information. The layout of this file + is determined by the GUID in this directory. Information about + the layout for a particular GUID is available at + http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_akc +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (WO) Used to write an Authentication Key Certificate (AKC) to + the SDSi NVRAM for the CPU. The AKC is used to authenticate a + Capability Activation Payload. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_cap +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (WO) Used to write a Capability Activation Payload (CAP) to the + SDSi NVRAM for the CPU. CAPs are used to activate a given CPU + feature. A CAP is validated by SDSi hardware using a previously + provisioned AKC file. Upon successful authentication, the CPU + configuration is updated. A cold reboot is required to fully + activate the feature. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/state_certificate +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) Used to read back the current State Certificate for the CPU + from SDSi hardware. The State Certificate contains information + about the current licenses on the CPU. Mailbox command. diff --git a/Documentation/ABI/testing/sysfs-mce b/Documentation/ABI/testing/sysfs-mce index c8cd989034b4..83172f50e27c 100644 --- a/Documentation/ABI/testing/sysfs-mce +++ b/Documentation/ABI/testing/sysfs-mce @@ -53,38 +53,6 @@ Description: (but some corrected errors might be still reported in other ways) -What: /sys/devices/system/machinecheck/machinecheckX/tolerant -Contact: Andi Kleen -Date: Feb, 2007 -Description: - The entries appear for each CPU, but they are truly shared - between all CPUs. - - Tolerance level. When a machine check exception occurs for a - non corrected machine check the kernel can take different - actions. - - Since machine check exceptions can happen any time it is - sometimes risky for the kernel to kill a process because it - defies normal kernel locking rules. The tolerance level - configures how hard the kernel tries to recover even at some - risk of deadlock. Higher tolerant values trade potentially - better uptime with the risk of a crash or even corruption - (for tolerant >= 3). - - == =========================================================== - 0 always panic on uncorrected errors, log corrected errors - 1 panic or SIGBUS on uncorrected errors, log corrected errors - 2 SIGBUS or log uncorrected errors, log corrected errors - 3 never panic or SIGBUS, log all errors (for testing only) - == =========================================================== - - Default: 1 - - Note this only makes a difference if the CPU allows recovery - from a machine check exception. Current x86 CPUs generally - do not. - What: /sys/devices/system/machinecheck/machinecheckX/trigger Contact: Andi Kleen Date: Feb, 2007 diff --git a/Documentation/ABI/testing/sysfs-platform-lg-laptop b/Documentation/ABI/testing/sysfs-platform-lg-laptop index cf47749b19df..0570cd524d0e 100644 --- a/Documentation/ABI/testing/sysfs-platform-lg-laptop +++ b/Documentation/ABI/testing/sysfs-platform-lg-laptop @@ -17,6 +17,7 @@ Date: October 2018 KernelVersion: 4.20 Contact: "Matan Ziv-Av Description: + Deprecated use /sys/class/power_supply/CMB0/charge_control_end_threshold Maximal battery charge level. Accepted values are 80 or 100. What: /sys/devices/platform/lg-laptop/fan_mode diff --git a/Documentation/admin-guide/laptops/lg-laptop.rst b/Documentation/admin-guide/laptops/lg-laptop.rst index 6fbe165dcd27..67fd6932cef4 100644 --- a/Documentation/admin-guide/laptops/lg-laptop.rst +++ b/Documentation/admin-guide/laptops/lg-laptop.rst @@ -38,7 +38,7 @@ FN lock. Battery care limit ------------------ -Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit +Writing 80/100 to /sys/class/power_supply/CMB0/charge_control_end_threshold sets the maximum capacity to charge the battery. Limiting the charge reduces battery capacity loss over time. diff --git a/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml b/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml new file mode 100644 index 000000000000..7c41d8e814cd --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/airoha,en7523-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha EN7523 GPIO controller + +maintainers: + - John Crispin + +description: | + Airoha's GPIO controller on their ARM EN7523 SoCs consists of two banks of 32 + GPIOs. + +properties: + $nodename: + pattern: "^gpio@[0-9a-f]+$" + + compatible: + items: + - const: airoha,en7523-gpio + + reg: + description: | + The first tuple points to the input register. + The second and third tuple point to the direction registers + The fourth tuple points to the output register + maxItems: 4 + + "#gpio-cells": + const: 2 + + gpio-controller: true + +required: + - compatible + - reg + - "#gpio-cells" + - gpio-controller + +additionalProperties: false + +examples: + - | + gpio0: gpio@1fbf0200 { + compatible = "airoha,en7523-gpio"; + reg = <0x1fbf0204 0x4>, + <0x1fbf0200 0x4>, + <0x1fbf0220 0x4>, + <0x1fbf0214 0x4>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio1: gpio@1fbf0270 { + compatible = "airoha,en7523-gpio"; + reg = <0x1fbf0270 0x4>, + <0x1fbf0260 0x4>, + <0x1fbf0264 0x4>, + <0x1fbf0278 0x4>; + gpio-controller; + #gpio-cells = <2>; + }; + +... diff --git a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt deleted file mode 100644 index d04236558619..000000000000 --- a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt +++ /dev/null @@ -1,27 +0,0 @@ -Faraday Technology FTGPIO010 GPIO Controller - -Required properties: - -- compatible : Should be one of - "cortina,gemini-gpio", "faraday,ftgpio010" - "moxa,moxart-gpio", "faraday,ftgpio010" - "faraday,ftgpio010" -- reg : Should contain registers location and length -- interrupts : Should contain the interrupt line for the GPIO block -- gpio-controller : marks this as a GPIO controller -- #gpio-cells : Should be 2, see gpio/gpio.txt -- interrupt-controller : marks this as an interrupt controller -- #interrupt-cells : a standard two-cell interrupt flag, see - interrupt-controller/interrupts.txt - -Example: - -gpio@4d000000 { - compatible = "cortina,gemini-gpio", "faraday,ftgpio010"; - reg = <0x4d000000 0x100>; - interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; -}; diff --git a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml new file mode 100644 index 000000000000..640da5b9b0cc --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/faraday,ftgpio010.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Faraday Technology FTGPIO010 GPIO Controller + +maintainers: + - Linus Walleij + +properties: + compatible: + oneOf: + - items: + - const: cortina,gemini-gpio + - const: faraday,ftgpio010 + - items: + - const: moxa,moxart-gpio + - const: faraday,ftgpio010 + - const: faraday,ftgpio010 + + reg: + maxItems: 1 + + resets: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + description: Should contain the interrupt line for the GPIO block + + gpio-controller: true + "#gpio-cells": + const: 2 + + interrupt-controller: true + "#interrupt-cells": + const: 2 + +required: + - compatible + - reg + - interrupts + - "#gpio-cells" + - interrupt-controller + - "#interrupt-cells" + +additionalProperties: false + +examples: + - | + #include + gpio@4d000000 { + compatible = "cortina,gemini-gpio", "faraday,ftgpio010"; + reg = <0x4d000000 0x100>; + interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml index e1359391d3a4..d2c39dba56ad 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml @@ -25,7 +25,9 @@ properties: - const: fsl,imx7ulp-gpio - const: fsl,vf610-gpio - items: - - const: fsl,imx8ulp-gpio + - enum: + - fsl,imx93-gpio + - fsl,imx8ulp-gpio - const: fsl,imx7ulp-gpio reg: diff --git a/Documentation/devicetree/bindings/gpio/gpio.txt b/Documentation/devicetree/bindings/gpio/gpio.txt index a8895d339bfe..5663e71b751f 100644 --- a/Documentation/devicetree/bindings/gpio/gpio.txt +++ b/Documentation/devicetree/bindings/gpio/gpio.txt @@ -213,7 +213,7 @@ Example of two SOC GPIO banks defined as gpio-controller nodes: gpio-controller; #gpio-cells = <2>; - line_b { + line_b-hog { gpio-hog; gpios = <6 0>; output-low; diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst index b24ae7d292cc..8741d390b184 100644 --- a/Documentation/input/event-codes.rst +++ b/Documentation/input/event-codes.rst @@ -137,7 +137,11 @@ A few EV_KEY codes have special meanings: code should be set to a value of 1. When the tool is no longer interacting with the input device, the BTN_TOOL_ code should be reset to 0. All trackpads, tablets, and touchscreens should use at least one BTN_TOOL_ - code when events are generated. + code when events are generated. Likewise all trackpads, tablets, and + touchscreens should export only one BTN_TOOL_ at a time. To not break + existing userspace, it is recommended to not switch tool in one EV_SYN frame + but first emitting the old BTN_TOOL_ at 0, then emit one SYN_REPORT + and then set the new BTN_TOOL_ at 1. * BTN_TOUCH: diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index dfbc27d17ff7..fcab013e47c9 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -375,6 +375,8 @@ Code Seq# Include File Comments 0xF6 all LTTng Linux Trace Toolkit Next Generation +0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver + 0xFD all linux/dm-ioctl.h 0xFE all linux/isst_if.h ==== ===== ======================================================= ================================================================ diff --git a/Documentation/vm/hwpoison.rst b/Documentation/vm/hwpoison.rst index 89b5f7a52077..c742de1769d1 100644 --- a/Documentation/vm/hwpoison.rst +++ b/Documentation/vm/hwpoison.rst @@ -60,8 +60,6 @@ There are two (actually three) modes memory failure recovery can be in: vm.memory_failure_recovery sysctl set to zero: All memory failures cause a panic. Do not attempt recovery. - (on x86 this can be also affected by the tolerant level of the - MCE subsystem) early kill (can be controlled globally and per process) diff --git a/Documentation/x86/amd_hsmp.rst b/Documentation/x86/amd_hsmp.rst new file mode 100644 index 000000000000..440e4b645a1c --- /dev/null +++ b/Documentation/x86/amd_hsmp.rst @@ -0,0 +1,86 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================ +AMD HSMP interface +============================================ + +Newer Fam19h EPYC server line of processors from AMD support system +management functionality via HSMP (Host System Management Port). + +The Host System Management Port (HSMP) is an interface to provide +OS-level software with access to system management functions via a +set of mailbox registers. + +More details on the interface can be found in chapter +"7 Host System Management Port (HSMP)" of the family/model PPR +Eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip + +HSMP interface is supported on EPYC server CPU models only. + + +HSMP device +============================================ + +amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice +/dev/hsmp to let user space programs run hsmp mailbox commands. + +$ ls -al /dev/hsmp +crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp + +Characteristics of the dev node: + * Write mode is used for running set/configure commands + * Read mode is used for running get/status monitor commands + +Access restrictions: + * Only root user is allowed to open the file in write mode. + * The file can be opened in read mode by all the users. + +In-kernel integration: + * Other subsystems in the kernel can use the exported transport + function hsmp_send_message(). + * Locking across callers is taken care by the driver. + + +An example +========== + +To access hsmp device from a C program. +First, you need to include the headers:: + + #include + +Which defines the supported messages/message IDs. + +Next thing, open the device file, as follows:: + + int file; + + file = open("/dev/hsmp", O_RDWR); + if (file < 0) { + /* ERROR HANDLING; you can check errno to see what went wrong */ + exit(1); + } + +The following IOCTL is defined: + +``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)`` + The argument is a pointer to a:: + + struct hsmp_message { + __u32 msg_id; /* Message ID */ + __u16 num_args; /* Number of input argument words in message */ + __u16 response_sz; /* Number of expected output/response words */ + __u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */ + __u16 sock_ind; /* socket number */ + }; + +The ioctl would return a non-zero on failure; you can read errno to see +what happened. The transaction returns 0 on success. + +More details on the interface and message definitions can be found in chapter +"7 Host System Management Port (HSMP)" of the respective family/model PPR +eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip + +User space C-APIs are made available by linking against the esmi library, +which is provided by the E-SMS project https://developer.amd.com/e-sms/. +See: https://github.com/amd/esmi_ib_library diff --git a/Documentation/x86/index.rst b/Documentation/x86/index.rst index 982c8af853b9..91b2fa456618 100644 --- a/Documentation/x86/index.rst +++ b/Documentation/x86/index.rst @@ -25,6 +25,7 @@ x86-specific Documentation intel-iommu intel_txt amd-memory-encryption + amd_hsmp pti mds microcode diff --git a/Documentation/x86/x86_64/boot-options.rst b/Documentation/x86/x86_64/boot-options.rst index ccb7e86bf8d9..07aa0007f346 100644 --- a/Documentation/x86/x86_64/boot-options.rst +++ b/Documentation/x86/x86_64/boot-options.rst @@ -47,14 +47,7 @@ Please see Documentation/x86/x86_64/machinecheck.rst for sysfs runtime tunables. in a reboot. On Intel systems it is enabled by default. mce=nobootlog Disable boot machine check logging. - mce=tolerancelevel[,monarchtimeout] (number,number) - tolerance levels: - 0: always panic on uncorrected errors, log corrected errors - 1: panic or SIGBUS on uncorrected errors, log corrected errors - 2: SIGBUS or log uncorrected errors, log corrected errors - 3: never panic or SIGBUS, log all errors (for testing only) - Default is 1 - Can be also set using sysfs which is preferable. + mce=monarchtimeout (number) monarchtimeout: Sets the time in us to wait for other CPUs on machine checks. 0 to disable. diff --git a/MAINTAINERS b/MAINTAINERS index 5fcde9581539..712aabc4dc10 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -989,6 +989,16 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/amd-pmc.* +AMD HSMP DRIVER +M: Naveen Krishna Chatradhi +R: Carlos Bilbao +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/x86/amd_hsmp.rst +F: arch/x86/include/asm/amd_hsmp.h +F: arch/x86/include/uapi/asm/amd_hsmp.h +F: drivers/platform/x86/amd_hsmp.c + AMD POWERPLAY AND SWSMU M: Evan Quan L: amd-gfx@lists.freedesktop.org @@ -9941,6 +9951,13 @@ S: Maintained F: arch/x86/include/asm/intel_scu_ipc.h F: drivers/platform/x86/intel_scu_* +INTEL SDSI DRIVER +M: David E. Box +S: Supported +F: drivers/platform/x86/intel/sdsi.c +F: tools/arch/x86/intel_sdsi/ +F: tools/testing/selftests/drivers/sdsi/ + INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally S: Maintained @@ -9977,7 +9994,7 @@ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel/uncore-frequency.c +F: drivers/platform/x86/intel/uncore-frequency/ INTEL VENDOR SPECIFIC EXTENDED CAPABILITIES DRIVER M: David E. Box diff --git a/arch/x86/include/asm/amd_hsmp.h b/arch/x86/include/asm/amd_hsmp.h new file mode 100644 index 000000000000..03c2ce3edaf5 --- /dev/null +++ b/arch/x86/include/asm/amd_hsmp.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _ASM_X86_AMD_HSMP_H_ +#define _ASM_X86_AMD_HSMP_H_ + +#include + +#if IS_ENABLED(CONFIG_AMD_HSMP) +int hsmp_send_message(struct hsmp_message *msg); +#else +static inline int hsmp_send_message(struct hsmp_message *msg) +{ + return -ENODEV; +} +#endif +#endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h new file mode 100644 index 000000000000..7ee7ba0d63a3 --- /dev/null +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _UAPI_ASM_X86_AMD_HSMP_H_ +#define _UAPI_ASM_X86_AMD_HSMP_H_ + +#include + +#pragma pack(4) + +#define HSMP_MAX_MSG_LEN 8 + +/* + * HSMP Messages supported + */ +enum hsmp_message_ids { + HSMP_TEST = 1, /* 01h Increments input value by 1 */ + HSMP_GET_SMU_VER, /* 02h SMU FW version */ + HSMP_GET_PROTO_VER, /* 03h HSMP interface version */ + HSMP_GET_SOCKET_POWER, /* 04h average package power consumption */ + HSMP_SET_SOCKET_POWER_LIMIT, /* 05h Set the socket power limit */ + HSMP_GET_SOCKET_POWER_LIMIT, /* 06h Get current socket power limit */ + HSMP_GET_SOCKET_POWER_LIMIT_MAX,/* 07h Get maximum socket power value */ + HSMP_SET_BOOST_LIMIT, /* 08h Set a core maximum frequency limit */ + HSMP_SET_BOOST_LIMIT_SOCKET, /* 09h Set socket maximum frequency level */ + HSMP_GET_BOOST_LIMIT, /* 0Ah Get current frequency limit */ + HSMP_GET_PROC_HOT, /* 0Bh Get PROCHOT status */ + HSMP_SET_XGMI_LINK_WIDTH, /* 0Ch Set max and min width of xGMI Link */ + HSMP_SET_DF_PSTATE, /* 0Dh Alter APEnable/Disable messages behavior */ + HSMP_SET_AUTO_DF_PSTATE, /* 0Eh Enable DF P-State Performance Boost algorithm */ + HSMP_GET_FCLK_MCLK, /* 0Fh Get FCLK and MEMCLK for current socket */ + HSMP_GET_CCLK_THROTTLE_LIMIT, /* 10h Get CCLK frequency limit in socket */ + HSMP_GET_C0_PERCENT, /* 11h Get average C0 residency in socket */ + HSMP_SET_NBIO_DPM_LEVEL, /* 12h Set max/min LCLK DPM Level for a given NBIO */ + /* 13h Reserved */ + HSMP_GET_DDR_BANDWIDTH = 0x14, /* 14h Get theoretical maximum and current DDR Bandwidth */ + HSMP_GET_TEMP_MONITOR, /* 15h Get per-DIMM temperature and refresh rates */ + HSMP_MSG_ID_MAX, +}; + +struct hsmp_message { + __u32 msg_id; /* Message ID */ + __u16 num_args; /* Number of input argument words in message */ + __u16 response_sz; /* Number of expected output/response words */ + __u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */ + __u16 sock_ind; /* socket number */ +}; + +enum hsmp_msg_type { + HSMP_RSVD = -1, + HSMP_SET = 0, + HSMP_GET = 1, +}; + +struct hsmp_msg_desc { + int num_args; + int response_sz; + enum hsmp_msg_type type; +}; + +/* + * User may use these comments as reference, please find the + * supported list of messages and message definition in the + * HSMP chapter of respective family/model PPR. + * + * Not supported messages would return -ENOMSG. + */ +static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { + /* RESERVED */ + {0, 0, HSMP_RSVD}, + + /* + * HSMP_TEST, num_args = 1, response_sz = 1 + * input: args[0] = xx + * output: args[0] = xx + 1 + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_GET_SMU_VER, num_args = 0, response_sz = 1 + * output: args[0] = smu fw ver + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_PROTO_VER, num_args = 0, response_sz = 1 + * output: args[0] = proto version + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_SOCKET_POWER, num_args = 0, response_sz = 1 + * output: args[0] = socket power in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_SOCKET_POWER_LIMIT, num_args = 1, response_sz = 0 + * input: args[0] = power limit value in mWatts + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_GET_SOCKET_POWER_LIMIT, num_args = 0, response_sz = 1 + * output: args[0] = socket power limit value in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_SOCKET_POWER_LIMIT_MAX, num_args = 0, response_sz = 1 + * output: args[0] = maximuam socket power limit in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_BOOST_LIMIT, num_args = 1, response_sz = 0 + * input: args[0] = apic id[31:16] + boost limit value in MHz[15:0] + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_SET_BOOST_LIMIT_SOCKET, num_args = 1, response_sz = 0 + * input: args[0] = boost limit value in MHz + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_GET_BOOST_LIMIT, num_args = 1, response_sz = 1 + * input: args[0] = apic id + * output: args[0] = boost limit value in MHz + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_GET_PROC_HOT, num_args = 0, response_sz = 1 + * output: args[0] = proc hot status + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 + * input: args[0] = min link width[15:8] + max link width[7:0] + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 + * input: args[0] = df pstate[7:0] + */ + {1, 0, HSMP_SET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, + + /* + * HSMP_GET_FCLK_MCLK, num_args = 0, response_sz = 2 + * output: args[0] = fclk in MHz, args[1] = mclk in MHz + */ + {0, 2, HSMP_GET}, + + /* + * HSMP_GET_CCLK_THROTTLE_LIMIT, num_args = 0, response_sz = 1 + * output: args[0] = core clock in MHz + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_C0_PERCENT, num_args = 0, response_sz = 1 + * output: args[0] = average c0 residency + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_NBIO_DPM_LEVEL, num_args = 1, response_sz = 0 + * input: args[0] = nbioid[23:16] + max dpm level[15:8] + min dpm level[7:0] + */ + {1, 0, HSMP_SET}, + + /* RESERVED message */ + {0, 0, HSMP_RSVD}, + + /* + * HSMP_GET_DDR_BANDWIDTH, num_args = 0, response_sz = 1 + * output: args[0] = max bw in Gbps[31:20] + utilised bw in Gbps[19:8] + + * bw in percentage[7:0] + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_TEMP_MONITOR, num_args = 0, response_sz = 1 + * output: args[0] = temperature in degree celsius. [15:8] integer part + + * [7:5] fractional part + */ + {0, 1, HSMP_GET}, +}; + +/* Reset to default packing */ +#pragma pack() + +/* Define unique ioctl command for hsmp msgs using generic _IOWR */ +#define HSMP_BASE_IOCTL_NR 0xF8 +#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + +#endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index e53d3e6096fb..981496e6bc0e 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -86,14 +86,6 @@ struct mce_vendor_flags mce_flags __read_mostly; struct mca_config mca_cfg __read_mostly = { .bootlog = -1, - /* - * Tolerant levels: - * 0: always panic on uncorrected errors, log corrected errors - * 1: panic or SIGBUS on uncorrected errors, log corrected errors - * 2: SIGBUS or log uncorrected errors (if possible), log corr. errors - * 3: never panic or SIGBUS, log all errors (for testing only) - */ - .tolerant = 1, .monarch_timeout = -1 }; @@ -168,27 +160,6 @@ void mce_unregister_decode_chain(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(mce_unregister_decode_chain); -u32 mca_msr_reg(int bank, enum mca_msr reg) -{ - if (mce_flags.smca) { - switch (reg) { - case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank); - case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank); - case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank); - case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank); - } - } - - switch (reg) { - case MCA_CTL: return MSR_IA32_MCx_CTL(bank); - case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank); - case MCA_MISC: return MSR_IA32_MCx_MISC(bank); - case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank); - } - - return 0; -} - static void __print_mce(struct mce *m) { pr_emerg(HW_ERR "CPU %d: Machine Check%s: %Lx Bank %d: %016Lx\n", @@ -769,7 +740,7 @@ bool machine_check_poll(enum mcp_flags flags, mce_banks_t *b) goto clear_it; mce_read_aux(&m, i); - m.severity = mce_severity(&m, NULL, mca_cfg.tolerant, NULL, false); + m.severity = mce_severity(&m, NULL, NULL, false); /* * Don't get the IP here because it's unlikely to * have anything to do with the actual error location. @@ -809,7 +780,8 @@ EXPORT_SYMBOL_GPL(machine_check_poll); * the severity assessment code. Pretend that EIPV was set, and take the * ip/cs values from the pt_regs that mce_gather_info() ignored earlier. */ -static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) +static __always_inline void +quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) { if (bank != 0) return; @@ -829,12 +801,65 @@ static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) m->cs = regs->cs; } +/* + * Disable fast string copy and return from the MCE handler upon the first SRAR + * MCE on bank 1 due to a CPU erratum on Intel Skylake/Cascade Lake/Cooper Lake + * CPUs. + * The fast string copy instructions ("REP; MOVS*") could consume an + * uncorrectable memory error in the cache line _right after_ the desired region + * to copy and raise an MCE with RIP pointing to the instruction _after_ the + * "REP; MOVS*". + * This mitigation addresses the issue completely with the caveat of performance + * degradation on the CPU affected. This is still better than the OS crashing on + * MCEs raised on an irrelevant process due to "REP; MOVS*" accesses from a + * kernel context (e.g., copy_page). + * + * Returns true when fast string copy on CPU has been disabled. + */ +static noinstr bool quirk_skylake_repmov(void) +{ + u64 mcgstatus = mce_rdmsrl(MSR_IA32_MCG_STATUS); + u64 misc_enable = mce_rdmsrl(MSR_IA32_MISC_ENABLE); + u64 mc1_status; + + /* + * Apply the quirk only to local machine checks, i.e., no broadcast + * sync is needed. + */ + if (!(mcgstatus & MCG_STATUS_LMCES) || + !(misc_enable & MSR_IA32_MISC_ENABLE_FAST_STRING)) + return false; + + mc1_status = mce_rdmsrl(MSR_IA32_MCx_STATUS(1)); + + /* Check for a software-recoverable data fetch error. */ + if ((mc1_status & + (MCI_STATUS_VAL | MCI_STATUS_OVER | MCI_STATUS_UC | MCI_STATUS_EN | + MCI_STATUS_ADDRV | MCI_STATUS_MISCV | MCI_STATUS_PCC | + MCI_STATUS_AR | MCI_STATUS_S)) == + (MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN | + MCI_STATUS_ADDRV | MCI_STATUS_MISCV | + MCI_STATUS_AR | MCI_STATUS_S)) { + misc_enable &= ~MSR_IA32_MISC_ENABLE_FAST_STRING; + mce_wrmsrl(MSR_IA32_MISC_ENABLE, misc_enable); + mce_wrmsrl(MSR_IA32_MCx_STATUS(1), 0); + + instrumentation_begin(); + pr_err_once("Erratum detected, disable fast string copy instructions.\n"); + instrumentation_end(); + + return true; + } + + return false; +} + /* * Do a quick check if any of the events requires a panic. * This decides if we keep the events around or clear them. */ -static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, - struct pt_regs *regs) +static __always_inline int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, + struct pt_regs *regs) { char *tmp = *msg; int i; @@ -844,12 +869,12 @@ static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, if (!(m->status & MCI_STATUS_VAL)) continue; - __set_bit(i, validp); + arch___set_bit(i, validp); if (mce_flags.snb_ifu_quirk) quirk_sandybridge_ifu(i, m, regs); m->bank = i; - if (mce_severity(m, regs, mca_cfg.tolerant, &tmp, true) >= MCE_PANIC_SEVERITY) { + if (mce_severity(m, regs, &tmp, true) >= MCE_PANIC_SEVERITY) { mce_read_aux(m, i); *msg = tmp; return 1; @@ -897,12 +922,11 @@ static noinstr int mce_timed_out(u64 *t, const char *msg) if (!mca_cfg.monarch_timeout) goto out; if ((s64)*t < SPINUNIT) { - if (mca_cfg.tolerant <= 1) { - if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus)) - pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n", - cpumask_pr_args(&mce_missing_cpus)); - mce_panic(msg, NULL, NULL); - } + if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus)) + pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n", + cpumask_pr_args(&mce_missing_cpus)); + mce_panic(msg, NULL, NULL); + ret = 1; goto out; } @@ -966,9 +990,9 @@ static void mce_reign(void) * This dumps all the mces in the log buffer and stops the * other CPUs. */ - if (m && global_worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) { + if (m && global_worst >= MCE_PANIC_SEVERITY) { /* call mce_severity() to get "msg" for panic */ - mce_severity(m, NULL, mca_cfg.tolerant, &msg, true); + mce_severity(m, NULL, &msg, true); mce_panic("Fatal machine check", m, msg); } @@ -982,7 +1006,7 @@ static void mce_reign(void) * No machine check event found. Must be some external * source or one CPU is hung. Panic. */ - if (global_worst <= MCE_KEEP_SEVERITY && mca_cfg.tolerant < 3) + if (global_worst <= MCE_KEEP_SEVERITY) mce_panic("Fatal machine check from unknown source", NULL, NULL); /* @@ -1010,13 +1034,13 @@ static noinstr int mce_start(int *no_way_out) if (!timeout) return ret; - atomic_add(*no_way_out, &global_nwo); + arch_atomic_add(*no_way_out, &global_nwo); /* * Rely on the implied barrier below, such that global_nwo * is updated before mce_callin. */ - order = atomic_inc_return(&mce_callin); - cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus); + order = arch_atomic_inc_return(&mce_callin); + arch_cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus); /* Enable instrumentation around calls to external facilities */ instrumentation_begin(); @@ -1024,10 +1048,10 @@ static noinstr int mce_start(int *no_way_out) /* * Wait for everyone. */ - while (atomic_read(&mce_callin) != num_online_cpus()) { + while (arch_atomic_read(&mce_callin) != num_online_cpus()) { if (mce_timed_out(&timeout, "Timeout: Not all CPUs entered broadcast exception handler")) { - atomic_set(&global_nwo, 0); + arch_atomic_set(&global_nwo, 0); goto out; } ndelay(SPINUNIT); @@ -1042,7 +1066,7 @@ static noinstr int mce_start(int *no_way_out) /* * Monarch: Starts executing now, the others wait. */ - atomic_set(&mce_executing, 1); + arch_atomic_set(&mce_executing, 1); } else { /* * Subject: Now start the scanning loop one by one in @@ -1050,10 +1074,10 @@ static noinstr int mce_start(int *no_way_out) * This way when there are any shared banks it will be * only seen by one CPU before cleared, avoiding duplicates. */ - while (atomic_read(&mce_executing) < order) { + while (arch_atomic_read(&mce_executing) < order) { if (mce_timed_out(&timeout, "Timeout: Subject CPUs unable to finish machine check processing")) { - atomic_set(&global_nwo, 0); + arch_atomic_set(&global_nwo, 0); goto out; } ndelay(SPINUNIT); @@ -1063,7 +1087,7 @@ static noinstr int mce_start(int *no_way_out) /* * Cache the global no_way_out state. */ - *no_way_out = atomic_read(&global_nwo); + *no_way_out = arch_atomic_read(&global_nwo); ret = order; @@ -1148,12 +1172,12 @@ static noinstr int mce_end(int order) return ret; } -static void mce_clear_state(unsigned long *toclear) +static __always_inline void mce_clear_state(unsigned long *toclear) { int i; for (i = 0; i < this_cpu_read(mce_num_banks); i++) { - if (test_bit(i, toclear)) + if (arch_test_bit(i, toclear)) mce_wrmsrl(mca_msr_reg(i, MCA_STATUS), 0); } } @@ -1203,8 +1227,8 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, int severity, i, taint = 0; for (i = 0; i < this_cpu_read(mce_num_banks); i++) { - __clear_bit(i, toclear); - if (!test_bit(i, valid_banks)) + arch___clear_bit(i, toclear); + if (!arch_test_bit(i, valid_banks)) continue; if (!mce_banks[i].ctl) @@ -1229,7 +1253,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, /* Set taint even when machine check was not enabled. */ taint++; - severity = mce_severity(m, regs, cfg->tolerant, NULL, true); + severity = mce_severity(m, regs, NULL, true); /* * When machine check was for corrected/deferred handler don't @@ -1239,7 +1263,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, severity == MCE_UCNA_SEVERITY) && !no_way_out) continue; - __set_bit(i, toclear); + arch___set_bit(i, toclear); /* Machine check event was not enabled. Clear, but ignore. */ if (severity == MCE_NO_SEVERITY) @@ -1389,7 +1413,6 @@ noinstr void do_machine_check(struct pt_regs *regs) int worst = 0, order, no_way_out, kill_current_task, lmce, taint = 0; DECLARE_BITMAP(valid_banks, MAX_NR_BANKS) = { 0 }; DECLARE_BITMAP(toclear, MAX_NR_BANKS) = { 0 }; - struct mca_config *cfg = &mca_cfg; struct mce m, *final; char *msg = NULL; @@ -1400,6 +1423,9 @@ noinstr void do_machine_check(struct pt_regs *regs) else if (unlikely(!mca_cfg.initialized)) return unexpected_machine_check(regs); + if (mce_flags.skx_repmov_quirk && quirk_skylake_repmov()) + goto clear; + /* * Establish sequential order between the CPUs entering the machine * check handler. @@ -1408,7 +1434,7 @@ noinstr void do_machine_check(struct pt_regs *regs) /* * If no_way_out gets set, there is no safe way to recover from this - * MCE. If mca_cfg.tolerant is cranked up, we'll try anyway. + * MCE. */ no_way_out = 0; @@ -1442,7 +1468,7 @@ noinstr void do_machine_check(struct pt_regs *regs) * severity is MCE_AR_SEVERITY we have other options. */ if (!(m.mcgstatus & MCG_STATUS_RIPV)) - kill_current_task = (cfg->tolerant == 3) ? 0 : 1; + kill_current_task = 1; /* * Check if this MCE is signaled to only this logical processor, * on Intel, Zhaoxin only. @@ -1459,7 +1485,7 @@ noinstr void do_machine_check(struct pt_regs *regs) * to see it will clear it. */ if (lmce) { - if (no_way_out && cfg->tolerant < 3) + if (no_way_out) mce_panic("Fatal local machine check", &m, msg); } else { order = mce_start(&no_way_out); @@ -1479,7 +1505,7 @@ noinstr void do_machine_check(struct pt_regs *regs) if (!no_way_out) no_way_out = worst >= MCE_PANIC_SEVERITY; - if (no_way_out && cfg->tolerant < 3) + if (no_way_out) mce_panic("Fatal machine check on current CPU", &m, msg); } } else { @@ -1491,8 +1517,8 @@ noinstr void do_machine_check(struct pt_regs *regs) * fatal error. We call "mce_severity()" again to * make sure we have the right "msg". */ - if (worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) { - mce_severity(&m, regs, cfg->tolerant, &msg, true); + if (worst >= MCE_PANIC_SEVERITY) { + mce_severity(&m, regs, &msg, true); mce_panic("Local fatal machine check!", &m, msg); } } @@ -1542,6 +1568,7 @@ noinstr void do_machine_check(struct pt_regs *regs) out: instrumentation_end(); +clear: mce_wrmsrl(MSR_IA32_MCG_STATUS, 0); } EXPORT_SYMBOL_GPL(do_machine_check); @@ -1855,6 +1882,13 @@ static int __mcheck_cpu_apply_quirks(struct cpuinfo_x86 *c) if (c->x86 == 6 && c->x86_model == 45) mce_flags.snb_ifu_quirk = 1; + + /* + * Skylake, Cascacde Lake and Cooper Lake require a quirk on + * rep movs. + */ + if (c->x86 == 6 && c->x86_model == INTEL_FAM6_SKYLAKE_X) + mce_flags.skx_repmov_quirk = 1; } if (c->x86_vendor == X86_VENDOR_ZHAOXIN) { @@ -2220,10 +2254,9 @@ static int __init mcheck_enable(char *str) cfg->bios_cmci_threshold = 1; else if (!strcmp(str, "recovery")) cfg->recovery = 1; - else if (isdigit(str[0])) { - if (get_option(&str, &cfg->tolerant) == 2) - get_option(&str, &(cfg->monarch_timeout)); - } else { + else if (isdigit(str[0])) + get_option(&str, &(cfg->monarch_timeout)); + else { pr_info("mce argument %s ignored. Please use /sys\n", str); return 0; } @@ -2473,7 +2506,6 @@ static ssize_t store_int_with_restart(struct device *s, return ret; } -static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant); static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce); static DEVICE_BOOL_ATTR(print_all, 0644, mca_cfg.print_all); @@ -2494,7 +2526,6 @@ static struct dev_ext_attribute dev_attr_cmci_disabled = { }; static struct device_attribute *mce_device_attrs[] = { - &dev_attr_tolerant.attr, &dev_attr_check_interval.attr, #ifdef CONFIG_X86_MCELOG_LEGACY &dev_attr_trigger, diff --git a/arch/x86/kernel/cpu/mce/internal.h b/arch/x86/kernel/cpu/mce/internal.h index 52c633950b38..4ae0e603f7fa 100644 --- a/arch/x86/kernel/cpu/mce/internal.h +++ b/arch/x86/kernel/cpu/mce/internal.h @@ -35,7 +35,7 @@ int mce_gen_pool_add(struct mce *mce); int mce_gen_pool_init(void); struct llist_node *mce_gen_pool_prepare_records(void); -int mce_severity(struct mce *a, struct pt_regs *regs, int tolerant, char **msg, bool is_excp); +int mce_severity(struct mce *a, struct pt_regs *regs, char **msg, bool is_excp); struct dentry *mce_get_debugfs_dir(void); extern mce_banks_t mce_banks_ce_disabled; @@ -127,7 +127,6 @@ struct mca_config { bool ignore_ce; bool print_all; - int tolerant; int monarch_timeout; int panic_timeout; u32 rip_msr; @@ -170,7 +169,10 @@ struct mce_vendor_flags { /* SandyBridge IFU quirk */ snb_ifu_quirk : 1, - __reserved_0 : 57; + /* Skylake, Cascade Lake, Cooper Lake REP;MOVS* quirk */ + skx_repmov_quirk : 1, + + __reserved_0 : 56; }; extern struct mce_vendor_flags mce_flags; @@ -182,8 +184,6 @@ enum mca_msr { MCA_MISC, }; -u32 mca_msr_reg(int bank, enum mca_msr reg); - /* Decide whether to add MCE record to MCE event pool or filter it out. */ extern bool filter_mce(struct mce *m); @@ -209,4 +209,25 @@ static inline void winchip_machine_check(struct pt_regs *regs) {} noinstr u64 mce_rdmsrl(u32 msr); +static __always_inline u32 mca_msr_reg(int bank, enum mca_msr reg) +{ + if (mce_flags.smca) { + switch (reg) { + case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank); + case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank); + case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank); + case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank); + } + } + + switch (reg) { + case MCA_CTL: return MSR_IA32_MCx_CTL(bank); + case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank); + case MCA_MISC: return MSR_IA32_MCx_MISC(bank); + case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank); + } + + return 0; +} + #endif /* __X86_MCE_INTERNAL_H__ */ diff --git a/arch/x86/kernel/cpu/mce/severity.c b/arch/x86/kernel/cpu/mce/severity.c index 7aa2bda93cbb..1add86935349 100644 --- a/arch/x86/kernel/cpu/mce/severity.c +++ b/arch/x86/kernel/cpu/mce/severity.c @@ -301,7 +301,7 @@ static noinstr int error_context(struct mce *m, struct pt_regs *regs) } } -static int mce_severity_amd_smca(struct mce *m, enum context err_ctx) +static __always_inline int mce_severity_amd_smca(struct mce *m, enum context err_ctx) { u64 mcx_cfg; @@ -330,8 +330,7 @@ static int mce_severity_amd_smca(struct mce *m, enum context err_ctx) * See AMD Error Scope Hierarchy table in a newer BKDG. For example * 49125_15h_Models_30h-3Fh_BKDG.pdf, section "RAS Features" */ -static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tolerant, - char **msg, bool is_excp) +static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { enum context ctx = error_context(m, regs); @@ -383,8 +382,7 @@ static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tol return MCE_KEEP_SEVERITY; } -static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, - int tolerant, char **msg, bool is_excp) +static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { enum exception excp = (is_excp ? EXCP_CONTEXT : NO_EXCP); enum context ctx = error_context(m, regs); @@ -412,22 +410,21 @@ static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, if (msg) *msg = s->msg; s->covered = 1; - if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL) { - if (tolerant < 1) - return MCE_PANIC_SEVERITY; - } + + if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL) + return MCE_PANIC_SEVERITY; + return s->sev; } } -int noinstr mce_severity(struct mce *m, struct pt_regs *regs, int tolerant, char **msg, - bool is_excp) +int noinstr mce_severity(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) - return mce_severity_amd(m, regs, tolerant, msg, is_excp); + return mce_severity_amd(m, regs, msg, is_excp); else - return mce_severity_intel(m, regs, tolerant, msg, is_excp); + return mce_severity_intel(m, regs, msg, is_excp); } #ifdef CONFIG_DEBUG_FS diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index c116e3a65ab4..9efbfe087de7 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1750,6 +1750,11 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) {"INT3515", }, /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, + /* + * Some ACPI devs contain SerialBus resources even though they are not + * attached to a serial bus at all. + */ + {"MSHW0028", }, /* * HIDs of device with an UartSerialBusV2 resource for which userspace * expects a regular tty cdev to be created (instead of the in kernel diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index ed889f827f53..2963229062f8 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -86,6 +86,8 @@ struct lpi_device_constraint_amd { int min_dstate; }; +static LIST_HEAD(lps0_s2idle_devops_head); + static struct lpi_constraints *lpi_constraints_table; static int lpi_constraints_table_size; static int rev_id; @@ -440,6 +442,8 @@ static struct acpi_scan_handler lps0_handler = { int acpi_s2idle_prepare_late(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return 0; @@ -470,14 +474,26 @@ int acpi_s2idle_prepare_late(void) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); } + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->prepare) + handler->prepare(); + } + return 0; } void acpi_s2idle_restore_early(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return; + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) + if (handler->restore) + handler->restore(); + /* Modern standby exit */ if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, @@ -520,4 +536,28 @@ void acpi_s2idle_setup(void) s2idle_set_ops(&acpi_s2idle_ops_lps0); } +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + if (!lps0_device_handle || sleep_no_lps0) + return -ENODEV; + + lock_system_sleep(); + list_add(&arg->list_node, &lps0_s2idle_devops_head); + unlock_system_sleep(); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); + +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + if (!lps0_device_handle || sleep_no_lps0) + return; + + lock_system_sleep(); + list_del(&arg->list_node); + unlock_system_sleep(); +} +EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); + #endif /* CONFIG_SUSPEND */ diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1c211b4c63be..b2d313fe3fc5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -247,6 +247,16 @@ config GPIO_EM help Say yes here to support GPIO on Renesas Emma Mobile SoCs. +config GPIO_EN7523 + tristate "Airoha GPIO support" + depends on ARCH_AIROHA + default ARCH_AIROHA + select GPIO_GENERIC + select GPIOLIB_IRQCHIP + help + Say Y or M here to support the GPIO controller block on the + Airoha EN7523 SoC. It supports two banks of 32 GPIOs. + config GPIO_EP93XX def_bool y depends on ARCH_EP93XX @@ -1380,7 +1390,7 @@ config GPIO_TPS65912 This driver supports TPS65912 GPIO chip. config GPIO_TPS68470 - bool "TPS68470 GPIO" + tristate "TPS68470 GPIO" depends on INTEL_SKL_INT3472 help Select this option to enable GPIO driver for the TPS68470 @@ -1390,10 +1400,6 @@ config GPIO_TPS68470 input or output as appropriate, the sensor related GPIOs are "output only" GPIOs. - This driver config is bool, as the GPIO functionality - of the TPS68470 must be available before dependent - drivers are loaded. - config GPIO_TQMX86 tristate "TQ-Systems QTMX86 GPIO" depends on MFD_TQMX86 || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index edbaa3cb343c..1a14e248bdbd 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o obj-$(CONFIG_GPIO_EIC_SPRD) += gpio-eic-sprd.o obj-$(CONFIG_GPIO_EM) += gpio-em.o +obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o diff --git a/drivers/gpio/gpio-altera-a10sr.c b/drivers/gpio/gpio-altera-a10sr.c index 6af51feda06f..be1ed7ee5225 100644 --- a/drivers/gpio/gpio-altera-a10sr.c +++ b/drivers/gpio/gpio-altera-a10sr.c @@ -10,6 +10,7 @@ #include #include #include +#include /** * struct altr_a10sr_gpio - Altera Max5 GPIO device private data structure @@ -88,7 +89,7 @@ static int altr_a10sr_gpio_probe(struct platform_device *pdev) gpio->gp = altr_a10sr_gc; gpio->gp.parent = pdev->dev.parent; - gpio->gp.of_node = pdev->dev.of_node; + gpio->gp.fwnode = dev_fwnode(&pdev->dev); return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio); } diff --git a/drivers/gpio/gpio-crystalcove.c b/drivers/gpio/gpio-crystalcove.c index 5a909f3c79e8..b55c74a5e064 100644 --- a/drivers/gpio/gpio-crystalcove.c +++ b/drivers/gpio/gpio-crystalcove.c @@ -370,7 +370,14 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) return retval; } - return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); + retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); + if (retval) + return retval; + + /* Distuingish IRQ domain from others sharing (MFD) the same fwnode */ + irq_domain_update_bus_token(cg->chip.irq.domain, DOMAIN_BUS_WIRED); + + return 0; } static struct platform_driver crystalcove_gpio_driver = { diff --git a/drivers/gpio/gpio-en7523.c b/drivers/gpio/gpio-en7523.c new file mode 100644 index 000000000000..f836a8db4c1d --- /dev/null +++ b/drivers/gpio/gpio-en7523.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +#define AIROHA_GPIO_MAX 32 + +/** + * airoha_gpio_ctrl - Airoha GPIO driver data + * @gc: Associated gpio_chip instance. + * @data: The data register. + * @dir0: The direction register for the lower 16 pins. + * @dir1: The direction register for the higher 16 pins. + * @output: The output enable register. + */ +struct airoha_gpio_ctrl { + struct gpio_chip gc; + void __iomem *data; + void __iomem *dir[2]; + void __iomem *output; +}; + +static struct airoha_gpio_ctrl *gc_to_ctrl(struct gpio_chip *gc) +{ + return container_of(gc, struct airoha_gpio_ctrl, gc); +} + +static int airoha_dir_set(struct gpio_chip *gc, unsigned int gpio, + int val, int out) +{ + struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc); + u32 dir = ioread32(ctrl->dir[gpio / 16]); + u32 output = ioread32(ctrl->output); + u32 mask = BIT((gpio % 16) * 2); + + if (out) { + dir |= mask; + output |= BIT(gpio); + } else { + dir &= ~mask; + output &= ~BIT(gpio); + } + + iowrite32(dir, ctrl->dir[gpio / 16]); + + if (out) + gc->set(gc, gpio, val); + + iowrite32(output, ctrl->output); + + return 0; +} + +static int airoha_dir_out(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + return airoha_dir_set(gc, gpio, val, 1); +} + +static int airoha_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + return airoha_dir_set(gc, gpio, 0, 0); +} + +static int airoha_get_dir(struct gpio_chip *gc, unsigned int gpio) +{ + struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc); + u32 dir = ioread32(ctrl->dir[gpio / 16]); + u32 mask = BIT((gpio % 16) * 2); + + return (dir & mask) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int airoha_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct airoha_gpio_ctrl *ctrl; + int err; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->data = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->data)) + return PTR_ERR(ctrl->data); + + ctrl->dir[0] = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(ctrl->dir[0])) + return PTR_ERR(ctrl->dir[0]); + + ctrl->dir[1] = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(ctrl->dir[1])) + return PTR_ERR(ctrl->dir[1]); + + ctrl->output = devm_platform_ioremap_resource(pdev, 3); + if (IS_ERR(ctrl->output)) + return PTR_ERR(ctrl->output); + + err = bgpio_init(&ctrl->gc, dev, 4, ctrl->data, NULL, + NULL, NULL, NULL, 0); + if (err) + return dev_err_probe(dev, err, "unable to init generic GPIO"); + + ctrl->gc.ngpio = AIROHA_GPIO_MAX; + ctrl->gc.owner = THIS_MODULE; + ctrl->gc.direction_output = airoha_dir_out; + ctrl->gc.direction_input = airoha_dir_in; + ctrl->gc.get_direction = airoha_get_dir; + + return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl); +} + +static const struct of_device_id airoha_gpio_of_match[] = { + { .compatible = "airoha,en7523-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, airoha_gpio_of_match); + +static struct platform_driver airoha_gpio_driver = { + .driver = { + .name = "airoha-gpio", + .of_match_table = airoha_gpio_of_match, + }, + .probe = airoha_gpio_probe, +}; +module_platform_driver(airoha_gpio_driver); + +MODULE_DESCRIPTION("Airoha GPIO support"); +MODULE_AUTHOR("John Crispin "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-merrifield.c b/drivers/gpio/gpio-merrifield.c index 42c4d9d0cd50..f3d1baeacbe9 100644 --- a/drivers/gpio/gpio-merrifield.c +++ b/drivers/gpio/gpio-merrifield.c @@ -409,6 +409,9 @@ static int mrfld_gpio_add_pin_ranges(struct gpio_chip *chip) int retval; pinctrl_dev_name = mrfld_gpio_get_pinctrl_dev_name(priv); + if (!pinctrl_dev_name) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(mrfld_gpio_ranges); i++) { range = &mrfld_gpio_ranges[i]; retval = gpiochip_add_pin_range(&priv->chip, pinctrl_dev_name, diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c index 7f5bc10a6479..ff2d2a1f9c73 100644 --- a/drivers/gpio/gpio-tegra.c +++ b/drivers/gpio/gpio-tegra.c @@ -691,7 +691,6 @@ static int tegra_gpio_probe(struct platform_device *pdev) tgi->gc.base = 0; tgi->gc.ngpio = tgi->bank_count * 32; tgi->gc.parent = &pdev->dev; - tgi->gc.of_node = pdev->dev.of_node; tgi->ic.name = "GPIO"; tgi->ic.irq_ack = tegra_gpio_irq_ack; diff --git a/drivers/gpio/gpio-tps68470.c b/drivers/gpio/gpio-tps68470.c index 423b7bc30ae8..aaddcabe9b35 100644 --- a/drivers/gpio/gpio-tps68470.c +++ b/drivers/gpio/gpio-tps68470.c @@ -154,5 +154,8 @@ static struct platform_driver tps68470_gpio_driver = { }, .probe = tps68470_gpio_probe, }; +module_platform_driver(tps68470_gpio_driver); -builtin_platform_driver(tps68470_gpio_driver) +MODULE_ALIAS("platform:tps68470-gpio"); +MODULE_DESCRIPTION("GPIO driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-ts4900.c b/drivers/gpio/gpio-ts4900.c index d918d2df4de2..69854fd2382a 100644 --- a/drivers/gpio/gpio-ts4900.c +++ b/drivers/gpio/gpio-ts4900.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Digital I/O driver for Technologic Systems I2C FPGA Core * * Copyright (C) 2015, 2018 Technologic Systems * Copyright (C) 2016 Savoir-Faire Linux - * - * 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 - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether expressed or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License version 2 for more details. */ #include diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 91dcf2c6cdd8..ae1ce319cd78 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -711,14 +711,12 @@ static int of_gpiochip_scan_gpios(struct gpio_chip *chip) static void of_gpiochip_remove_hog(struct gpio_chip *chip, struct device_node *hog) { - struct gpio_desc *descs = chip->gpiodev->descs; + struct gpio_desc *desc; unsigned int i; - for (i = 0; i < chip->ngpio; i++) { - if (test_bit(FLAG_IS_HOGGED, &descs[i].flags) && - descs[i].hog == hog) - gpiochip_free_own_desc(&descs[i]); - } + for_each_gpio_desc_with_flag(i, chip, desc, FLAG_IS_HOGGED) + if (desc->hog == hog) + gpiochip_free_own_desc(desc); } static int of_gpiochip_match_node(struct gpio_chip *chip, void *data) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 44c1ad51b3fe..d44ffea038f5 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -13,6 +13,7 @@ #include "gpiolib.h" #include "gpiolib-sysfs.h" +#define GPIO_IRQF_TRIGGER_NONE 0 #define GPIO_IRQF_TRIGGER_FALLING BIT(0) #define GPIO_IRQF_TRIGGER_RISING BIT(1) #define GPIO_IRQF_TRIGGER_BOTH (GPIO_IRQF_TRIGGER_FALLING | \ @@ -61,17 +62,16 @@ static ssize_t direction_show(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status; + int value; mutex_lock(&data->mutex); gpiod_get_direction(desc); - status = sysfs_emit(buf, "%s\n", - test_bit(FLAG_IS_OUT, &desc->flags) ? "out" : "in"); + value = !!test_bit(FLAG_IS_OUT, &desc->flags); mutex_unlock(&data->mutex); - return status; + return sysfs_emit(buf, "%s\n", value ? "out" : "in"); } static ssize_t direction_store(struct device *dev, @@ -108,12 +108,13 @@ static ssize_t value_show(struct device *dev, mutex_lock(&data->mutex); status = gpiod_get_value_cansleep(desc); - if (status >= 0) - status = sysfs_emit(buf, "%zd\n", status); mutex_unlock(&data->mutex); - return status; + if (status < 0) + return status; + + return sysfs_emit(buf, "%zd\n", status); } static ssize_t value_store(struct device *dev, @@ -121,24 +122,18 @@ static ssize_t value_store(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status = 0; + ssize_t status; + long value; + + status = kstrtol(buf, 0, &value); mutex_lock(&data->mutex); if (!test_bit(FLAG_IS_OUT, &desc->flags)) { status = -EPERM; - } else { - long value; - - if (size <= 2 && isdigit(buf[0]) && - (size == 1 || buf[1] == '\n')) - value = buf[0] - '0'; - else - status = kstrtol(buf, 0, &value); - if (status == 0) { - gpiod_set_value_cansleep(desc, value); - status = size; - } + } else if (status == 0) { + gpiod_set_value_cansleep(desc, value); + status = size; } mutex_unlock(&data->mutex); @@ -224,54 +219,41 @@ static void gpio_sysfs_free_irq(struct device *dev) sysfs_put(data->value_kn); } -static const struct { - const char *name; - unsigned char flags; -} trigger_types[] = { - { "none", 0 }, - { "falling", GPIO_IRQF_TRIGGER_FALLING }, - { "rising", GPIO_IRQF_TRIGGER_RISING }, - { "both", GPIO_IRQF_TRIGGER_BOTH }, +static const char * const trigger_names[] = { + [GPIO_IRQF_TRIGGER_NONE] = "none", + [GPIO_IRQF_TRIGGER_FALLING] = "falling", + [GPIO_IRQF_TRIGGER_RISING] = "rising", + [GPIO_IRQF_TRIGGER_BOTH] = "both", }; static ssize_t edge_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpiod_data *data = dev_get_drvdata(dev); - ssize_t status = 0; - int i; + int flags; mutex_lock(&data->mutex); - for (i = 0; i < ARRAY_SIZE(trigger_types); i++) { - if (data->irq_flags == trigger_types[i].flags) - break; - } - if (i < ARRAY_SIZE(trigger_types)) - status = sysfs_emit(buf, "%s\n", trigger_types[i].name); + flags = data->irq_flags; mutex_unlock(&data->mutex); - return status; + if (flags >= ARRAY_SIZE(trigger_names)) + return 0; + + return sysfs_emit(buf, "%s\n", trigger_names[flags]); } static ssize_t edge_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct gpiod_data *data = dev_get_drvdata(dev); - unsigned char flags; ssize_t status = size; - int i; + int flags; - for (i = 0; i < ARRAY_SIZE(trigger_types); i++) { - if (sysfs_streq(trigger_types[i].name, buf)) - break; - } - - if (i == ARRAY_SIZE(trigger_types)) - return -EINVAL; - - flags = trigger_types[i].flags; + flags = sysfs_match_string(trigger_names, buf); + if (flags < 0) + return flags; mutex_lock(&data->mutex); @@ -324,16 +306,15 @@ static ssize_t active_low_show(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status; + int value; mutex_lock(&data->mutex); - status = sysfs_emit(buf, "%d\n", - !!test_bit(FLAG_ACTIVE_LOW, &desc->flags)); + value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags); mutex_unlock(&data->mutex); - return status; + return sysfs_emit(buf, "%d\n", value); } static ssize_t active_low_store(struct device *dev, @@ -343,11 +324,13 @@ static ssize_t active_low_store(struct device *dev, ssize_t status; long value; + status = kstrtol(buf, 0, &value); + if (status) + return status; + mutex_lock(&data->mutex); - status = kstrtol(buf, 0, &value); - if (status == 0) - status = gpio_sysfs_set_active_low(dev, value); + status = gpio_sysfs_set_active_low(dev, value); mutex_unlock(&data->mutex); @@ -790,11 +773,8 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev) mutex_unlock(&sysfs_lock); /* unregister gpiod class devices owned by sysfs */ - for (i = 0; i < chip->ngpio; i++) { - desc = &gdev->descs[i]; - if (test_and_clear_bit(FLAG_SYSFS, &desc->flags)) - gpiod_free(desc); - } + for_each_gpio_desc_with_flag(i, chip, desc, FLAG_SYSFS) + gpiod_free(desc); } static int __init gpiolib_sysfs_init(void) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 6630d92e30ad..e59884cc12a7 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -262,14 +262,14 @@ static int gpiodev_add_to_list(struct gpio_device *gdev) return 0; } - next = list_entry(gpio_devices.next, struct gpio_device, list); + next = list_first_entry(&gpio_devices, struct gpio_device, list); if (gdev->base + gdev->ngpio <= next->base) { /* add before first entry */ list_add(&gdev->list, &gpio_devices); return 0; } - prev = list_entry(gpio_devices.prev, struct gpio_device, list); + prev = list_last_entry(&gpio_devices, struct gpio_device, list); if (prev->base + prev->ngpio <= gdev->base) { /* add behind last entry */ list_add_tail(&gdev->list, &gpio_devices); @@ -3951,23 +3951,21 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev, * If a connection label was passed use that, else attempt to use * the device name as label */ - ret = gpiod_request(desc, con_id ? con_id : devname); + ret = gpiod_request(desc, con_id ?: devname); if (ret) { - if (ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) { - /* - * This happens when there are several consumers for - * the same GPIO line: we just return here without - * further initialization. It is a bit if a hack. - * This is necessary to support fixed regulators. - * - * FIXME: Make this more sane and safe. - */ - dev_info(dev, "nonexclusive access to GPIO for %s\n", - con_id ? con_id : devname); - return desc; - } else { + if (!(ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE)) return ERR_PTR(ret); - } + + /* + * This happens when there are several consumers for + * the same GPIO line: we just return here without + * further initialization. It is a bit of a hack. + * This is necessary to support fixed regulators. + * + * FIXME: Make this more sane and safe. + */ + dev_info(dev, "nonexclusive access to GPIO for %s\n", con_id ?: devname); + return desc; } ret = gpiod_configure_flags(desc, con_id, lookupflags, flags); @@ -4122,12 +4120,11 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, */ static void gpiochip_free_hogs(struct gpio_chip *gc) { + struct gpio_desc *desc; int id; - for (id = 0; id < gc->ngpio; id++) { - if (test_bit(FLAG_IS_HOGGED, &gc->gpiodev->descs[id].flags)) - gpiochip_free_own_desc(&gc->gpiodev->descs[id]); - } + for_each_gpio_desc_with_flag(id, gc, desc, FLAG_IS_HOGGED) + gpiochip_free_own_desc(desc); } /** @@ -4446,7 +4443,7 @@ static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos) if (list_is_last(&gdev->list, &gpio_devices)) ret = NULL; else - ret = list_entry(gdev->list.next, struct gpio_device, list); + ret = list_first_entry(&gdev->list, struct gpio_device, list); spin_unlock_irqrestore(&gpio_lock, flags); s->private = "\n"; diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index c31f4626915d..06f3faa9fbef 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -37,6 +37,9 @@ * or name of the IP component in a System on Chip. * @data: per-instance data assigned by the driver * @list: links gpio_device:s together for traversal + * @notifier: used to notify subscribers about lines being requested, released + * or reconfigured + * @pin_ranges: range of pins served by the GPIO driver * * This state container holds most of the runtime variable data * for a GPIO device and can hold references and live on after the @@ -72,6 +75,20 @@ struct gpio_device { /* gpio suffixes used for ACPI and device tree lookup */ static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" }; +/** + * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes + * + * @desc: Array of pointers to the GPIO descriptors + * @size: Number of elements in desc + * @chip: Parent GPIO chip + * @get_mask: Get mask used in fastpath + * @set_mask: Set mask used in fastpath + * @invert_mask: Invert mask used in fastpath + * + * This structure is attached to struct gpiod_descs obtained from + * gpiod_get_array() and can be passed back to get/set array functions in order + * to activate fast processing path if applicable. + */ struct gpio_array { struct gpio_desc **desc; unsigned int size; @@ -82,6 +99,13 @@ struct gpio_array { }; struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum); + +#define for_each_gpio_desc_with_flag(i, gc, desc, flag) \ + for (i = 0, desc = gpiochip_get_desc(gc, i); \ + i < gc->ngpio; \ + i++, desc = gpiochip_get_desc(gc, i)) \ + if (!test_bit(flag, &desc->flags)) {} else + int gpiod_get_array_value_complex(bool raw, bool can_sleep, unsigned int array_size, struct gpio_desc **desc_array, @@ -96,6 +120,23 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep, extern spinlock_t gpio_lock; extern struct list_head gpio_devices; + +/** + * struct gpio_desc - Opaque descriptor for a GPIO + * + * @gdev: Pointer to the parent GPIO device + * @flags: Binary descriptor flags + * @label: Name of the consumer + * @name: Line name + * @hog: Pointer to the device node that hogs this line (if any) + * @debounce_period_us: Debounce period in microseconds + * + * These are obtained using gpiod_get() and are preferable to the old + * integer-based handles. + * + * Contrary to integers, a pointer to a &struct gpio_desc is guaranteed to be + * valid until the GPIO is released. + */ struct gpio_desc { struct gpio_device *gdev; unsigned long flags; diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f5544157576c..7a674873d794 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -128,6 +128,8 @@ config HID_ACRUX_FF config HID_APPLE tristate "Apple {i,Power,Mac}Books" depends on HID + depends on LEDS_CLASS + depends on NEW_LEDS default !EXPERT help Support for some Apple devices which less or more break @@ -929,6 +931,13 @@ config PLAYSTATION_FF Say Y here if you would like to enable force feedback support for PlayStation game controllers. +config HID_RAZER + tristate "Razer non-fully HID-compliant devices" + depends on HID + help + Support for Razer devices that are not fully compliant with the + HID standard. + config HID_PRIMAX tristate "Primax non-fully HID-compliant devices" depends on HID @@ -984,6 +993,16 @@ config HID_SEMITEK - Woo-dy - X-Bows Nature/Knight +config HID_SIGMAMICRO + tristate "SiGma Micro-based keyboards" + depends on USB_HID + help + Support for keyboards that use the SiGma Micro (a.k.a SigmaChip) IC. + + Supported devices: + - Landslides KR-700 + - Rapoo V500 + config HID_SONY tristate "Sony PS2/3/4 accessories" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..d5ce8d747b14 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -99,6 +99,7 @@ hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o +obj-$(CONFIG_HID_RAZER) += hid-razer.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o obj-$(CONFIG_HID_RETRODE) += hid-retrode.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ @@ -109,6 +110,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o obj-$(CONFIG_HID_SAITEK) += hid-saitek.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o +obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 19fa734a9a79..6b5fd90b0bd1 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -301,11 +301,8 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i pci_set_master(pdev); rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (rc) { - rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); - if (rc) { - dev_err(&pdev->dev, "failed to set DMA mask\n"); - return rc; - } + dev_err(&pdev->dev, "failed to set DMA mask\n"); + return rc; } privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL); diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 7dc89dc6b0f0..0cf35caee9fa 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -7,6 +7,7 @@ * Copyright (c) 2005 Michael Haboustak for Concept2, Inc * Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2019 Paul Pawlowski */ /* @@ -33,6 +34,7 @@ /* BIT(7) reserved, was: APPLE_IGNORE_HIDINPUT */ #define APPLE_NUMLOCK_EMULATION BIT(8) #define APPLE_RDESC_BATTERY BIT(9) +#define APPLE_BACKLIGHT_CTL BIT(10) #define APPLE_FLAG_FKEY 0x01 @@ -61,6 +63,12 @@ MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. " "(For people who want to keep PC keyboard muscle memory. " "[0] = as-is, Mac layout, 1 = swapped, PC layout)"); +struct apple_sc_backlight { + struct led_classdev cdev; + struct hid_device *hdev; + unsigned short backlight_off, backlight_on_min, backlight_on_max; +}; + struct apple_sc { struct hid_device *hdev; unsigned long quirks; @@ -68,6 +76,7 @@ struct apple_sc { unsigned int fn_found; DECLARE_BITMAP(pressed_numlock, KEY_CNT); struct timer_list battery_timer; + struct apple_sc_backlight *backlight; }; struct apple_key_translation { @@ -76,6 +85,61 @@ struct apple_key_translation { u8 flags; }; +static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, + { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, + { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, + { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY }, + { KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY }, + { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, + { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, + { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, + { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, + { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, + { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + +static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, + { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, + { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, + { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY }, + { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, + { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, + { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, + { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, + { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, + { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + +struct apple_backlight_config_report { + u8 report_id; + u8 version; + u16 backlight_off, backlight_on_min, backlight_on_max; +}; + +struct apple_backlight_set_report { + u8 report_id; + u8 version; + u16 backlight; + u16 rate; +}; + + static const struct apple_key_translation apple2021_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -119,6 +183,51 @@ static const struct apple_key_translation macbookair_fn_keys[] = { { } }; +static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_GRAVE, KEY_ESC }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + +static const struct apple_key_translation macbookpro_dedicated_esc_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + static const struct apple_key_translation apple_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -202,6 +311,15 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = { { } }; +static inline void apple_setup_key_translation(struct input_dev *input, + const struct apple_key_translation *table) +{ + const struct apple_key_translation *trans; + + for (trans = table; trans->from; trans++) + set_bit(trans->to, input->keybit); +} + static const struct apple_key_translation *apple_find_translation( const struct apple_key_translation *table, u16 from) { @@ -242,10 +360,34 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, } if (fnmode) { - if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 || - hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || - hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) + if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS) + table = magic_keyboard_alu_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015) + table = magic_keyboard_2015_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) table = apple2021_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) + table = macbookpro_no_esc_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) + table = macbookpro_dedicated_esc_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) + table = apple_fn_keys; else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) table = macbookair_fn_keys; @@ -452,30 +594,21 @@ static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc, static void apple_setup_input(struct input_dev *input) { - const struct apple_key_translation *trans; - set_bit(KEY_NUMLOCK, input->keybit); /* Enable all needed keys */ - for (trans = apple_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); + apple_setup_key_translation(input, apple_fn_keys); + apple_setup_key_translation(input, powerbook_fn_keys); + apple_setup_key_translation(input, powerbook_numlock_keys); + apple_setup_key_translation(input, apple_iso_keyboard); + apple_setup_key_translation(input, magic_keyboard_alu_fn_keys); + apple_setup_key_translation(input, magic_keyboard_2015_fn_keys); + apple_setup_key_translation(input, apple2021_fn_keys); + apple_setup_key_translation(input, macbookpro_no_esc_fn_keys); + apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys); - for (trans = powerbook_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = powerbook_numlock_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = apple_iso_keyboard; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = apple2021_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - if (swap_fn_leftctrl) { - for (trans = swapped_fn_leftctrl_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - } + if (swap_fn_leftctrl) + apple_setup_key_translation(input, swapped_fn_leftctrl_keys); } static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -530,6 +663,105 @@ static int apple_input_configured(struct hid_device *hdev, return 0; } +static bool apple_backlight_check_support(struct hid_device *hdev) +{ + int i; + unsigned int hid; + struct hid_report *report; + + list_for_each_entry(report, &hdev->report_enum[HID_INPUT_REPORT].report_list, list) { + for (i = 0; i < report->maxfield; i++) { + hid = report->field[i]->usage->hid; + if ((hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR && (hid & HID_USAGE) == 0xf) + return true; + } + } + + return false; +} + +static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate) +{ + int ret = 0; + struct apple_backlight_set_report *rep; + + rep = kmalloc(sizeof(*rep), GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + rep->report_id = 0xB0; + rep->version = 1; + rep->backlight = value; + rep->rate = rate; + + ret = hid_hw_raw_request(hdev, 0xB0u, (u8 *) rep, sizeof(*rep), + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + + kfree(rep); + return ret; +} + +static int apple_backlight_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct apple_sc_backlight *backlight = container_of(led_cdev, + struct apple_sc_backlight, cdev); + + return apple_backlight_set(backlight->hdev, brightness, 0); +} + +static int apple_backlight_init(struct hid_device *hdev) +{ + int ret; + struct apple_sc *asc = hid_get_drvdata(hdev); + struct apple_backlight_config_report *rep; + + if (!apple_backlight_check_support(hdev)) + return -EINVAL; + + rep = kmalloc(0x200, GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, 0xBFu, (u8 *) rep, sizeof(*rep), + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) { + hid_err(hdev, "backlight request failed: %d\n", ret); + goto cleanup_and_exit; + } + if (ret < 8 || rep->version != 1) { + hid_err(hdev, "backlight config struct: bad version %i\n", rep->version); + ret = -EINVAL; + goto cleanup_and_exit; + } + + hid_dbg(hdev, "backlight config: off=%u, on_min=%u, on_max=%u\n", + rep->backlight_off, rep->backlight_on_min, rep->backlight_on_max); + + asc->backlight = devm_kzalloc(&hdev->dev, sizeof(*asc->backlight), GFP_KERNEL); + if (!asc->backlight) { + ret = -ENOMEM; + goto cleanup_and_exit; + } + + asc->backlight->hdev = hdev; + asc->backlight->cdev.name = "apple::kbd_backlight"; + asc->backlight->cdev.max_brightness = rep->backlight_on_max; + asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set; + + ret = apple_backlight_set(hdev, 0, 0); + if (ret < 0) { + hid_err(hdev, "backlight set request failed: %d\n", ret); + goto cleanup_and_exit; + } + + ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev); + +cleanup_and_exit: + kfree(rep); + return ret; +} + static int apple_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -565,6 +797,9 @@ static int apple_probe(struct hid_device *hdev, jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS)); apple_fetch_battery(hdev); + if (quirks & APPLE_BACKLIGHT_CTL) + apple_backlight_init(hdev); + return 0; } @@ -736,6 +971,22 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS), .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F), + .driver_data = APPLE_HAS_FN }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO), @@ -748,15 +999,15 @@ static const struct hid_device_id apple_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..db925794fbe6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -81,6 +81,7 @@ struct hid_report *hid_register_report(struct hid_device *device, report_enum->report_id_hash[id] = report; list_add_tail(&report->list, &report_enum->report_list); + INIT_LIST_HEAD(&report->field_entry_list); return report; } @@ -101,7 +102,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field = kzalloc((sizeof(struct hid_field) + usages * sizeof(struct hid_usage) + - usages * sizeof(unsigned)), GFP_KERNEL); + 3 * usages * sizeof(unsigned int)), GFP_KERNEL); if (!field) return NULL; @@ -109,6 +110,8 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned report->field[field->index] = field; field->usage = (struct hid_usage *)(field + 1); field->value = (s32 *)(field->usage + usages); + field->new_value = (s32 *)(field->value + usages); + field->usages_priorities = (s32 *)(field->new_value + usages); field->report = report; return field; @@ -656,6 +659,8 @@ static void hid_free_report(struct hid_report *report) { unsigned n; + kfree(report->field_entries); + for (n = 0; n < report->maxfield; n++) kfree(report->field[n]); kfree(report); @@ -1525,25 +1530,41 @@ static void hid_process_event(struct hid_device *hid, struct hid_field *field, } /* - * Analyse a received field, and fetch the data from it. The field - * content is stored for next report processing (we do differential - * reporting to the layer). + * Checks if the given value is valid within this field */ +static inline int hid_array_value_is_valid(struct hid_field *field, + __s32 value) +{ + __s32 min = field->logical_minimum; -static void hid_input_field(struct hid_device *hid, struct hid_field *field, - __u8 *data, int interrupt) + /* + * Value needs to be between logical min and max, and + * (value - min) is used as an index in the usage array. + * This array is of size field->maxusage + */ + return value >= min && + value <= field->logical_maximum && + value - min < field->maxusage; +} + +/* + * Fetch the field from the data. The field content is stored for next + * report processing (we do differential reporting to the layer). + */ +static void hid_input_fetch_field(struct hid_device *hid, + struct hid_field *field, + __u8 *data) { unsigned n; unsigned count = field->report_count; unsigned offset = field->report_offset; unsigned size = field->report_size; __s32 min = field->logical_minimum; - __s32 max = field->logical_maximum; __s32 *value; - value = kmalloc_array(count, sizeof(__s32), GFP_ATOMIC); - if (!value) - return; + value = field->new_value; + memset(value, 0, count * sizeof(__s32)); + field->ignored = false; for (n = 0; n < count; n++) { @@ -1554,35 +1575,228 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, /* Ignore report if ErrorRollOver */ if (!(field->flags & HID_MAIN_ITEM_VARIABLE) && - value[n] >= min && value[n] <= max && - value[n] - min < field->maxusage && - field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) - goto exit; + hid_array_value_is_valid(field, value[n]) && + field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) { + field->ignored = true; + return; + } } +} + +/* + * Process a received variable field. + */ + +static void hid_input_var_field(struct hid_device *hid, + struct hid_field *field, + int interrupt) +{ + unsigned int count = field->report_count; + __s32 *value = field->new_value; + unsigned int n; + + for (n = 0; n < count; n++) + hid_process_event(hid, + field, + &field->usage[n], + value[n], + interrupt); + + memcpy(field->value, value, count * sizeof(__s32)); +} + +/* + * Process a received array field. The field content is stored for + * next report processing (we do differential reporting to the layer). + */ + +static void hid_input_array_field(struct hid_device *hid, + struct hid_field *field, + int interrupt) +{ + unsigned int n; + unsigned int count = field->report_count; + __s32 min = field->logical_minimum; + __s32 *value; + + value = field->new_value; + + /* ErrorRollOver */ + if (field->ignored) + return; for (n = 0; n < count; n++) { + if (hid_array_value_is_valid(field, field->value[n]) && + search(value, field->value[n], count)) + hid_process_event(hid, + field, + &field->usage[field->value[n] - min], + 0, + interrupt); - if (HID_MAIN_ITEM_VARIABLE & field->flags) { - hid_process_event(hid, field, &field->usage[n], value[n], interrupt); - continue; - } - - if (field->value[n] >= min && field->value[n] <= max - && field->value[n] - min < field->maxusage - && field->usage[field->value[n] - min].hid - && search(value, field->value[n], count)) - hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt); - - if (value[n] >= min && value[n] <= max - && value[n] - min < field->maxusage - && field->usage[value[n] - min].hid - && search(field->value, value[n], count)) - hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt); + if (hid_array_value_is_valid(field, value[n]) && + search(field->value, value[n], count)) + hid_process_event(hid, + field, + &field->usage[value[n] - min], + 1, + interrupt); } memcpy(field->value, value, count * sizeof(__s32)); -exit: - kfree(value); +} + +/* + * Analyse a received report, and fetch the data from it. The field + * content is stored for next report processing (we do differential + * reporting to the layer). + */ +static void hid_process_report(struct hid_device *hid, + struct hid_report *report, + __u8 *data, + int interrupt) +{ + unsigned int a; + struct hid_field_entry *entry; + struct hid_field *field; + + /* first retrieve all incoming values in data */ + for (a = 0; a < report->maxfield; a++) + hid_input_fetch_field(hid, field = report->field[a], data); + + if (!list_empty(&report->field_entry_list)) { + /* INPUT_REPORT, we have a priority list of fields */ + list_for_each_entry(entry, + &report->field_entry_list, + list) { + field = entry->field; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + hid_process_event(hid, + field, + &field->usage[entry->index], + field->new_value[entry->index], + interrupt); + else + hid_input_array_field(hid, field, interrupt); + } + + /* we need to do the memcpy at the end for var items */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + memcpy(field->value, field->new_value, + field->report_count * sizeof(__s32)); + } + } else { + /* FEATURE_REPORT, regular processing */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + hid_input_var_field(hid, field, interrupt); + else + hid_input_array_field(hid, field, interrupt); + } + } +} + +/* + * Insert a given usage_index in a field in the list + * of processed usages in the report. + * + * The elements of lower priority score are processed + * first. + */ +static void __hid_insert_field_entry(struct hid_device *hid, + struct hid_report *report, + struct hid_field_entry *entry, + struct hid_field *field, + unsigned int usage_index) +{ + struct hid_field_entry *next; + + entry->field = field; + entry->index = usage_index; + entry->priority = field->usages_priorities[usage_index]; + + /* insert the element at the correct position */ + list_for_each_entry(next, + &report->field_entry_list, + list) { + /* + * the priority of our element is strictly higher + * than the next one, insert it before + */ + if (entry->priority > next->priority) { + list_add_tail(&entry->list, &next->list); + return; + } + } + + /* lowest priority score: insert at the end */ + list_add_tail(&entry->list, &report->field_entry_list); +} + +static void hid_report_process_ordering(struct hid_device *hid, + struct hid_report *report) +{ + struct hid_field *field; + struct hid_field_entry *entries; + unsigned int a, u, usages; + unsigned int count = 0; + + /* count the number of individual fields in the report */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + count += field->report_count; + else + count++; + } + + /* allocate the memory to process the fields */ + entries = kcalloc(count, sizeof(*entries), GFP_KERNEL); + if (!entries) + return; + + report->field_entries = entries; + + /* + * walk through all fields in the report and + * store them by priority order in report->field_entry_list + * + * - Var elements are individualized (field + usage_index) + * - Arrays are taken as one, we can not chose an order for them + */ + usages = 0; + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) { + for (u = 0; u < field->report_count; u++) { + __hid_insert_field_entry(hid, report, + &entries[usages], + field, u); + usages++; + } + } else { + __hid_insert_field_entry(hid, report, &entries[usages], + field, 0); + usages++; + } + } +} + +static void hid_process_ordering(struct hid_device *hid) +{ + struct hid_report *report; + struct hid_report_enum *report_enum = &hid->report_enum[HID_INPUT_REPORT]; + + list_for_each_entry(report, &report_enum->report_list, list) + hid_report_process_ordering(hid, report); } /* @@ -1746,7 +1960,6 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_report_enum *report_enum = hid->report_enum + type; struct hid_report *report; struct hid_driver *hdrv; - unsigned int a; u32 rsize, csize = size; u8 *cdata = data; int ret = 0; @@ -1782,8 +1995,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, } if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) { - for (a = 0; a < report->maxfield; a++) - hid_input_field(hid, report->field[a], cdata, interrupt); + hid_process_report(hid, report, cdata, interrupt); hdrv = hid->driver; if (hdrv && hdrv->report) hdrv->report(hid, report); @@ -1970,6 +2182,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) return -ENODEV; } + hid_process_ordering(hdev); + if ((hdev->claimed & HID_CLAIMED_INPUT) && (connect_mask & HID_CONNECT_FF) && hdev->ff_init) hdev->ff_init(hdev); diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 0403beb3104b..ddbe0de177e2 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -58,7 +58,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, struct cros_ec_command *msg; int ret; - msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)), + msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), GFP_KERNEL); if (!msg) return -ENOMEM; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 78bd3ddda442..053853a891c5 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -167,6 +167,14 @@ #define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272 #define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273 #define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340 #define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a #define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b #define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240 @@ -606,7 +614,7 @@ #define USB_VENDOR_ID_HUION 0x256c #define USB_DEVICE_ID_HUION_TABLET 0x006e -#define USB_DEVICE_ID_HUION_HS64 0x006d +#define USB_DEVICE_ID_HUION_TABLET2 0x006d #define USB_VENDOR_ID_IBM 0x04b3 #define USB_DEVICE_ID_IBM_SCROLLPOINT_III 0x3100 @@ -1030,6 +1038,9 @@ #define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118 #define USB_VENDOR_ID_RAZER 0x1532 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D +#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC 0x011b #define USB_DEVICE_ID_RAZER_BLADE_14 0x011D #define USB_VENDOR_ID_REALTEK 0x0bda @@ -1092,6 +1103,7 @@ #define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 +#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059 #define USB_VENDOR_ID_SIGMATEL 0x066F #define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 56ec27398a00..c6b27aab9041 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -48,6 +48,51 @@ static const struct { __s32 y; } hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; +struct usage_priority { + __u32 usage; /* the HID usage associated */ + bool global; /* we assume all usages to be slotted, + * unless global + */ + unsigned int slot_overwrite; /* for globals: allows to set the usage + * before or after the slots + */ +}; + +/* + * hid-input will convert this list into priorities: + * the first element will have the highest priority + * (the length of the following array) and the last + * element the lowest (1). + * + * hid-input will then shift the priority by 8 bits to leave some space + * in case drivers want to interleave other fields. + * + * To accommodate slotted devices, the slot priority is + * defined in the next 8 bits (defined by 0xff - slot). + * + * If drivers want to add fields before those, hid-input will + * leave out the first 8 bits of the priority value. + * + * This still leaves us 65535 individual priority values. + */ +static const struct usage_priority hidinput_usages_priorities[] = { + { /* Eraser (eraser touching) must always come before tipswitch */ + .usage = HID_DG_ERASER, + }, + { /* Invert must always come before In Range */ + .usage = HID_DG_INVERT, + }, + { /* Is the tip of the tool touching? */ + .usage = HID_DG_TIPSWITCH, + }, + { /* Tip Pressure might emulate tip switch */ + .usage = HID_DG_TIPPRESSURE, + }, + { /* In Range needs to come after the other tool states */ + .usage = HID_DG_INRANGE, + }, +}; + #define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c)) #define map_rel(c) hid_map_usage(hidinput, usage, &bit, &max, EV_REL, (c)) #define map_key(c) hid_map_usage(hidinput, usage, &bit, &max, EV_KEY, (c)) @@ -586,11 +631,13 @@ static bool hidinput_field_in_collection(struct hid_device *device, struct hid_f } static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, - struct hid_usage *usage) + struct hid_usage *usage, unsigned int usage_index) { struct input_dev *input = hidinput->input; struct hid_device *device = input_get_drvdata(input); + const struct usage_priority *usage_priority = NULL; int max = 0, code; + unsigned int i = 0; unsigned long *bit = NULL; field->hidinput = hidinput; @@ -608,6 +655,28 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel goto ignore; } + /* assign a priority based on the static list declared here */ + for (i = 0; i < ARRAY_SIZE(hidinput_usages_priorities); i++) { + if (usage->hid == hidinput_usages_priorities[i].usage) { + usage_priority = &hidinput_usages_priorities[i]; + + field->usages_priorities[usage_index] = + (ARRAY_SIZE(hidinput_usages_priorities) - i) << 8; + break; + } + } + + /* + * For slotted devices, we need to also add the slot index + * in the priority. + */ + if (usage_priority && usage_priority->global) + field->usages_priorities[usage_index] |= + usage_priority->slot_overwrite; + else + field->usages_priorities[usage_index] |= + (0xff - field->slot_idx) << 16; + if (device->driver->input_mapping) { int ret = device->driver->input_mapping(device, hidinput, field, usage, &bit, &max); @@ -828,10 +897,31 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel break; case 0x32: /* InRange */ - switch (field->physical & 0xff) { - case 0x21: map_key(BTN_TOOL_MOUSE); break; - case 0x22: map_key(BTN_TOOL_FINGER); break; - default: map_key(BTN_TOOL_PEN); break; + switch (field->physical) { + case HID_DG_PUCK: + map_key(BTN_TOOL_MOUSE); + break; + case HID_DG_FINGER: + map_key(BTN_TOOL_FINGER); + break; + default: + /* + * If the physical is not given, + * rely on the application. + */ + if (!field->physical) { + switch (field->application) { + case HID_DG_TOUCHSCREEN: + case HID_DG_TOUCHPAD: + map_key_clear(BTN_TOOL_FINGER); + break; + default: + map_key_clear(BTN_TOOL_PEN); + } + } else { + map_key(BTN_TOOL_PEN); + } + break; } break; @@ -1318,9 +1408,38 @@ static void hidinput_handle_scroll(struct hid_usage *usage, input_event(input, EV_REL, usage->code, hi_res); } +static void hid_report_release_tool(struct hid_report *report, struct input_dev *input, + unsigned int tool) +{ + /* if the given tool is not currently reported, ignore */ + if (!test_bit(tool, input->key)) + return; + + /* + * if the given tool was previously set, release it, + * release any TOUCH and send an EV_SYN + */ + input_event(input, EV_KEY, BTN_TOUCH, 0); + input_event(input, EV_KEY, tool, 0); + input_event(input, EV_SYN, SYN_REPORT, 0); + + report->tool = 0; +} + +static void hid_report_set_tool(struct hid_report *report, struct input_dev *input, + unsigned int new_tool) +{ + if (report->tool != new_tool) + hid_report_release_tool(report, input, report->tool); + + input_event(input, EV_KEY, new_tool, 1); + report->tool = new_tool; +} + void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value) { struct input_dev *input; + struct hid_report *report = field->report; unsigned *quirks = &hid->quirks; if (!usage->type) @@ -1336,12 +1455,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct input = field->hidinput->input; - if (usage->type == EV_ABS && - (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) || - ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))) { - value = field->logical_maximum - value; - } - if (usage->hat_min < usage->hat_max || usage->hat_dir) { int hat_dir = usage->hat_dir; if (!hat_dir) @@ -1352,61 +1465,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct return; } - if (usage->hid == HID_DG_INVERT) { - *quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); - return; - } - - if (usage->hid == HID_DG_INRANGE) { - if (value) { - input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1); - return; - } - input_event(input, usage->type, usage->code, 0); - input_event(input, usage->type, BTN_TOOL_RUBBER, 0); - return; - } - - if (usage->hid == HID_DG_TIPPRESSURE && (*quirks & HID_QUIRK_NOTOUCH)) { - int a = field->logical_minimum; - int b = field->logical_maximum; - input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); - } - - if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */ - dbg_hid("Maximum Effects - %d\n",value); - return; - } - - if (usage->hid == (HID_UP_PID | 0x7fUL)) { - dbg_hid("PID Pool Report\n"); - return; - } - - if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */ - return; - - if ((usage->type == EV_REL) && (usage->code == REL_WHEEL_HI_RES || - usage->code == REL_HWHEEL_HI_RES)) { - hidinput_handle_scroll(usage, input, value); - return; - } - - if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) && - (usage->code == ABS_VOLUME)) { - int count = abs(value); - int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; - int i; - - for (i = 0; i < count; i++) { - input_event(input, EV_KEY, direction, 1); - input_sync(input); - input_event(input, EV_KEY, direction, 0); - input_sync(input); - } - return; - } - /* * Ignore out-of-range values as per HID specification, * section 5.10 and 6.2.25, when NULL state bit is present. @@ -1419,7 +1477,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct * don't specify logical min and max. */ if ((field->flags & HID_MAIN_ITEM_VARIABLE) && - (field->logical_minimum < field->logical_maximum)) { + field->logical_minimum < field->logical_maximum) { if (field->flags & HID_MAIN_ITEM_NULL_STATE && (value < field->logical_minimum || value > field->logical_maximum)) { @@ -1431,6 +1489,123 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct field->logical_maximum); } + switch (usage->hid) { + case HID_DG_ERASER: + report->tool_active |= !!value; + + /* + * if eraser is set, we must enforce BTN_TOOL_RUBBER + * to accommodate for devices not following the spec. + */ + if (value) + hid_report_set_tool(report, input, BTN_TOOL_RUBBER); + else if (report->tool != BTN_TOOL_RUBBER) + /* value is off, tool is not rubber, ignore */ + return; + + /* let hid-input set BTN_TOUCH */ + break; + + case HID_DG_INVERT: + report->tool_active |= !!value; + + /* + * If invert is set, we store BTN_TOOL_RUBBER. + */ + if (value) + hid_report_set_tool(report, input, BTN_TOOL_RUBBER); + else if (!report->tool_active) + /* tool_active not set means Invert and Eraser are not set */ + hid_report_release_tool(report, input, BTN_TOOL_RUBBER); + + /* no further processing */ + return; + + case HID_DG_INRANGE: + report->tool_active |= !!value; + + if (report->tool_active) { + /* + * if tool is not set but is marked as active, + * assume ours + */ + if (!report->tool) + hid_report_set_tool(report, input, usage->code); + } else { + hid_report_release_tool(report, input, usage->code); + } + + /* reset tool_active for the next event */ + report->tool_active = false; + + /* no further processing */ + return; + + case HID_DG_TIPSWITCH: + report->tool_active |= !!value; + + /* if tool is set to RUBBER we should ignore the current value */ + if (report->tool == BTN_TOOL_RUBBER) + return; + + break; + + case HID_DG_TIPPRESSURE: + if (*quirks & HID_QUIRK_NOTOUCH) { + int a = field->logical_minimum; + int b = field->logical_maximum; + + if (value > a + ((b - a) >> 3)) { + input_event(input, EV_KEY, BTN_TOUCH, 1); + report->tool_active = true; + } + } + break; + + case HID_UP_PID | 0x83UL: /* Simultaneous Effects Max */ + dbg_hid("Maximum Effects - %d\n",value); + return; + + case HID_UP_PID | 0x7fUL: + dbg_hid("PID Pool Report\n"); + return; + } + + switch (usage->type) { + case EV_KEY: + if (usage->code == 0) /* Key 0 is "unassigned", not KEY_UNKNOWN */ + return; + break; + + case EV_REL: + if (usage->code == REL_WHEEL_HI_RES || + usage->code == REL_HWHEEL_HI_RES) { + hidinput_handle_scroll(usage, input, value); + return; + } + break; + + case EV_ABS: + if ((field->flags & HID_MAIN_ITEM_RELATIVE) && + usage->code == ABS_VOLUME) { + int count = abs(value); + int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + int i; + + for (i = 0; i < count; i++) { + input_event(input, EV_KEY, direction, 1); + input_sync(input); + input_event(input, EV_KEY, direction, 0); + input_sync(input); + } + return; + + } else if (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) || + ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y)) + value = field->logical_maximum - value; + break; + } + /* * Ignore reports for absolute data if the data didn't change. This is * not only an optimization but also fixes 'dead' key reports. Some @@ -1933,12 +2108,63 @@ static struct hid_input *hidinput_match_application(struct hid_report *report) static inline void hidinput_configure_usages(struct hid_input *hidinput, struct hid_report *report) { - int i, j; + int i, j, k; + int first_field_index = 0; + int slot_collection_index = -1; + int prev_collection_index = -1; + unsigned int slot_idx = 0; + struct hid_field *field; + + /* + * First tag all the fields that are part of a slot, + * a slot needs to have one Contact ID in the collection + */ + for (i = 0; i < report->maxfield; i++) { + field = report->field[i]; + + /* ignore fields without usage */ + if (field->maxusage < 1) + continue; + + /* + * janitoring when collection_index changes + */ + if (prev_collection_index != field->usage->collection_index) { + prev_collection_index = field->usage->collection_index; + first_field_index = i; + } + + /* + * if we already found a Contact ID in the collection, + * tag and continue to the next. + */ + if (slot_collection_index == field->usage->collection_index) { + field->slot_idx = slot_idx; + continue; + } + + /* check if the current field has Contact ID */ + for (j = 0; j < field->maxusage; j++) { + if (field->usage[j].hid == HID_DG_CONTACTID) { + slot_collection_index = field->usage->collection_index; + slot_idx++; + + /* + * mark all previous fields and this one in the + * current collection to be slotted. + */ + for (k = first_field_index; k <= i; k++) + report->field[k]->slot_idx = slot_idx; + break; + } + } + } for (i = 0; i < report->maxfield; i++) for (j = 0; j < report->field[i]->maxusage; j++) hidinput_configure_usage(hidinput, report->field[i], - report->field[i]->usage + j); + report->field[i]->usage + j, + j); } /* diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c066ba901867..dc67717d2dab 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -295,6 +295,14 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS) }, @@ -930,6 +938,14 @@ static const struct hid_device_id hid_mouse_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { } diff --git a/drivers/hid/hid-razer.c b/drivers/hid/hid-razer.c new file mode 100644 index 000000000000..740df148b0ef --- /dev/null +++ b/drivers/hid/hid-razer.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for gaming keys on Razer Blackwidow gaming keyboards + * Macro Key Keycodes: M1 = 191, M2 = 192, M3 = 193, M4 = 194, M5 = 195 + * + * Copyright (c) 2021 Jelle van der Waa + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +#define RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE 91 + +static bool macro_key_remapping = 1; +module_param(macro_key_remapping, bool, 0644); +MODULE_PARM_DESC(macro_key_remapping, " on (Y) off (N)"); + + +static unsigned char blackwidow_init[RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE] = { + 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00 +}; + +static int razer_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + + if (!macro_key_remapping) + return 0; + + if ((usage->hid & HID_UP_KEYBOARD) != HID_UP_KEYBOARD) + return 0; + + switch (usage->hid & ~HID_UP_KEYBOARD) { + case 0x68: + map_key_clear(KEY_MACRO1); + return 1; + case 0x69: + map_key_clear(KEY_MACRO2); + return 1; + case 0x6a: + map_key_clear(KEY_MACRO3); + return 1; + case 0x6b: + map_key_clear(KEY_MACRO4); + return 1; + case 0x6c: + map_key_clear(KEY_MACRO5); + return 1; + } + + return 0; +} + +static int razer_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + char *buf; + int ret = 0; + + ret = hid_parse(hdev); + if (ret) + return ret; + + /* + * Only send the enable macro keys command for the third device + * identified as mouse input. + */ + if (hdev->type == HID_TYPE_USBMOUSE) { + buf = kmemdup(blackwidow_init, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, 0, buf, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret != RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE) + hid_err(hdev, "failed to enable macro keys: %d\n", ret); + + kfree(buf); + } + + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE) }, + { } +}; +MODULE_DEVICE_TABLE(hid, razer_devices); + +static struct hid_driver razer_driver = { + .name = "razer", + .id_table = razer_devices, + .input_mapping = razer_input_mapping, + .probe = razer_probe, +}; +module_hid_driver(razer_driver); + +MODULE_AUTHOR("Jelle van der Waa "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-sigmamicro.c b/drivers/hid/hid-sigmamicro.c new file mode 100644 index 000000000000..2e7058ac0e9d --- /dev/null +++ b/drivers/hid/hid-sigmamicro.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for SiGma Micro-based keyboards + * + * Copyright (c) 2016 Kinglong Mee + * Copyright (c) 2021 Desmond Lim + */ + +#include +#include +#include + +#include "hid-ids.h" + +static const __u8 sm_0059_rdesc[] = { + 0x05, 0x0c, /* Usage Page (Consumer Devices) 0 */ + 0x09, 0x01, /* Usage (Consumer Control) 2 */ + 0xa1, 0x01, /* Collection (Application) 4 */ + 0x85, 0x01, /* Report ID (1) 6 */ + 0x19, 0x00, /* Usage Minimum (0) 8 */ + 0x2a, 0x3c, 0x02, /* Usage Maximum (572) 10 */ + 0x15, 0x00, /* Logical Minimum (0) 13 */ + 0x26, 0x3c, 0x02, /* Logical Maximum (572) 15 */ + 0x95, 0x01, /* Report Count (1) 18 */ + 0x75, 0x10, /* Report Size (16) 20 */ + 0x81, 0x00, /* Input (Data,Arr,Abs) 22 */ + 0xc0, /* End Collection 24 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 25 */ + 0x09, 0x80, /* Usage (System Control) 27 */ + 0xa1, 0x01, /* Collection (Application) 29 */ + 0x85, 0x02, /* Report ID (2) 31 */ + 0x19, 0x81, /* Usage Minimum (129) 33 */ + 0x29, 0x83, /* Usage Maximum (131) 35 */ + 0x25, 0x01, /* Logical Maximum (1) 37 */ + 0x75, 0x01, /* Report Size (1) 39 */ + 0x95, 0x03, /* Report Count (3) 41 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 43 */ + 0x95, 0x05, /* Report Count (5) 45 */ + 0x81, 0x01, /* Input (Cnst,Arr,Abs) 47 */ + 0xc0, /* End Collection 49 */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) 50 */ + 0x09, 0x01, /* Usage (Vendor Usage 1) 53 */ + 0xa1, 0x01, /* Collection (Application) 55 */ + 0x85, 0x03, /* Report ID (3) 57 */ + 0x1a, 0xf1, 0x00, /* Usage Minimum (241) 59 */ + 0x2a, 0xf8, 0x00, /* Usage Maximum (248) 62 */ + 0x15, 0x00, /* Logical Minimum (0) 65 */ + 0x25, 0x01, /* Logical Maximum (1) 67 */ + 0x75, 0x01, /* Report Size (1) 69 */ + 0x95, 0x08, /* Report Count (8) 71 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 73 */ + 0xc0, /* End Collection 75 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 76 */ + 0x09, 0x06, /* Usage (Keyboard) 78 */ + 0xa1, 0x01, /* Collection (Application) 80 */ + 0x85, 0x04, /* Report ID (4) 82 */ + 0x05, 0x07, /* Usage Page (Keyboard) 84 */ + 0x19, 0xe0, /* Usage Minimum (224) 86 */ + 0x29, 0xe7, /* Usage Maximum (231) 88 */ + 0x15, 0x00, /* Logical Minimum (0) 90 */ + 0x25, 0x01, /* Logical Maximum (1) 92 */ + 0x75, 0x01, /* Report Size (1) 94 */ + 0x95, 0x08, /* Report Count (8) 96 */ + 0x81, 0x00, /* Input (Data,Arr,Abs) 98 */ + 0x95, 0x30, /* Report Count (48) 100 */ + 0x75, 0x01, /* Report Size (1) 102 */ + 0x15, 0x00, /* Logical Minimum (0) 104 */ + 0x25, 0x01, /* Logical Maximum (1) 106 */ + 0x05, 0x07, /* Usage Page (Keyboard) 108 */ + 0x19, 0x00, /* Usage Minimum (0) 110 */ + 0x29, 0x2f, /* Usage Maximum (47) 112 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 114 */ + 0xc0, /* End Collection 116 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 117 */ + 0x09, 0x06, /* Usage (Keyboard) 119 */ + 0xa1, 0x01, /* Collection (Application) 121 */ + 0x85, 0x05, /* Report ID (5) 123 */ + 0x95, 0x38, /* Report Count (56) 125 */ + 0x75, 0x01, /* Report Size (1) 127 */ + 0x15, 0x00, /* Logical Minimum (0) 129 */ + 0x25, 0x01, /* Logical Maximum (1) 131 */ + 0x05, 0x07, /* Usage Page (Keyboard) 133 */ + 0x19, 0x30, /* Usage Minimum (48) 135 */ + 0x29, 0x67, /* Usage Maximum (103) 137 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 139 */ + 0xc0, /* End Collection 141 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 142 */ + 0x09, 0x06, /* Usage (Keyboard) 144 */ + 0xa1, 0x01, /* Collection (Application) 146 */ + 0x85, 0x06, /* Report ID (6) 148 */ + 0x95, 0x38, /* Report Count (56) 150 */ + 0x75, 0x01, /* Report Size (1) 152 */ + 0x15, 0x00, /* Logical Minimum (0) 154 */ + 0x25, 0x01, /* Logical Maximum (1) 156 */ + 0x05, 0x07, /* Usage Page (Keyboard) 158 */ + 0x19, 0x68, /* Usage Minimum (104) 160 */ + 0x29, 0x9f, /* Usage Maximum (159) 162 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 164 */ + 0xc0, /* End Collection 166 */ +}; + +static __u8 *sm_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize == sizeof(sm_0059_rdesc) && + !memcmp(sm_0059_rdesc, rdesc, *rsize)) { + hid_info(hdev, "Fixing up SiGma Micro report descriptor\n"); + rdesc[99] = 0x02; + } + return rdesc; +} + +static const struct hid_device_id sm_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, + USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2) }, + { } +}; +MODULE_DEVICE_TABLE(hid, sm_devices); + +static struct hid_driver sm_driver = { + .name = "sigmamicro", + .id_table = sm_devices, + .report_fixup = sm_report_fixup, +}; +module_hid_driver(sm_driver); + +MODULE_AUTHOR("Kinglong Mee "); +MODULE_AUTHOR("Desmond Lim "); +MODULE_DESCRIPTION("SiGma Micro HID driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index d8ab0139e5cd..05147f2d7564 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -81,24 +81,6 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } -static int uclogic_input_mapping(struct hid_device *hdev, - struct hid_input *hi, - struct hid_field *field, - struct hid_usage *usage, - unsigned long **bit, - int *max) -{ - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - struct uclogic_params *params = &drvdata->params; - - /* discard the unused pen interface */ - if (params->pen_unused && (field->application == HID_DG_PEN)) - return -1; - - /* let hid-core decide what to do */ - return 0; -} - static int uclogic_input_configured(struct hid_device *hdev, struct hid_input *hi) { @@ -246,100 +228,171 @@ static int uclogic_resume(struct hid_device *hdev) } #endif +/** + * uclogic_raw_event_pen - handle raw pen events (pen HID reports). + * + * @drvdata: Driver data. + * @data: Report data buffer, can be modified. + * @size: Report data size, bytes. + * + * Returns: + * Negative value on error (stops event delivery), zero for success. + */ +static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, + u8 *data, int size) +{ + struct uclogic_params_pen *pen = &drvdata->params.pen; + + WARN_ON(drvdata == NULL); + WARN_ON(data == NULL && size != 0); + + /* If in-range reports are inverted */ + if (pen->inrange == + UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { + /* Invert the in-range bit */ + data[1] ^= 0x40; + } + /* + * If report contains fragmented high-resolution pen + * coordinates + */ + if (size >= 10 && pen->fragmented_hires) { + u8 pressure_low_byte; + u8 pressure_high_byte; + + /* Lift pressure bytes */ + pressure_low_byte = data[6]; + pressure_high_byte = data[7]; + /* + * Move Y coord to make space for high-order X + * coord byte + */ + data[6] = data[5]; + data[5] = data[4]; + /* Move high-order X coord byte */ + data[4] = data[8]; + /* Move high-order Y coord byte */ + data[7] = data[9]; + /* Place pressure bytes */ + data[8] = pressure_low_byte; + data[9] = pressure_high_byte; + } + /* If we need to emulate in-range detection */ + if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { + /* Set in-range bit */ + data[1] |= 0x40; + /* (Re-)start in-range timeout */ + mod_timer(&drvdata->inrange_timer, + jiffies + msecs_to_jiffies(100)); + } + /* If we report tilt and Y direction is flipped */ + if (size >= 12 && pen->tilt_y_flipped) + data[11] = -data[11]; + + return 0; +} + +/** + * uclogic_raw_event_frame - handle raw frame events (frame HID reports). + * + * @drvdata: Driver data. + * @frame: The parameters of the frame controls to handle. + * @data: Report data buffer, can be modified. + * @size: Report data size, bytes. + * + * Returns: + * Negative value on error (stops event delivery), zero for success. + */ +static int uclogic_raw_event_frame( + struct uclogic_drvdata *drvdata, + const struct uclogic_params_frame *frame, + u8 *data, int size) +{ + WARN_ON(drvdata == NULL); + WARN_ON(data == NULL && size != 0); + + /* If need to, and can, set pad device ID for Wacom drivers */ + if (frame->dev_id_byte > 0 && frame->dev_id_byte < size) { + data[frame->dev_id_byte] = 0xf; + } + /* If need to, and can, read rotary encoder state change */ + if (frame->re_lsb > 0 && frame->re_lsb / 8 < size) { + unsigned int byte = frame->re_lsb / 8; + unsigned int bit = frame->re_lsb % 8; + + u8 change; + u8 prev_state = drvdata->re_state; + /* Read Gray-coded state */ + u8 state = (data[byte] >> bit) & 0x3; + /* Encode state change into 2-bit signed integer */ + if ((prev_state == 1 && state == 0) || + (prev_state == 2 && state == 3)) { + change = 1; + } else if ((prev_state == 2 && state == 0) || + (prev_state == 1 && state == 3)) { + change = 3; + } else { + change = 0; + } + /* Write change */ + data[byte] = (data[byte] & ~((u8)3 << bit)) | + (change << bit); + /* Remember state */ + drvdata->re_state = state; + } + + return 0; +} + static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { + unsigned int report_id = report->id; struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_params *params = &drvdata->params; + struct uclogic_params_pen_subreport *subreport; + struct uclogic_params_pen_subreport *subreport_list_end; + size_t i; - /* Tweak pen reports, if necessary */ - if (!params->pen_unused && - (report->type == HID_INPUT_REPORT) && - (report->id == params->pen.id) && - (size >= 2)) { - /* If it's the "virtual" frame controls report */ - if (params->frame.id != 0 && - data[1] & params->pen_frame_flag) { - /* Change to virtual frame controls report ID */ - data[0] = params->frame.id; - return 0; - } - /* If in-range reports are inverted */ - if (params->pen.inrange == - UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { - /* Invert the in-range bit */ - data[1] ^= 0x40; - } - /* - * If report contains fragmented high-resolution pen - * coordinates - */ - if (size >= 10 && params->pen.fragmented_hires) { - u8 pressure_low_byte; - u8 pressure_high_byte; + /* Do not handle anything but input reports */ + if (report->type != HID_INPUT_REPORT) + return 0; - /* Lift pressure bytes */ - pressure_low_byte = data[6]; - pressure_high_byte = data[7]; - /* - * Move Y coord to make space for high-order X - * coord byte - */ - data[6] = data[5]; - data[5] = data[4]; - /* Move high-order X coord byte */ - data[4] = data[8]; - /* Move high-order Y coord byte */ - data[7] = data[9]; - /* Place pressure bytes */ - data[8] = pressure_low_byte; - data[9] = pressure_high_byte; - } - /* If we need to emulate in-range detection */ - if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { - /* Set in-range bit */ - data[1] |= 0x40; - /* (Re-)start in-range timeout */ - mod_timer(&drvdata->inrange_timer, - jiffies + msecs_to_jiffies(100)); - } - } - - /* Tweak frame control reports, if necessary */ - if ((report->type == HID_INPUT_REPORT) && - (report->id == params->frame.id)) { - /* If need to, and can, set pad device ID for Wacom drivers */ - if (params->frame.dev_id_byte > 0 && - params->frame.dev_id_byte < size) { - data[params->frame.dev_id_byte] = 0xf; - } - /* If need to, and can, read rotary encoder state change */ - if (params->frame.re_lsb > 0 && - params->frame.re_lsb / 8 < size) { - unsigned int byte = params->frame.re_lsb / 8; - unsigned int bit = params->frame.re_lsb % 8; - - u8 change; - u8 prev_state = drvdata->re_state; - /* Read Gray-coded state */ - u8 state = (data[byte] >> bit) & 0x3; - /* Encode state change into 2-bit signed integer */ - if ((prev_state == 1 && state == 0) || - (prev_state == 2 && state == 3)) { - change = 1; - } else if ((prev_state == 2 && state == 0) || - (prev_state == 1 && state == 3)) { - change = 3; - } else { - change = 0; + while (true) { + /* Tweak pen reports, if necessary */ + if ((report_id == params->pen.id) && (size >= 2)) { + subreport_list_end = + params->pen.subreport_list + + ARRAY_SIZE(params->pen.subreport_list); + /* Try to match a subreport */ + for (subreport = params->pen.subreport_list; + subreport < subreport_list_end; subreport++) { + if (subreport->value != 0 && + subreport->value == data[1]) { + break; + } + } + /* If a subreport matched */ + if (subreport < subreport_list_end) { + /* Change to subreport ID, and restart */ + report_id = data[0] = subreport->id; + continue; + } else { + return uclogic_raw_event_pen(drvdata, data, size); } - /* Write change */ - data[byte] = (data[byte] & ~((u8)3 << bit)) | - (change << bit); - /* Remember state */ - drvdata->re_state = state; } + + /* Tweak frame control reports, if necessary */ + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + if (report_id == params->frame_list[i].id) { + return uclogic_raw_event_frame( + drvdata, ¶ms->frame_list[i], + data, size); + } + } + + break; } return 0; @@ -373,7 +426,7 @@ static const struct hid_device_id uclogic_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, { HID_USB_DEVICE(USB_VENDOR_ID_HUION, - USB_DEVICE_ID_HUION_HS64) }, + USB_DEVICE_ID_HUION_TABLET2) }, { HID_USB_DEVICE(USB_VENDOR_ID_TRUST, USB_DEVICE_ID_TRUST_PANORA_TABLET) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, @@ -415,7 +468,6 @@ static struct hid_driver uclogic_driver = { .remove = uclogic_remove, .report_fixup = uclogic_report_fixup, .raw_event = uclogic_raw_event, - .input_mapping = uclogic_input_mapping, .input_configured = uclogic_input_configured, #ifdef CONFIG_PM .resume = uclogic_resume, diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 3e70f969fb84..5f50ceb875d6 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -207,8 +207,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen, * Generate pen report descriptor */ desc_ptr = uclogic_rdesc_template_apply( - uclogic_rdesc_pen_v1_template_arr, - uclogic_rdesc_pen_v1_template_size, + uclogic_rdesc_v1_pen_template_arr, + uclogic_rdesc_v1_pen_template_size, desc_params, ARRAY_SIZE(desc_params)); if (desc_ptr == NULL) { rc = -ENOMEM; @@ -221,8 +221,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen, memset(pen, 0, sizeof(*pen)); pen->desc_ptr = desc_ptr; desc_ptr = NULL; - pen->desc_size = uclogic_rdesc_pen_v1_template_size; - pen->id = UCLOGIC_RDESC_PEN_V1_ID; + pen->desc_size = uclogic_rdesc_v1_pen_template_size; + pen->id = UCLOGIC_RDESC_V1_PEN_ID; pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED; found = true; finish: @@ -351,8 +351,8 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, * Generate pen report descriptor */ desc_ptr = uclogic_rdesc_template_apply( - uclogic_rdesc_pen_v2_template_arr, - uclogic_rdesc_pen_v2_template_size, + uclogic_rdesc_v2_pen_template_arr, + uclogic_rdesc_v2_pen_template_size, desc_params, ARRAY_SIZE(desc_params)); if (desc_ptr == NULL) { rc = -ENOMEM; @@ -365,10 +365,11 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, memset(pen, 0, sizeof(*pen)); pen->desc_ptr = desc_ptr; desc_ptr = NULL; - pen->desc_size = uclogic_rdesc_pen_v2_template_size; - pen->id = UCLOGIC_RDESC_PEN_V2_ID; + pen->desc_size = uclogic_rdesc_v2_pen_template_size; + pen->id = UCLOGIC_RDESC_V2_PEN_ID; pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE; pen->fragmented_hires = true; + pen->tilt_y_flipped = true; found = true; finish: *pfound = found; @@ -430,8 +431,8 @@ static int uclogic_params_frame_init_with_desc( } /** - * uclogic_params_frame_init_v1_buttonpad() - initialize abstract buttonpad - * on a v1 tablet interface. + * uclogic_params_frame_init_v1() - initialize v1 tablet interface frame + * controls. * * @frame: Pointer to the frame parameters to initialize (to be cleaned * up with uclogic_params_frame_cleanup()). Not modified in case @@ -445,8 +446,7 @@ static int uclogic_params_frame_init_with_desc( * Returns: * Zero, if successful. A negative errno code on error. */ -static int uclogic_params_frame_init_v1_buttonpad( - struct uclogic_params_frame *frame, +static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame, bool *pfound, struct hid_device *hdev) { @@ -487,9 +487,9 @@ static int uclogic_params_frame_init_v1_buttonpad( hid_dbg(hdev, "generic buttons enabled\n"); rc = uclogic_params_frame_init_with_desc( frame, - uclogic_rdesc_buttonpad_v1_arr, - uclogic_rdesc_buttonpad_v1_size, - UCLOGIC_RDESC_BUTTONPAD_V1_ID); + uclogic_rdesc_v1_frame_arr, + uclogic_rdesc_v1_frame_size, + UCLOGIC_RDESC_V1_FRAME_ID); if (rc != 0) goto cleanup; found = true; @@ -512,10 +512,12 @@ static int uclogic_params_frame_init_v1_buttonpad( void uclogic_params_cleanup(struct uclogic_params *params) { if (!params->invalid) { + size_t i; kfree(params->desc_ptr); - if (!params->pen_unused) - uclogic_params_pen_cleanup(¶ms->pen); - uclogic_params_frame_cleanup(¶ms->frame); + uclogic_params_pen_cleanup(¶ms->pen); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) + uclogic_params_frame_cleanup(¶ms->frame_list[i]); + memset(params, 0, sizeof(*params)); } } @@ -543,60 +545,53 @@ int uclogic_params_get_desc(const struct uclogic_params *params, __u8 **pdesc, unsigned int *psize) { - bool common_present; - bool pen_present; - bool frame_present; - unsigned int size; + int rc = -ENOMEM; + bool present = false; + unsigned int size = 0; __u8 *desc = NULL; + size_t i; /* Check arguments */ if (params == NULL || pdesc == NULL || psize == NULL) return -EINVAL; - size = 0; + /* Concatenate descriptors */ +#define ADD_DESC(_desc_ptr, _desc_size) \ + do { \ + unsigned int new_size; \ + __u8 *new_desc; \ + if ((_desc_ptr) == NULL) { \ + break; \ + } \ + new_size = size + (_desc_size); \ + new_desc = krealloc(desc, new_size, GFP_KERNEL); \ + if (new_desc == NULL) { \ + goto cleanup; \ + } \ + memcpy(new_desc + size, (_desc_ptr), (_desc_size)); \ + desc = new_desc; \ + size = new_size; \ + present = true; \ + } while (0) - common_present = (params->desc_ptr != NULL); - pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL); - frame_present = (params->frame.desc_ptr != NULL); - - if (common_present) - size += params->desc_size; - if (pen_present) - size += params->pen.desc_size; - if (frame_present) - size += params->frame.desc_size; - - if (common_present || pen_present || frame_present) { - __u8 *p; - - desc = kmalloc(size, GFP_KERNEL); - if (desc == NULL) - return -ENOMEM; - p = desc; - - if (common_present) { - memcpy(p, params->desc_ptr, - params->desc_size); - p += params->desc_size; - } - if (pen_present) { - memcpy(p, params->pen.desc_ptr, - params->pen.desc_size); - p += params->pen.desc_size; - } - if (frame_present) { - memcpy(p, params->frame.desc_ptr, - params->frame.desc_size); - p += params->frame.desc_size; - } - - WARN_ON(p != desc + size); - - *psize = size; + ADD_DESC(params->desc_ptr, params->desc_size); + ADD_DESC(params->pen.desc_ptr, params->pen.desc_size); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + ADD_DESC(params->frame_list[i].desc_ptr, + params->frame_list[i].desc_size); } - *pdesc = desc; - return 0; +#undef ADD_DESC + + if (present) { + *pdesc = desc; + *psize = size; + desc = NULL; + } + rc = 0; +cleanup: + kfree(desc); + return rc; } /** @@ -679,21 +674,6 @@ static int uclogic_params_init_with_opt_desc(struct uclogic_params *params, return rc; } -/** - * uclogic_params_init_with_pen_unused() - initialize tablet interface - * parameters preserving original reports and generic HID processing, but - * disabling pen usage. - * - * @params: Parameters to initialize (to be cleaned with - * uclogic_params_cleanup()). Not modified in case of - * error. Cannot be NULL. - */ -static void uclogic_params_init_with_pen_unused(struct uclogic_params *params) -{ - memset(params, 0, sizeof(*params)); - params->pen_unused = true; -} - /** * uclogic_params_huion_init() - initialize a Huion tablet interface and discover * its parameters. @@ -733,8 +713,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, /* If it's not a pen interface */ if (bInterfaceNumber != 0) { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); goto output; } @@ -766,20 +745,22 @@ static int uclogic_params_huion_init(struct uclogic_params *params, goto cleanup; } else if (found) { hid_dbg(hdev, "pen v2 parameters found\n"); - /* Create v2 buttonpad parameters */ + /* Create v2 frame parameters */ rc = uclogic_params_frame_init_with_desc( - &p.frame, - uclogic_rdesc_buttonpad_v2_arr, - uclogic_rdesc_buttonpad_v2_size, - UCLOGIC_RDESC_BUTTONPAD_V2_ID); + &p.frame_list[0], + uclogic_rdesc_v2_frame_arr, + uclogic_rdesc_v2_frame_size, + UCLOGIC_RDESC_V2_FRAME_ID); if (rc != 0) { hid_err(hdev, - "failed creating v2 buttonpad parameters: %d\n", + "failed creating v2 frame parameters: %d\n", rc); goto cleanup; } - /* Set bitmask marking frame reports in pen reports */ - p.pen_frame_flag = 0x20; + /* Link frame button subreports from pen reports */ + p.pen.subreport_list[0].value = 0xe0; + p.pen.subreport_list[0].id = + UCLOGIC_RDESC_V2_FRAME_ID; goto output; } hid_dbg(hdev, "pen v2 parameters not found\n"); @@ -793,19 +774,20 @@ static int uclogic_params_huion_init(struct uclogic_params *params, goto cleanup; } else if (found) { hid_dbg(hdev, "pen v1 parameters found\n"); - /* Try to probe v1 buttonpad */ - rc = uclogic_params_frame_init_v1_buttonpad( - &p.frame, - &found, hdev); + /* Try to probe v1 frame */ + rc = uclogic_params_frame_init_v1(&p.frame_list[0], + &found, hdev); if (rc != 0) { - hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc); + hid_err(hdev, "v1 frame probing failed: %d\n", rc); goto cleanup; } - hid_dbg(hdev, "buttonpad v1 parameters%s found\n", + hid_dbg(hdev, "frame v1 parameters%s found\n", (found ? "" : " not")); if (found) { - /* Set bitmask marking frame reports */ - p.pen_frame_flag = 0x20; + /* Link frame button subreports from pen reports */ + p.pen.subreport_list[0].value = 0xe0; + p.pen.subreport_list[0].id = + UCLOGIC_RDESC_V1_FRAME_ID; } goto output; } @@ -992,7 +974,7 @@ int uclogic_params_init(struct uclogic_params *params, case VID_PID(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET): case VID_PID(USB_VENDOR_ID_HUION, - USB_DEVICE_ID_HUION_HS64): + USB_DEVICE_ID_HUION_TABLET2): case VID_PID(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET): case VID_PID(USB_VENDOR_ID_UCLOGIC, @@ -1032,8 +1014,7 @@ int uclogic_params_init(struct uclogic_params *params, uclogic_params_init_invalid(&p); } } else { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); } break; case VID_PID(USB_VENDOR_ID_UGEE, @@ -1048,15 +1029,14 @@ int uclogic_params_init(struct uclogic_params *params, } /* Initialize frame parameters */ rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_xppen_deco01_frame_arr, uclogic_rdesc_xppen_deco01_frame_size, 0); if (rc != 0) goto cleanup; } else { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); } break; case VID_PID(USB_VENDOR_ID_TRUST, @@ -1075,19 +1055,19 @@ int uclogic_params_init(struct uclogic_params *params, goto cleanup; } else if (found) { rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_ugee_g5_frame_arr, uclogic_rdesc_ugee_g5_frame_size, UCLOGIC_RDESC_UGEE_G5_FRAME_ID); if (rc != 0) { hid_err(hdev, - "failed creating buttonpad parameters: %d\n", + "failed creating frame parameters: %d\n", rc); goto cleanup; } - p.frame.re_lsb = + p.frame_list[0].re_lsb = UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB; - p.frame.dev_id_byte = + p.frame_list[0].dev_id_byte = UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE; } else { hid_warn(hdev, "pen parameters not found"); @@ -1109,13 +1089,13 @@ int uclogic_params_init(struct uclogic_params *params, goto cleanup; } else if (found) { rc = uclogic_params_frame_init_with_desc( - &p.frame, - uclogic_rdesc_ugee_ex07_buttonpad_arr, - uclogic_rdesc_ugee_ex07_buttonpad_size, + &p.frame_list[0], + uclogic_rdesc_ugee_ex07_frame_arr, + uclogic_rdesc_ugee_ex07_frame_size, 0); if (rc != 0) { hid_err(hdev, - "failed creating buttonpad parameters: %d\n", + "failed creating frame parameters: %d\n", rc); goto cleanup; } diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index ba48b1c7a0e5..86f616dfbb53 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -33,6 +33,25 @@ enum uclogic_params_pen_inrange { extern const char *uclogic_params_pen_inrange_to_str( enum uclogic_params_pen_inrange inrange); + +/* + * Pen report's subreport data. + */ +struct uclogic_params_pen_subreport { + /* + * The value of the second byte of the pen report indicating this + * subreport. If zero, the subreport should be considered invalid and + * not matched. + */ + __u8 value; + + /* + * The ID to be assigned to the report, if the second byte of the pen + * report is equal to "value". Only valid if "value" is not zero. + */ + __u8 id; +}; + /* * Tablet interface's pen input parameters. * @@ -54,6 +73,8 @@ struct uclogic_params_pen { unsigned int desc_size; /* Report ID, if reports should be tweaked, zero if not */ unsigned int id; + /* The list of subreports */ + struct uclogic_params_pen_subreport subreport_list[1]; /* Type of in-range reporting, only valid if "id" is not zero */ enum uclogic_params_pen_inrange inrange; /* @@ -62,6 +83,12 @@ struct uclogic_params_pen { * Only valid if "id" is not zero. */ bool fragmented_hires; + /* + * True if the pen reports tilt in bytes at offset 10 (X) and 11 (Y), + * and the Y tilt direction is flipped. + * Only valid if "id" is not zero. + */ + bool tilt_y_flipped; }; /* @@ -132,28 +159,16 @@ struct uclogic_params { * Only valid, if "desc_ptr" is not NULL. */ unsigned int desc_size; - /* - * True, if pen usage in report descriptor is invalid, when present. - * Only valid, if "invalid" is false. - */ - bool pen_unused; /* * Pen parameters and optional report descriptor part. - * Only valid if "pen_unused" is valid and false. + * Only valid, if "invalid" is false. */ struct uclogic_params_pen pen; /* - * Frame control parameters and optional report descriptor part. - * Only valid, if "invalid" is false. + * The list of frame control parameters and optional report descriptor + * parts. Only valid, if "invalid" is false. */ - struct uclogic_params_frame frame; - /* - * Bitmask matching frame controls "sub-report" flag in the second - * byte of the pen report, or zero if it's not expected. - * Only valid if both "pen" and "frame" are valid, and "frame.id" is - * not zero. - */ - __u8 pen_frame_flag; + struct uclogic_params_frame frame_list[1]; }; /* Initialize a tablet interface and discover its parameters */ @@ -162,39 +177,40 @@ extern int uclogic_params_init(struct uclogic_params *params, /* Tablet interface parameters *printf format string */ #define UCLOGIC_PARAMS_FMT_STR \ - ".invalid = %s\n" \ - ".desc_ptr = %p\n" \ - ".desc_size = %u\n" \ - ".pen_unused = %s\n" \ - ".pen.desc_ptr = %p\n" \ - ".pen.desc_size = %u\n" \ - ".pen.id = %u\n" \ - ".pen.inrange = %s\n" \ - ".pen.fragmented_hires = %s\n" \ - ".frame.desc_ptr = %p\n" \ - ".frame.desc_size = %u\n" \ - ".frame.id = %u\n" \ - ".frame.re_lsb = %u\n" \ - ".frame.dev_id_byte = %u\n" \ - ".pen_frame_flag = 0x%02x\n" + ".invalid = %s\n" \ + ".desc_ptr = %p\n" \ + ".desc_size = %u\n" \ + ".pen.desc_ptr = %p\n" \ + ".pen.desc_size = %u\n" \ + ".pen.id = %u\n" \ + ".pen.subreport_list[0] = {0x%02hhx, %hhu}\n" \ + ".pen.inrange = %s\n" \ + ".pen.fragmented_hires = %s\n" \ + ".pen.tilt_y_flipped = %s\n" \ + ".frame_list[0].desc_ptr = %p\n" \ + ".frame_list[0].desc_size = %u\n" \ + ".frame_list[0].id = %u\n" \ + ".frame_list[0].re_lsb = %u\n" \ + ".frame_list[0].dev_id_byte = %u\n" /* Tablet interface parameters *printf format arguments */ #define UCLOGIC_PARAMS_FMT_ARGS(_params) \ ((_params)->invalid ? "true" : "false"), \ (_params)->desc_ptr, \ (_params)->desc_size, \ - ((_params)->pen_unused ? "true" : "false"), \ (_params)->pen.desc_ptr, \ (_params)->pen.desc_size, \ (_params)->pen.id, \ + (_params)->pen.subreport_list[0].value, \ + (_params)->pen.subreport_list[0].id, \ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ ((_params)->pen.fragmented_hires ? "true" : "false"), \ - (_params)->frame.desc_ptr, \ - (_params)->frame.desc_size, \ - (_params)->frame.id, \ - (_params)->frame.re_lsb, \ - (_params)->frame.dev_id_byte, \ - (_params)->pen_frame_flag + ((_params)->pen.tilt_y_flipped ? "true" : "false"), \ + (_params)->frame_list[0].desc_ptr, \ + (_params)->frame_list[0].desc_size, \ + (_params)->frame_list[0].id, \ + (_params)->frame_list[0].re_lsb, \ + (_params)->frame_list[0].dev_id_byte /* Get a replacement report descriptor for a tablet's interface. */ extern int uclogic_params_get_desc(const struct uclogic_params *params, diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 6dd6dcd09c8b..04644d93bd11 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -532,7 +532,7 @@ const size_t uclogic_rdesc_twha60_fixed1_size = sizeof(uclogic_rdesc_twha60_fixed1_arr); /* Fixed report descriptor template for (tweaked) v1 pen reports */ -const __u8 uclogic_rdesc_pen_v1_template_arr[] = { +const __u8 uclogic_rdesc_v1_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -582,11 +582,11 @@ const __u8 uclogic_rdesc_pen_v1_template_arr[] = { 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_pen_v1_template_size = - sizeof(uclogic_rdesc_pen_v1_template_arr); +const size_t uclogic_rdesc_v1_pen_template_size = + sizeof(uclogic_rdesc_v1_pen_template_arr); /* Fixed report descriptor template for (tweaked) v2 pen reports */ -const __u8 uclogic_rdesc_pen_v2_template_arr[] = { +const __u8 uclogic_rdesc_v2_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -633,25 +633,35 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = { 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM), /* Logical Maximum (PLACEHOLDER), */ 0x81, 0x02, /* Input (Variable), */ - 0x81, 0x03, /* Input (Constant, Variable), */ + 0x54, /* Unit Exponent (0), */ + 0x65, 0x14, /* Unit (Degrees), */ + 0x35, 0xC4, /* Physical Minimum (-60), */ + 0x45, 0x3C, /* Physical Maximum (60), */ + 0x15, 0xC4, /* Logical Minimum (-60), */ + 0x25, 0x3C, /* Logical Maximum (60), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x09, 0x3D, /* Usage (X Tilt), */ + 0x09, 0x3E, /* Usage (Y Tilt), */ + 0x81, 0x02, /* Input (Variable), */ 0xC0, /* End Collection, */ 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_pen_v2_template_size = - sizeof(uclogic_rdesc_pen_v2_template_arr); +const size_t uclogic_rdesc_v2_pen_template_size = + sizeof(uclogic_rdesc_v2_pen_template_arr); /* - * Expand to the contents of a generic buttonpad report descriptor. + * Expand to the contents of a generic frame report descriptor. * - * @_padding: Padding from the end of button bits at bit 44, until - * the end of the report, in bits. + * @_id: The report ID to use. + * @_size: Size of the report to pad to, including report ID, bytes. */ -#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_padding) \ +#define UCLOGIC_RDESC_FRAME_BYTES(_id, _size) \ 0x05, 0x01, /* Usage Page (Desktop), */ \ 0x09, 0x07, /* Usage (Keypad), */ \ 0xA1, 0x01, /* Collection (Application), */ \ - 0x85, 0xF7, /* Report ID (247), */ \ + 0x85, (_id), /* Report ID (_id), */ \ 0x14, /* Logical Minimum (0), */ \ 0x25, 0x01, /* Logical Maximum (1), */ \ 0x75, 0x01, /* Report Size (1), */ \ @@ -679,30 +689,31 @@ const size_t uclogic_rdesc_pen_v2_template_size = 0xA0, /* Collection (Physical), */ \ 0x05, 0x09, /* Usage Page (Button), */ \ 0x19, 0x01, /* Usage Minimum (01h), */ \ - 0x29, 0x02, /* Usage Maximum (02h), */ \ - 0x95, 0x02, /* Report Count (2), */ \ + 0x29, 0x03, /* Usage Maximum (03h), */ \ + 0x95, 0x03, /* Report Count (3), */ \ 0x81, 0x02, /* Input (Variable), */ \ - 0x95, _padding, /* Report Count (_padding), */ \ + 0x95, ((_size) * 8 - 45), \ + /* Report Count (padding), */ \ 0x81, 0x01, /* Input (Constant), */ \ 0xC0, /* End Collection, */ \ 0xC0 /* End Collection */ -/* Fixed report descriptor for (tweaked) v1 buttonpad reports */ -const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(20) +/* Fixed report descriptor for (tweaked) v1 frame reports */ +const __u8 uclogic_rdesc_v1_frame_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V1_FRAME_ID, 8) }; -const size_t uclogic_rdesc_buttonpad_v1_size = - sizeof(uclogic_rdesc_buttonpad_v1_arr); +const size_t uclogic_rdesc_v1_frame_size = + sizeof(uclogic_rdesc_v1_frame_arr); -/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ -const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(52) +/* Fixed report descriptor for (tweaked) v2 frame reports */ +const __u8 uclogic_rdesc_v2_frame_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V2_FRAME_ID, 12) }; -const size_t uclogic_rdesc_buttonpad_v2_size = - sizeof(uclogic_rdesc_buttonpad_v2_arr); +const size_t uclogic_rdesc_v2_frame_size = + sizeof(uclogic_rdesc_v2_frame_arr); -/* Fixed report descriptor for Ugee EX07 buttonpad */ -const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = { +/* Fixed report descriptor for Ugee EX07 frame */ +const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = { 0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x07, /* Usage (Keypad), */ 0xA1, 0x01, /* Collection (Application), */ @@ -725,8 +736,8 @@ const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = { 0xC0, /* End Collection, */ 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_ugee_ex07_buttonpad_size = - sizeof(uclogic_rdesc_ugee_ex07_buttonpad_arr); +const size_t uclogic_rdesc_ugee_ex07_frame_size = + sizeof(uclogic_rdesc_ugee_ex07_frame_arr); /* Fixed report descriptor for Ugee G5 frame controls */ const __u8 uclogic_rdesc_ugee_g5_frame_arr[] = { diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index c5da51055af3..3d904c27b86a 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -104,36 +104,36 @@ enum uclogic_rdesc_pen_ph_id { UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID /* Report ID for v1 pen reports */ -#define UCLOGIC_RDESC_PEN_V1_ID 0x07 +#define UCLOGIC_RDESC_V1_PEN_ID 0x07 /* Fixed report descriptor template for (tweaked) v1 pen reports */ -extern const __u8 uclogic_rdesc_pen_v1_template_arr[]; -extern const size_t uclogic_rdesc_pen_v1_template_size; +extern const __u8 uclogic_rdesc_v1_pen_template_arr[]; +extern const size_t uclogic_rdesc_v1_pen_template_size; /* Report ID for v2 pen reports */ -#define UCLOGIC_RDESC_PEN_V2_ID 0x08 +#define UCLOGIC_RDESC_V2_PEN_ID 0x08 /* Fixed report descriptor template for (tweaked) v2 pen reports */ -extern const __u8 uclogic_rdesc_pen_v2_template_arr[]; -extern const size_t uclogic_rdesc_pen_v2_template_size; +extern const __u8 uclogic_rdesc_v2_pen_template_arr[]; +extern const size_t uclogic_rdesc_v2_pen_template_size; -/* Fixed report descriptor for (tweaked) v1 buttonpad reports */ -extern const __u8 uclogic_rdesc_buttonpad_v1_arr[]; -extern const size_t uclogic_rdesc_buttonpad_v1_size; +/* Report ID for tweaked v1 frame reports */ +#define UCLOGIC_RDESC_V1_FRAME_ID 0xf7 -/* Report ID for tweaked v1 buttonpad reports */ -#define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7 +/* Fixed report descriptor for (tweaked) v1 frame reports */ +extern const __u8 uclogic_rdesc_v1_frame_arr[]; +extern const size_t uclogic_rdesc_v1_frame_size; -/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ -extern const __u8 uclogic_rdesc_buttonpad_v2_arr[]; -extern const size_t uclogic_rdesc_buttonpad_v2_size; +/* Report ID for tweaked v2 frame reports */ +#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7 -/* Report ID for tweaked v2 buttonpad reports */ -#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7 +/* Fixed report descriptor for (tweaked) v2 frame reports */ +extern const __u8 uclogic_rdesc_v2_frame_arr[]; +extern const size_t uclogic_rdesc_v2_frame_size; -/* Fixed report descriptor for Ugee EX07 buttonpad */ -extern const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[]; -extern const size_t uclogic_rdesc_ugee_ex07_buttonpad_size; +/* Fixed report descriptor for Ugee EX07 frame */ +extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; +extern const size_t uclogic_rdesc_ugee_ex07_frame_size; /* Fixed report descriptor for XP-Pen Deco 01 frame controls */ extern const __u8 uclogic_rdesc_xppen_deco01_frame_arr[]; diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 6726567d7297..c078f09a2318 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "../hid-ids.h" #include "i2c-hid.h" @@ -47,6 +48,15 @@ #define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6) #define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7) +/* Command opcodes */ +#define I2C_HID_OPCODE_RESET 0x01 +#define I2C_HID_OPCODE_GET_REPORT 0x02 +#define I2C_HID_OPCODE_SET_REPORT 0x03 +#define I2C_HID_OPCODE_GET_IDLE 0x04 +#define I2C_HID_OPCODE_SET_IDLE 0x05 +#define I2C_HID_OPCODE_GET_PROTOCOL 0x06 +#define I2C_HID_OPCODE_SET_PROTOCOL 0x07 +#define I2C_HID_OPCODE_SET_POWER 0x08 /* flags */ #define I2C_HID_STARTED 0 @@ -84,60 +94,11 @@ struct i2c_hid_desc { __le32 reserved; } __packed; -struct i2c_hid_cmd { - unsigned int registerIndex; - __u8 opcode; - unsigned int length; - bool wait; -}; - -union command { - u8 data[0]; - struct cmd { - __le16 reg; - __u8 reportTypeID; - __u8 opcode; - } __packed c; -}; - -#define I2C_HID_CMD(opcode_) \ - .opcode = opcode_, .length = 4, \ - .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) - -/* fetch HID descriptor */ -static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 }; -/* fetch report descriptors */ -static const struct i2c_hid_cmd hid_report_descr_cmd = { - .registerIndex = offsetof(struct i2c_hid_desc, - wReportDescRegister), - .opcode = 0x00, - .length = 2 }; -/* commands */ -static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01), - .wait = true }; -static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; -static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) }; -static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) }; -static const struct i2c_hid_cmd hid_no_cmd = { .length = 0 }; - -/* - * These definitions are not used here, but are defined by the spec. - * Keeping them here for documentation purposes. - * - * static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) }; - * static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) }; - * static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) }; - * static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) }; - */ - /* The main device structure */ struct i2c_hid { struct i2c_client *client; /* i2c client */ struct hid_device *hid; /* pointer to corresponding HID dev */ - union { - __u8 hdesc_buffer[sizeof(struct i2c_hid_desc)]; - struct i2c_hid_desc hdesc; /* the HID Descriptor */ - }; + struct i2c_hid_desc hdesc; /* the HID Descriptor */ __le16 wHIDDescRegister; /* location of the i2c * register of the HID * descriptor. */ @@ -145,7 +106,6 @@ struct i2c_hid { u8 *inbuf; /* Input buffer */ u8 *rawbuf; /* Raw Input buffer */ u8 *cmdbuf; /* Command buffer */ - u8 *argsbuf; /* Command arguments buffer */ unsigned long flags; /* device flags */ unsigned long quirks; /* Various quirks */ @@ -207,196 +167,228 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct) return quirks; } -static int __i2c_hid_command(struct i2c_client *client, - const struct i2c_hid_cmd *command, u8 reportID, - u8 reportType, u8 *args, int args_len, - unsigned char *buf_recv, int data_len) +static int i2c_hid_xfer(struct i2c_hid *ihid, + u8 *send_buf, int send_len, u8 *recv_buf, int recv_len) { - struct i2c_hid *ihid = i2c_get_clientdata(client); - union command *cmd = (union command *)ihid->cmdbuf; + struct i2c_client *client = ihid->client; + struct i2c_msg msgs[2] = { 0 }; + int n = 0; int ret; - struct i2c_msg msg[2]; - int msg_num = 1; - int length = command->length; - bool wait = command->wait; - unsigned int registerIndex = command->registerIndex; + if (send_len) { + i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", + __func__, send_len, send_buf); - /* special case for hid_descr_cmd */ - if (command == &hid_descr_cmd) { - cmd->c.reg = ihid->wHIDDescRegister; - } else { - cmd->data[0] = ihid->hdesc_buffer[registerIndex]; - cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1]; + msgs[n].addr = client->addr; + msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_DMA_SAFE; + msgs[n].len = send_len; + msgs[n].buf = send_buf; + n++; } - if (length > 2) { - cmd->c.opcode = command->opcode; - cmd->c.reportTypeID = reportID | reportType << 4; - } + if (recv_len) { + msgs[n].addr = client->addr; + msgs[n].flags = (client->flags & I2C_M_TEN) | + I2C_M_RD | I2C_M_DMA_SAFE; + msgs[n].len = recv_len; + msgs[n].buf = recv_buf; + n++; - memcpy(cmd->data + length, args, args_len); - length += args_len; - - i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data); - - msg[0].addr = client->addr; - msg[0].flags = client->flags & I2C_M_TEN; - msg[0].len = length; - msg[0].buf = cmd->data; - if (data_len > 0) { - msg[1].addr = client->addr; - msg[1].flags = client->flags & I2C_M_TEN; - msg[1].flags |= I2C_M_RD; - msg[1].len = data_len; - msg[1].buf = buf_recv; - msg_num = 2; set_bit(I2C_HID_READ_PENDING, &ihid->flags); } - if (wait) - set_bit(I2C_HID_RESET_PENDING, &ihid->flags); + ret = i2c_transfer(client->adapter, msgs, n); - ret = i2c_transfer(client->adapter, msg, msg_num); - - if (data_len > 0) + if (recv_len) clear_bit(I2C_HID_READ_PENDING, &ihid->flags); - if (ret != msg_num) + if (ret != n) return ret < 0 ? ret : -EIO; - ret = 0; - - if (wait && (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET)) { - msleep(100); - } else if (wait) { - i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); - if (!wait_event_timeout(ihid->wait, - !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), - msecs_to_jiffies(5000))) - ret = -ENODATA; - i2c_hid_dbg(ihid, "%s: finished.\n", __func__); - } - - return ret; -} - -static int i2c_hid_command(struct i2c_client *client, - const struct i2c_hid_cmd *command, - unsigned char *buf_recv, int data_len) -{ - return __i2c_hid_command(client, command, 0, 0, NULL, 0, - buf_recv, data_len); -} - -static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, - u8 reportID, unsigned char *buf_recv, int data_len) -{ - struct i2c_hid *ihid = i2c_get_clientdata(client); - u8 args[3]; - int ret; - int args_len = 0; - u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister); - - i2c_hid_dbg(ihid, "%s\n", __func__); - - if (reportID >= 0x0F) { - args[args_len++] = reportID; - reportID = 0x0F; - } - - args[args_len++] = readRegister & 0xFF; - args[args_len++] = readRegister >> 8; - - ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID, - reportType, args, args_len, buf_recv, data_len); - if (ret) { - dev_err(&client->dev, - "failed to retrieve report from device.\n"); - return ret; - } - return 0; } +static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg, + void *buf, size_t len) +{ + *(__le16 *)ihid->cmdbuf = reg; + + return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len); +} + +static size_t i2c_hid_encode_command(u8 *buf, u8 opcode, + int report_type, int report_id) +{ + size_t length = 0; + + if (report_id < 0x0F) { + buf[length++] = report_type << 4 | report_id; + buf[length++] = opcode; + } else { + buf[length++] = report_type << 4 | 0x0F; + buf[length++] = opcode; + buf[length++] = report_id; + } + + return length; +} + +static int i2c_hid_get_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + u8 *recv_buf, size_t recv_len) +{ + size_t length = 0; + size_t ret_count; + int error; + + i2c_hid_dbg(ihid, "%s\n", __func__); + + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is GET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_GET_REPORT, + report_type, report_id); + /* + * Device will send report data through data register. Because + * command can be either 2 or 3 bytes destination for the data + * register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); + + /* + * In addition to report data device will supply data length + * in the first 2 bytes of the response, so adjust . + */ + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, + ihid->rawbuf, recv_len + sizeof(__le16)); + if (error) { + dev_err(&ihid->client->dev, + "failed to set a report to device: %d\n", error); + return error; + } + + /* The buffer is sufficiently aligned */ + ret_count = le16_to_cpup((__le16 *)ihid->rawbuf); + + /* Check for empty report response */ + if (ret_count <= sizeof(__le16)) + return 0; + + recv_len = min(recv_len, ret_count - sizeof(__le16)); + memcpy(recv_buf, ihid->rawbuf + sizeof(__le16), recv_len); + + if (report_id && recv_len != 0 && recv_buf[0] != report_id) { + dev_err(&ihid->client->dev, + "device returned incorrect report (%d vs %d expected)\n", + recv_buf[0], report_id); + return -EINVAL; + } + + return recv_len; +} + +static size_t i2c_hid_format_report(u8 *buf, int report_id, + const u8 *data, size_t size) +{ + size_t length = sizeof(__le16); /* reserve space to store size */ + + if (report_id) + buf[length++] = report_id; + + memcpy(buf + length, data, size); + length += size; + + /* Store overall size in the beginning of the buffer */ + put_unaligned_le16(length, buf); + + return length; +} + /** * i2c_hid_set_or_send_report: forward an incoming report to the device - * @client: the i2c_client of the device - * @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT - * @reportID: the report ID + * @ihid: the i2c hid device + * @report_type: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT + * @report_id: the report ID * @buf: the actual data to transfer, without the report ID * @data_len: size of buf - * @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report + * @do_set: true: use SET_REPORT HID command, false: send plain OUTPUT report */ -static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType, - u8 reportID, unsigned char *buf, size_t data_len, bool use_data) +static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + const u8 *buf, size_t data_len, + bool do_set) { - struct i2c_hid *ihid = i2c_get_clientdata(client); - u8 *args = ihid->argsbuf; - const struct i2c_hid_cmd *hidcmd; - int ret; - u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister); - u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister); - u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength); - u16 size; - int args_len; - int index = 0; + size_t length = 0; + int error; i2c_hid_dbg(ihid, "%s\n", __func__); if (data_len > ihid->bufsize) return -EINVAL; - size = 2 /* size */ + - (reportID ? 1 : 0) /* reportID */ + - data_len /* buf */; - args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ + - 2 /* dataRegister */ + - size /* args */; - - if (!use_data && maxOutputLength == 0) + if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0) return -ENOSYS; - if (reportID >= 0x0F) { - args[index++] = reportID; - reportID = 0x0F; - } - - /* - * use the data register for feature reports or if the device does not - * support the output register - */ - if (use_data) { - args[index++] = dataRegister & 0xFF; - args[index++] = dataRegister >> 8; - hidcmd = &hid_set_report_cmd; + if (do_set) { + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is SET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_REPORT, + report_type, report_id); + /* + * Report data will go into the data register. Because + * command can be either 2 or 3 bytes destination for + * the data register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); } else { - args[index++] = outputRegister & 0xFF; - args[index++] = outputRegister >> 8; - hidcmd = &hid_no_cmd; + /* + * With simple "send report" all data goes into the output + * register. + */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister; + length += sizeof(__le16); } - args[index++] = size & 0xFF; - args[index++] = size >> 8; + length += i2c_hid_format_report(ihid->cmdbuf + length, + report_id, buf, data_len); - if (reportID) - args[index++] = reportID; - - memcpy(&args[index], buf, data_len); - - ret = __i2c_hid_command(client, hidcmd, reportID, - reportType, args, args_len, NULL, 0); - if (ret) { - dev_err(&client->dev, "failed to set a report to device.\n"); - return ret; + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); + if (error) { + dev_err(&ihid->client->dev, + "failed to set a report to device: %d\n", error); + return error; } return data_len; } -static int i2c_hid_set_power(struct i2c_client *client, int power_state) +static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state) +{ + size_t length; + + /* SET_POWER uses command register */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length = sizeof(__le16); + + /* Now the command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_POWER, + 0, power_state); + + return i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); +} + +static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) { - struct i2c_hid *ihid = i2c_get_clientdata(client); int ret; i2c_hid_dbg(ihid, "%s\n", __func__); @@ -408,18 +400,17 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state) */ if (power_state == I2C_HID_PWR_ON && ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) { - ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0); + ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON); /* Device was already activated */ if (!ret) goto set_pwr_exit; } - ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state, - 0, NULL, 0, NULL, 0); - + ret = i2c_hid_set_power_command(ihid, power_state); if (ret) - dev_err(&client->dev, "failed to change power setting.\n"); + dev_err(&ihid->client->dev, + "failed to change power setting.\n"); set_pwr_exit: @@ -438,9 +429,49 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state) return ret; } -static int i2c_hid_hwreset(struct i2c_client *client) +static int i2c_hid_execute_reset(struct i2c_hid *ihid) +{ + size_t length = 0; + int ret; + + i2c_hid_dbg(ihid, "resetting...\n"); + + /* Prepare reset command. Command register goes first. */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is RESET command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_RESET, 0, 0); + + set_bit(I2C_HID_RESET_PENDING, &ihid->flags); + + ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); + if (ret) { + dev_err(&ihid->client->dev, "failed to reset device.\n"); + goto out; + } + + if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) { + msleep(100); + goto out; + } + + i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); + if (!wait_event_timeout(ihid->wait, + !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), + msecs_to_jiffies(5000))) { + ret = -ENODATA; + goto out; + } + i2c_hid_dbg(ihid, "%s: finished.\n", __func__); + +out: + clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); + return ret; +} + +static int i2c_hid_hwreset(struct i2c_hid *ihid) { - struct i2c_hid *ihid = i2c_get_clientdata(client); int ret; i2c_hid_dbg(ihid, "%s\n", __func__); @@ -452,22 +483,21 @@ static int i2c_hid_hwreset(struct i2c_client *client) */ mutex_lock(&ihid->reset_lock); - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); if (ret) goto out_unlock; - i2c_hid_dbg(ihid, "resetting...\n"); - - ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0); + ret = i2c_hid_execute_reset(ihid); if (ret) { - dev_err(&client->dev, "failed to reset device.\n"); - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + dev_err(&ihid->client->dev, + "failed to reset device: %d\n", ret); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); goto out_unlock; } /* At least some SIS devices need this after reset */ if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET)) - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); out_unlock: mutex_unlock(&ihid->reset_lock); @@ -476,9 +506,9 @@ static int i2c_hid_hwreset(struct i2c_client *client) static void i2c_hid_get_input(struct i2c_hid *ihid) { + u16 size = le16_to_cpu(ihid->hdesc.wMaxInputLength); + u16 ret_size; int ret; - u32 ret_size; - int size = le16_to_cpu(ihid->hdesc.wMaxInputLength); if (size > ihid->bufsize) size = ihid->bufsize; @@ -493,8 +523,8 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) return; } - ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8; - + /* Receiving buffer is properly aligned */ + ret_size = le16_to_cpup((__le16 *)ihid->inbuf); if (!ret_size) { /* host or device initiated RESET completed */ if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags)) @@ -502,19 +532,20 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) return; } - if (ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ && ret_size == 0xffff) { - dev_warn_once(&ihid->client->dev, "%s: IRQ triggered but " - "there's no data\n", __func__); + if ((ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ) && ret_size == 0xffff) { + dev_warn_once(&ihid->client->dev, + "%s: IRQ triggered but there's no data\n", + __func__); return; } - if ((ret_size > size) || (ret_size < 2)) { + if (ret_size > size || ret_size < sizeof(__le16)) { if (ihid->quirks & I2C_HID_QUIRK_BAD_INPUT_SIZE) { - ihid->inbuf[0] = size & 0xff; - ihid->inbuf[1] = size >> 8; + *(__le16 *)ihid->inbuf = cpu_to_le16(size); ret_size = size; } else { - dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n", + dev_err(&ihid->client->dev, + "%s: incomplete report (%d/%d)\n", __func__, size, ret_size); return; } @@ -525,8 +556,9 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) if (test_bit(I2C_HID_STARTED, &ihid->flags)) { pm_wakeup_event(&ihid->client->dev, 0); - hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2, - ret_size - 2, 1); + hid_input_report(ihid->hid, HID_INPUT_REPORT, + ihid->inbuf + sizeof(__le16), + ret_size - sizeof(__le16), 1); } return; @@ -572,31 +604,33 @@ static void i2c_hid_free_buffers(struct i2c_hid *ihid) { kfree(ihid->inbuf); kfree(ihid->rawbuf); - kfree(ihid->argsbuf); kfree(ihid->cmdbuf); ihid->inbuf = NULL; ihid->rawbuf = NULL; ihid->cmdbuf = NULL; - ihid->argsbuf = NULL; ihid->bufsize = 0; } static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size) { - /* the worst case is computed from the set_report command with a - * reportID > 15 and the maximum report length */ - int args_len = sizeof(__u8) + /* ReportID */ - sizeof(__u8) + /* optional ReportID byte */ - sizeof(__u16) + /* data register */ - sizeof(__u16) + /* size of the report */ - report_size; /* report */ + /* + * The worst case is computed from the set_report command with a + * reportID > 15 and the maximum report length. + */ + int cmd_len = sizeof(__le16) + /* command register */ + sizeof(u8) + /* encoded report type/ID */ + sizeof(u8) + /* opcode */ + sizeof(u8) + /* optional 3rd byte report ID */ + sizeof(__le16) + /* data register */ + sizeof(__le16) + /* report data size */ + sizeof(u8) + /* report ID if numbered report */ + report_size; ihid->inbuf = kzalloc(report_size, GFP_KERNEL); ihid->rawbuf = kzalloc(report_size, GFP_KERNEL); - ihid->argsbuf = kzalloc(args_len, GFP_KERNEL); - ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL); + ihid->cmdbuf = kzalloc(cmd_len, GFP_KERNEL); - if (!ihid->inbuf || !ihid->rawbuf || !ihid->argsbuf || !ihid->cmdbuf) { + if (!ihid->inbuf || !ihid->rawbuf || !ihid->cmdbuf) { i2c_hid_free_buffers(ihid); return -ENOMEM; } @@ -607,43 +641,39 @@ static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size) } static int i2c_hid_get_raw_report(struct hid_device *hid, - unsigned char report_number, __u8 *buf, size_t count, - unsigned char report_type) + u8 report_type, u8 report_id, + u8 *buf, size_t count) { struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); - size_t ret_count, ask_count; - int ret; + int ret_count; if (report_type == HID_OUTPUT_REPORT) return -EINVAL; - /* +2 bytes to include the size of the reply in the query buffer */ - ask_count = min(count + 2, (size_t)ihid->bufsize); + /* + * In case of unnumbered reports the response from the device will + * not have the report ID that the upper layers expect, so we need + * to stash it the buffer ourselves and adjust the data size. + */ + if (!report_id) { + buf[0] = 0; + buf++; + count--; + } - ret = i2c_hid_get_report(client, + ret_count = i2c_hid_get_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x01, - report_number, ihid->rawbuf, ask_count); + report_id, buf, count); - if (ret < 0) - return ret; + if (ret_count > 0 && !report_id) + ret_count++; - ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8); - - if (ret_count <= 2) - return 0; - - ret_count = min(ret_count, ask_count); - - /* The query buffer contains the size, dropping it in the reply */ - count = min(count, ret_count - 2); - memcpy(buf, ihid->rawbuf + 2, count); - - return count; + return ret_count; } -static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, - size_t count, unsigned char report_type, bool use_data) +static int i2c_hid_output_raw_report(struct hid_device *hid, u8 report_type, + const u8 *buf, size_t count, bool do_set) { struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); @@ -655,28 +685,29 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, mutex_lock(&ihid->reset_lock); - if (report_id) { - buf++; - count--; - } - - ret = i2c_hid_set_or_send_report(client, + /* + * Note that both numbered and unnumbered reports passed here + * are supposed to have report ID stored in the 1st byte of the + * buffer, so we strip it off unconditionally before passing payload + * to i2c_hid_set_or_send_report which takes care of encoding + * everything properly. + */ + ret = i2c_hid_set_or_send_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, - report_id, buf, count, use_data); + report_id, buf + 1, count - 1, do_set); - if (report_id && ret >= 0) - ret++; /* add report_id to the number of transfered bytes */ + if (ret >= 0) + ret++; /* add report_id to the number of transferred bytes */ mutex_unlock(&ihid->reset_lock); return ret; } -static int i2c_hid_output_report(struct hid_device *hid, __u8 *buf, - size_t count) +static int i2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) { - return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT, - false); + return i2c_hid_output_raw_report(hid, HID_OUTPUT_REPORT, buf, count, + false); } static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, @@ -685,11 +716,11 @@ static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, { switch (reqtype) { case HID_REQ_GET_REPORT: - return i2c_hid_get_raw_report(hid, reportnum, buf, len, rtype); + return i2c_hid_get_raw_report(hid, rtype, reportnum, buf, len); case HID_REQ_SET_REPORT: if (buf[0] != reportnum) return -EINVAL; - return i2c_hid_output_raw_report(hid, buf, len, rtype, true); + return i2c_hid_output_raw_report(hid, rtype, buf, len, true); default: return -EIO; } @@ -715,7 +746,7 @@ static int i2c_hid_parse(struct hid_device *hid) } do { - ret = i2c_hid_hwreset(client); + ret = i2c_hid_hwreset(ihid); if (ret) msleep(1000); } while (tries-- > 0 && ret); @@ -739,8 +770,9 @@ static int i2c_hid_parse(struct hid_device *hid) i2c_hid_dbg(ihid, "asking HID report descriptor\n"); - ret = i2c_hid_command(client, &hid_report_descr_cmd, - rdesc, rsize); + ret = i2c_hid_read_register(ihid, + ihid->hdesc.wReportDescRegister, + rdesc, rsize); if (ret) { hid_err(hid, "reading report descriptor failed\n"); kfree(rdesc); @@ -850,7 +882,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) struct i2c_client *client = ihid->client; struct i2c_hid_desc *hdesc = &ihid->hdesc; unsigned int dsize; - int ret; + int error; /* i2c hid fetch using a fixed descriptor size (30 bytes) */ if (i2c_hid_get_dmi_i2c_hid_desc_override(client->name)) { @@ -859,11 +891,14 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) *i2c_hid_get_dmi_i2c_hid_desc_override(client->name); } else { i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); - ret = i2c_hid_command(client, &hid_descr_cmd, - ihid->hdesc_buffer, - sizeof(struct i2c_hid_desc)); - if (ret) { - dev_err(&client->dev, "hid_descr_cmd failed\n"); + error = i2c_hid_read_register(ihid, + ihid->wHIDDescRegister, + &ihid->hdesc, + sizeof(ihid->hdesc)); + if (error) { + dev_err(&ihid->client->dev, + "failed to fetch HID descriptor: %d\n", + error); return -ENODEV; } } @@ -873,7 +908,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) * bytes 2-3 -> bcdVersion (has to be 1.00) */ /* check bcdVersion == 1.0 */ if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) { - dev_err(&client->dev, + dev_err(&ihid->client->dev, "unexpected HID descriptor bcdVersion (0x%04hx)\n", le16_to_cpu(hdesc->bcdVersion)); return -ENODEV; @@ -882,11 +917,11 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) /* Descriptor length should be 30 bytes as per the specification */ dsize = le16_to_cpu(hdesc->wHIDDescLength); if (dsize != sizeof(struct i2c_hid_desc)) { - dev_err(&client->dev, "weird size of HID descriptor (%u)\n", - dsize); + dev_err(&ihid->client->dev, + "weird size of HID descriptor (%u)\n", dsize); return -ENODEV; } - i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer); + i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, &ihid->hdesc); return 0; } @@ -1052,7 +1087,7 @@ void i2c_hid_core_shutdown(struct i2c_client *client) { struct i2c_hid *ihid = i2c_get_clientdata(client); - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); free_irq(client->irq, ihid); i2c_hid_core_shutdown_tail(ihid); @@ -1073,7 +1108,7 @@ static int i2c_hid_core_suspend(struct device *dev) return ret; /* Save some power */ - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); disable_irq(client->irq); @@ -1121,9 +1156,9 @@ static int i2c_hid_core_resume(struct device *dev) * let's still reset them here. */ if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) - ret = i2c_hid_hwreset(client); + ret = i2c_hid_hwreset(ihid); else - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); if (ret) return ret; diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c index e24988586710..16aa030af845 100644 --- a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c @@ -661,21 +661,12 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, */ payload_max_size &= ~(L1_CACHE_BYTES - 1); - dma_buf = kmalloc(payload_max_size, GFP_KERNEL | GFP_DMA32); + dma_buf = dma_alloc_coherent(devc, payload_max_size, &dma_buf_phy, GFP_KERNEL); if (!dma_buf) { client_data->flag_retry = true; return -ENOMEM; } - dma_buf_phy = dma_map_single(devc, dma_buf, payload_max_size, - DMA_TO_DEVICE); - if (dma_mapping_error(devc, dma_buf_phy)) { - dev_err(cl_data_to_dev(client_data), "DMA map failed\n"); - client_data->flag_retry = true; - rv = -ENOMEM; - goto end_err_dma_buf_release; - } - ldr_xfer_dma_frag.fragment.hdr.command = LOADER_CMD_XFER_FRAGMENT; ldr_xfer_dma_frag.fragment.xfer_mode = LOADER_XFER_MODE_DIRECT_DMA; ldr_xfer_dma_frag.ddr_phys_addr = (u64)dma_buf_phy; @@ -695,14 +686,7 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, ldr_xfer_dma_frag.fragment.size = fragment_size; memcpy(dma_buf, &fw->data[fragment_offset], fragment_size); - dma_sync_single_for_device(devc, dma_buf_phy, - payload_max_size, - DMA_TO_DEVICE); - - /* - * Flush cache here because the dma_sync_single_for_device() - * does not do for x86. - */ + /* Flush cache to be sure the data is in main memory. */ clflush_cache_range(dma_buf, payload_max_size); dev_dbg(cl_data_to_dev(client_data), @@ -725,15 +709,8 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, fragment_offset += fragment_size; } - dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE); - kfree(dma_buf); - return 0; - end_err_resp_buf_release: - /* Free ISH buffer if not done already, in error case */ - dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE); -end_err_dma_buf_release: - kfree(dma_buf); + dma_free_coherent(devc, payload_max_size, dma_buf, dma_buf_phy); return rv; } diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index cb6ec59a045d..cbb1599a520e 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -469,6 +469,27 @@ static const struct soc_device_data soc_device_INT33D3 = { .button_info = soc_button_INT33D3, }; +/* + * Button info for Microsoft Surface 3 (non pro), this is indentical to + * the PNP0C40 info except that the home button is active-high. + * + * The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom + * version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API + * instead. A check() callback is not necessary though as the Surface 3 Pro + * MSHW0028 ACPI device's resource table does not contain any GPIOs. + */ +static const struct soc_button_info soc_button_MSHW0028[] = { + { "power", 0, EV_KEY, KEY_POWER, false, true, true }, + { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, + { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, + { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, + { } +}; + +static const struct soc_device_data soc_device_MSHW0028 = { + .button_info = soc_button_MSHW0028, +}; + /* * Special device check for Surface Book 2 and Surface Pro (2017). * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned @@ -535,7 +556,8 @@ static const struct acpi_device_id soc_button_acpi_match[] = { { "ID9001", (unsigned long)&soc_device_INT33D3 }, { "ACPI0011", 0 }, - /* Microsoft Surface Devices (5th and 6th generation) */ + /* Microsoft Surface Devices (3th, 5th and 6th generation) */ + { "MSHW0028", (unsigned long)&soc_device_MSHW0028 }, { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, { } diff --git a/drivers/pinctrl/intel/pinctrl-baytrail.c b/drivers/pinctrl/intel/pinctrl-baytrail.c index 4c01333e1406..1cc660e6458e 100644 --- a/drivers/pinctrl/intel/pinctrl-baytrail.c +++ b/drivers/pinctrl/intel/pinctrl-baytrail.c @@ -443,6 +443,9 @@ static const unsigned int byt_sus_pcu_spi_pins[] = { 21 }; static const unsigned int byt_sus_pcu_spi_mode_values[] = { 0 }; static const unsigned int byt_sus_pcu_spi_gpio_mode_values[] = { 1 }; +static const unsigned int byt_sus_pmu_clk1_pins[] = { 5 }; +static const unsigned int byt_sus_pmu_clk2_pins[] = { 6 }; + static const struct intel_pingroup byt_sus_groups[] = { PIN_GROUP("usb_oc_grp", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_mode_values), PIN_GROUP("usb_ulpi_grp", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_mode_values), @@ -450,20 +453,27 @@ static const struct intel_pingroup byt_sus_groups[] = { PIN_GROUP("usb_oc_grp_gpio", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_gpio_mode_values), PIN_GROUP("usb_ulpi_grp_gpio", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_gpio_mode_values), PIN_GROUP("pcu_spi_grp_gpio", byt_sus_pcu_spi_pins, byt_sus_pcu_spi_gpio_mode_values), + PIN_GROUP("pmu_clk1_grp", byt_sus_pmu_clk1_pins, 1), + PIN_GROUP("pmu_clk2_grp", byt_sus_pmu_clk2_pins, 1), }; static const char * const byt_sus_usb_groups[] = { "usb_oc_grp", "usb_ulpi_grp", }; static const char * const byt_sus_spi_groups[] = { "pcu_spi_grp" }; +static const char * const byt_sus_pmu_clk_groups[] = { + "pmu_clk1_grp", "pmu_clk2_grp", +}; static const char * const byt_sus_gpio_groups[] = { "usb_oc_grp_gpio", "usb_ulpi_grp_gpio", "pcu_spi_grp_gpio", + "pmu_clk1_grp", "pmu_clk2_grp", }; static const struct intel_function byt_sus_functions[] = { FUNCTION("usb", byt_sus_usb_groups), FUNCTION("spi", byt_sus_spi_groups), FUNCTION("gpio", byt_sus_gpio_groups), + FUNCTION("pmu_clk", byt_sus_pmu_clk_groups), }; static const struct intel_community byt_sus_communities[] = { diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 463f1ec5c14e..eb79fbed8059 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -28,13 +28,6 @@ config SURFACE3_WMI To compile this driver as a module, choose M here: the module will be called surface3-wmi. -config SURFACE_3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" - depends on ACPI - depends on KEYBOARD_GPIO && I2C - help - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - config SURFACE_3_POWER_OPREGION tristate "Surface 3 battery platform operation region support" depends on ACPI diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 32889482de55..0fc9cd3e4dd9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -5,7 +5,6 @@ # obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o -obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 09ac9cfc40d8..ca4602bcc7de 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -116,15 +116,11 @@ static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, void *data, void **return_value) { - struct acpi_device *adev, **ts_adev; + struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + struct acpi_device **ts_adev = data; - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - ts_adev = data; - - if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, - strlen(SPI_TS_OBJ_NAME))) + if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, + strlen(SPI_TS_OBJ_NAME))) return AE_OK; if (*ts_adev) { @@ -190,14 +186,11 @@ static int s3_wmi_create_and_register_input(struct platform_device *pdev) error = input_register_device(input); if (error) - goto out_err; + return error; s3_wmi.input = input; return 0; - out_err: - input_free_device(s3_wmi.input); - return error; } static int __init s3_wmi_probe(struct platform_device *pdev) diff --git a/drivers/platform/surface/surface3_button.c b/drivers/platform/surface/surface3_button.c deleted file mode 100644 index 48d77e7aae76..000000000000 --- a/drivers/platform/surface/surface3_button.c +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Supports for the button array on the Surface tablets. - * - * (C) Copyright 2016 Red Hat, Inc - * - * Based on soc_button_array.c: - * - * {C} Copyright 2014 Intel Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#define SURFACE_BUTTON_OBJ_NAME "TEV2" -#define MAX_NBUTTONS 4 - -/* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put - * buttons into them based on whether the key should be auto repeat. - */ -#define BUTTON_TYPES 2 - -/* - * Power button, Home button, Volume buttons support is supposed to - * be covered by drivers/input/misc/soc_button_array.c, which is implemented - * according to "Windows ACPI Design Guide for SoC Platforms". - * However surface 3 seems not to obey the specs, instead it uses - * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly - * different in which the Home button is active high. - * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 - * is a reduce platform and thus uses GPIOs, not ACPI events. - * We choose an I2C driver here because we need to access the resources - * declared under the device node, while surfacepro3_button.c only needs - * the ACPI companion node. - */ -static const struct acpi_device_id surface3_acpi_match[] = { - { "MSHW0028", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); - -struct surface3_button_info { - const char *name; - int acpi_index; - unsigned int event_type; - unsigned int event_code; - bool autorepeat; - bool wakeup; - bool active_low; -}; - -struct surface3_button_data { - struct platform_device *children[BUTTON_TYPES]; -}; - -/* - * Get the Nth GPIO number from the ACPI object. - */ -static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) -{ - struct gpio_desc *desc; - int gpio; - - desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); - if (IS_ERR(desc)) - return PTR_ERR(desc); - - gpio = desc_to_gpio(desc); - - gpiod_put(desc); - - return gpio; -} - -static struct platform_device * -surface3_button_device_create(struct i2c_client *client, - const struct surface3_button_info *button_info, - bool autorepeat) -{ - const struct surface3_button_info *info; - struct platform_device *pd; - struct gpio_keys_button *gpio_keys; - struct gpio_keys_platform_data *gpio_keys_pdata; - int n_buttons = 0; - int gpio; - int error; - - gpio_keys_pdata = devm_kzalloc(&client->dev, - sizeof(*gpio_keys_pdata) + - sizeof(*gpio_keys) * MAX_NBUTTONS, - GFP_KERNEL); - if (!gpio_keys_pdata) - return ERR_PTR(-ENOMEM); - - gpio_keys = (void *)(gpio_keys_pdata + 1); - - for (info = button_info; info->name; info++) { - if (info->autorepeat != autorepeat) - continue; - - gpio = surface3_button_lookup_gpio(&client->dev, - info->acpi_index); - if (!gpio_is_valid(gpio)) - continue; - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; - gpio_keys[n_buttons].gpio = gpio; - gpio_keys[n_buttons].active_low = info->active_low; - gpio_keys[n_buttons].desc = info->name; - gpio_keys[n_buttons].wakeup = info->wakeup; - n_buttons++; - } - - if (n_buttons == 0) { - error = -ENODEV; - goto err_free_mem; - } - - gpio_keys_pdata->buttons = gpio_keys; - gpio_keys_pdata->nbuttons = n_buttons; - gpio_keys_pdata->rep = autorepeat; - - pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); - if (!pd) { - error = -ENOMEM; - goto err_free_mem; - } - - error = platform_device_add_data(pd, gpio_keys_pdata, - sizeof(*gpio_keys_pdata)); - if (error) - goto err_free_pdev; - - error = platform_device_add(pd); - if (error) - goto err_free_pdev; - - return pd; - -err_free_pdev: - platform_device_put(pd); -err_free_mem: - devm_kfree(&client->dev, gpio_keys_pdata); - return ERR_PTR(error); -} - -static int surface3_button_remove(struct i2c_client *client) -{ - struct surface3_button_data *priv = i2c_get_clientdata(client); - - int i; - - for (i = 0; i < BUTTON_TYPES; i++) - if (priv->children[i]) - platform_device_unregister(priv->children[i]); - - return 0; -} - -static struct surface3_button_info surface3_button_surface3[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, - { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, - { } -}; - -static int surface3_button_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct surface3_button_data *priv; - struct platform_device *pd; - int i; - int error; - - if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), - SURFACE_BUTTON_OBJ_NAME, - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - - error = gpiod_count(dev, NULL); - if (error < 0) { - dev_dbg(dev, "no GPIO attached, ignoring...\n"); - return error; - } - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - i2c_set_clientdata(client, priv); - - for (i = 0; i < BUTTON_TYPES; i++) { - pd = surface3_button_device_create(client, - surface3_button_surface3, - i == 0); - if (IS_ERR(pd)) { - error = PTR_ERR(pd); - if (error != -ENODEV) { - surface3_button_remove(client); - return error; - } - continue; - } - - priv->children[i] = pd; - } - - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - - return 0; -} - -static const struct i2c_device_id surface3_id[] = { - { } -}; -MODULE_DEVICE_TABLE(i2c, surface3_id); - -static struct i2c_driver surface3_driver = { - .probe = surface3_button_probe, - .remove = surface3_button_remove, - .id_table = surface3_id, - .driver = { - .name = "surface3", - .acpi_match_table = ACPI_PTR(surface3_acpi_match), - }, -}; -module_i2c_driver(surface3_driver); - -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_DESCRIPTION("surface3 button array driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 8339988d95c1..7b758f8cc137 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -770,7 +770,8 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, return AE_OK; /* Ignore ACPI devices that are not present. */ - if (acpi_bus_get_device(handle, &adev) != 0) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return AE_OK; san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 8d1eec208854..5d9dd70e4e0f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -210,6 +210,19 @@ config AMD_PMC If you choose to compile this driver as a module the module will be called amd-pmc. +config AMD_HSMP + tristate "AMD HSMP Driver" + depends on AMD_NB && X86_64 + help + The driver provides a way for user space tools to monitor and manage + system management functionality on EPYC server CPUs from AMD. + + Host System Management Port (HSMP) interface is a mailbox interface + between the x86 core and the System Management Unit (SMU) firmware. + + If you choose to compile this driver as a module the module will be + called amd_hsmp. + config ADV_SWBUTTON tristate "Advantech ACPI Software Button Driver" depends on ACPI && INPUT @@ -915,6 +928,7 @@ config COMPAL_LAPTOP config LG_LAPTOP tristate "LG Laptop Extras" depends on ACPI + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1027,7 +1041,7 @@ config TOUCHSCREEN_DMI config X86_ANDROID_TABLETS tristate "X86 Android tablet support" - depends on I2C && SERIAL_DEV_BUS && ACPI && GPIOLIB + depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB help X86 tablets which ship with Android as (part of) the factory image typically have various problems with their DSDTs. The factory kernels diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9527088bba7f..fe4d4c8970ef 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o # AMD obj-$(CONFIG_AMD_PMC) += amd-pmc.o +obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o # Advantech obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index b1103f85a85a..e9d0dbbb2887 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -42,6 +41,16 @@ #define AMD_PMC_STB_PMI_0 0x03E30600 #define AMD_PMC_STB_PREDEF 0xC6000001 +/* STB S2D(Spill to DRAM) has different message port offset */ +#define STB_SPILL_TO_DRAM 0xBE +#define AMD_S2D_REGISTER_MESSAGE 0xA20 +#define AMD_S2D_REGISTER_RESPONSE 0xA80 +#define AMD_S2D_REGISTER_ARGUMENT 0xA88 + +/* STB Spill to DRAM Parameters */ +#define S2D_TELEMETRY_BYTES_MAX 0x100000 +#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000 + /* Base address of SMU for mapping physical address to virtual address */ #define AMD_PMC_SMU_INDEX_ADDRESS 0xB8 #define AMD_PMC_SMU_INDEX_DATA 0xBC @@ -86,9 +95,6 @@ #define PMC_MSG_DELAY_MIN_US 50 #define RESPONSE_REGISTER_LOOP_MAX 20000 -/* QoS request for letting CPUs in idle states, but not the deepest */ -#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3 - #define SOC_SUBSYSTEM_IP_MAX 12 #define DELAY_MIN_US 2000 #define DELAY_MAX_US 3000 @@ -99,6 +105,12 @@ enum amd_pmc_def { MSG_OS_HINT_RN, }; +enum s2d_arg { + S2D_TELEMETRY_SIZE = 0x01, + S2D_PHYS_ADDR_LOW, + S2D_PHYS_ADDR_HIGH, +}; + struct amd_pmc_bit_map { const char *name; u32 bit_mask; @@ -123,7 +135,9 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; + void __iomem *stb_virt_addr; void __iomem *fch_virt_addr; + bool msg_port; u32 base_addr; u32 cpu_id; u32 active_ips; @@ -135,7 +149,6 @@ struct amd_pmc_dev { struct device *dev; struct pci_dev *rdev; struct mutex lock; /* generic mutex lock */ - struct pm_qos_request amd_pmc_pm_qos_req; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ @@ -241,6 +254,44 @@ static const struct file_operations amd_pmc_stb_debugfs_fops = { .release = amd_pmc_stb_debugfs_release, }; +static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp) +{ + struct amd_pmc_dev *dev = filp->f_inode->i_private; + u32 *buf; + + buf = kzalloc(S2D_TELEMETRY_BYTES_MAX, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy_fromio(buf, dev->stb_virt_addr, S2D_TELEMETRY_BYTES_MAX); + filp->private_data = buf; + + return 0; +} + +static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, + loff_t *pos) +{ + if (!filp->private_data) + return -EINVAL; + + return simple_read_from_buffer(buf, size, pos, filp->private_data, + S2D_TELEMETRY_BYTES_MAX); +} + +static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp) +{ + kfree(filp->private_data); + return 0; +} + +static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = { + .owner = THIS_MODULE, + .open = amd_pmc_stb_debugfs_open_v2, + .read = amd_pmc_stb_debugfs_read_v2, + .release = amd_pmc_stb_debugfs_release_v2, +}; + static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, struct seq_file *s) { @@ -266,6 +317,28 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, return 0; } +static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table) +{ + if (pdev->cpu_id == AMD_CPU_ID_PCO) + return -ENODEV; + memcpy_fromio(table, pdev->smu_virt_addr, sizeof(struct smu_metrics)); + return 0; +} + +static void amd_pmc_validate_deepest(struct amd_pmc_dev *pdev) +{ + struct smu_metrics table; + + if (get_metrics_table(pdev, &table)) + return; + + if (!table.s0i3_last_entry_status) + dev_warn(pdev->dev, "Last suspend didn't reach deepest state\n"); + else + dev_dbg(pdev->dev, "Last suspend in deepest state for %lluus\n", + table.timein_s0i3_lastcapture); +} + #ifdef CONFIG_DEBUG_FS static int smu_fw_info_show(struct seq_file *s, void *unused) { @@ -273,11 +346,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) struct smu_metrics table; int idx; - if (dev->cpu_id == AMD_CPU_ID_PCO) + if (get_metrics_table(dev, &table)) return -EINVAL; - memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); - seq_puts(s, "\n=== SMU Statistics ===\n"); seq_printf(s, "Table Version: %d\n", table.table_version); seq_printf(s, "Hint Count: %d\n", table.hint_count); @@ -355,9 +426,14 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev, &amd_pmc_idlemask_fops); /* Enable STB only when the module_param is set */ - if (enable_stb) - debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, - &amd_pmc_stb_debugfs_fops); + if (enable_stb) { + if (dev->cpu_id == AMD_CPU_ID_YC) + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops_v2); + else + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops); + } } #else static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) @@ -397,26 +473,47 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) { - u32 value; + u32 value, message, argument, response; - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE); + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } + + value = amd_pmc_reg_read(dev, response); dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + value = amd_pmc_reg_read(dev, argument); dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE); + value = amd_pmc_reg_read(dev, message); dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); } static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret) { int rc; - u32 val; + u32 val, message, argument, response; mutex_lock(&dev->lock); + + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } + /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -425,16 +522,16 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, } /* Write zero to response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0); + amd_pmc_reg_write(dev, response, 0); /* Write argument into response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, arg); + amd_pmc_reg_write(dev, argument, arg); /* Write message ID to message ID register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); + amd_pmc_reg_write(dev, message, msg); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -447,7 +544,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, if (ret) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + *data = amd_pmc_reg_read(dev, argument); } break; case AMD_PMC_RESULT_CMD_REJECT_BUSY: @@ -526,20 +623,12 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) rc = rtc_alarm_irq_enable(rtc_device, 0); dev_dbg(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration); - /* - * Prevent CPUs from getting into deep idle states while sending OS_HINT - * which is otherwise generally safe to send when at least one of the CPUs - * is not in deep idle states. - */ - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, AMD_PMC_MAX_IDLE_STATE_LATENCY); - wake_up_all_idle_cpus(); - return rc; } -static int __maybe_unused amd_pmc_suspend(struct device *dev) +static void amd_pmc_s2idle_prepare(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; u32 arg = 1; @@ -551,68 +640,59 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) /* Activate CZN specific RTC functionality */ if (pdev->cpu_id == AMD_CPU_ID_CZN) { rc = amd_pmc_verify_czn_rtc(pdev, &arg); - if (rc) - goto fail; + if (rc) { + dev_err(pdev->dev, "failed to set RTC: %d\n", rc); + return; + } } /* Dump the IdleMask before we send hint to SMU */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); if (rc) { - dev_err(pdev->dev, "suspend failed\n"); - goto fail; + dev_err(pdev->dev, "suspend failed: %d\n", rc); + return; } - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); - if (rc) { - dev_err(pdev->dev, "error writing to STB\n"); - goto fail; + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); } - - return 0; -fail: - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); - return rc; } -static int __maybe_unused amd_pmc_resume(struct device *dev) +static void amd_pmc_s2idle_restore(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); if (rc) - dev_err(pdev->dev, "resume failed\n"); + dev_err(pdev->dev, "resume failed: %d\n", rc); /* Let SMU know that we are looking for stats */ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); /* Dump the IdleMask to see the blockers */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); /* Write data incremented by 1 to distinguish in stb_read */ - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1); - if (rc) - dev_err(pdev->dev, "error writing to STB\n"); + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); + } - /* Restore the QoS request back to defaults if it was set */ - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); - - return rc; + /* Notify on failed entry */ + amd_pmc_validate_deepest(pdev); } -static const struct dev_pm_ops amd_pmc_pm_ops = { - .suspend_noirq = amd_pmc_suspend, - .resume_noirq = amd_pmc_resume, +static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = { + .prepare = amd_pmc_s2idle_prepare, + .restore = amd_pmc_s2idle_restore, }; static const struct pci_device_id pmc_pci_ids[] = { @@ -624,6 +704,35 @@ static const struct pci_device_id pmc_pci_ids[] = { { } }; +static int amd_pmc_s2d_init(struct amd_pmc_dev *dev) +{ + u32 phys_addr_low, phys_addr_hi; + u64 stb_phys_addr; + u32 size = 0; + + /* Spill to DRAM feature uses separate SMU message port */ + dev->msg_port = 1; + + amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, STB_SPILL_TO_DRAM, 1); + if (size != S2D_TELEMETRY_BYTES_MAX) + return -EIO; + + /* Get STB DRAM address */ + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, STB_SPILL_TO_DRAM, 1); + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, STB_SPILL_TO_DRAM, 1); + + stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); + + /* Clear msg_port for other SMU operation */ + dev->msg_port = 0; + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, S2D_TELEMETRY_DRAMBYTES_MAX); + if (!dev->stb_virt_addr) + return -ENOMEM; + + return 0; +} + static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data) { int err; @@ -742,10 +851,19 @@ static int amd_pmc_probe(struct platform_device *pdev) if (err) dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); + if (enable_stb && dev->cpu_id == AMD_CPU_ID_YC) { + err = amd_pmc_s2d_init(dev); + if (err) + return err; + } + amd_pmc_get_smu_version(dev); platform_set_drvdata(pdev, dev); + err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops); + if (err) + dev_warn(dev->dev, "failed to register LPS0 sleep handler, expect increased power consumption\n"); + amd_pmc_dbgfs_register(dev); - cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); return 0; err_pci_dev_put: @@ -757,6 +875,7 @@ static int amd_pmc_remove(struct platform_device *pdev) { struct amd_pmc_dev *dev = platform_get_drvdata(pdev); + acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); amd_pmc_dbgfs_unregister(dev); pci_dev_put(dev->rdev); mutex_destroy(&dev->lock); @@ -777,7 +896,6 @@ static struct platform_driver amd_pmc_driver = { .driver = { .name = "amd_pmc", .acpi_match_table = amd_pmc_acpi_ids, - .pm = &amd_pmc_pm_ops, }, .probe = amd_pmc_probe, .remove = amd_pmc_remove, diff --git a/drivers/platform/x86/amd_hsmp.c b/drivers/platform/x86/amd_hsmp.c new file mode 100644 index 000000000000..a0c54b838c11 --- /dev/null +++ b/drivers/platform/x86/amd_hsmp.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD HSMP Platform Driver + * Copyright (c) 2022, AMD. + * All Rights Reserved. + * + * This file provides a device implementation for HSMP interface + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "amd_hsmp" +#define DRIVER_VERSION "1.0" + +/* HSMP Status / Error codes */ +#define HSMP_STATUS_NOT_READY 0x00 +#define HSMP_STATUS_OK 0x01 +#define HSMP_ERR_INVALID_MSG 0xFE +#define HSMP_ERR_INVALID_INPUT 0xFF + +/* Timeout in millsec */ +#define HSMP_MSG_TIMEOUT 100 +#define HSMP_SHORT_SLEEP 1 + +#define HSMP_WR true +#define HSMP_RD false + +/* + * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox + * register into the SMN_INDEX register, and reads/writes the SMN_DATA reg. + * Below are required SMN address for HSMP Mailbox register offsets in SMU address space + */ +#define SMN_HSMP_MSG_ID 0x3B10534 +#define SMN_HSMP_MSG_RESP 0x3B10980 +#define SMN_HSMP_MSG_DATA 0x3B109E0 + +#define HSMP_INDEX_REG 0xc4 +#define HSMP_DATA_REG 0xc8 + +static struct semaphore *hsmp_sem; + +static struct miscdevice hsmp_device; + +static int amd_hsmp_rdwr(struct pci_dev *root, u32 address, + u32 *value, bool write) +{ + int ret; + + ret = pci_write_config_dword(root, HSMP_INDEX_REG, address); + if (ret) + return ret; + + ret = (write ? pci_write_config_dword(root, HSMP_DATA_REG, *value) + : pci_read_config_dword(root, HSMP_DATA_REG, value)); + + return ret; +} + +/* + * Send a message to the HSMP port via PCI-e config space registers. + * + * The caller is expected to zero out any unused arguments. + * If a response is expected, the number of response words should be greater than 0. + * + * Returns 0 for success and populates the requested number of arguments. + * Returns a negative error code for failure. + */ +static int __hsmp_send_message(struct pci_dev *root, struct hsmp_message *msg) +{ + unsigned long timeout, short_sleep; + u32 mbox_status; + u32 index; + int ret; + + /* Clear the status register */ + mbox_status = HSMP_STATUS_NOT_READY; + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_WR); + if (ret) { + pr_err("Error %d clearing mailbox status register\n", ret); + return ret; + } + + index = 0; + /* Write any message arguments */ + while (index < msg->num_args) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_WR); + if (ret) { + pr_err("Error %d writing message argument %d\n", ret, index); + return ret; + } + index++; + } + + /* Write the message ID which starts the operation */ + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_ID, &msg->msg_id, HSMP_WR); + if (ret) { + pr_err("Error %d writing message ID %u\n", ret, msg->msg_id); + return ret; + } + + /* + * Depending on when the trigger write completes relative to the SMU + * firmware 1 ms cycle, the operation may take from tens of us to 1 ms + * to complete. Some operations may take more. Therefore we will try + * a few short duration sleeps and switch to long sleeps if we don't + * succeed quickly. + */ + short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP); + timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT); + + while (time_before(jiffies, timeout)) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_RD); + if (ret) { + pr_err("Error %d reading mailbox status\n", ret); + return ret; + } + + if (mbox_status != HSMP_STATUS_NOT_READY) + break; + if (time_before(jiffies, short_sleep)) + usleep_range(50, 100); + else + usleep_range(1000, 2000); + } + + if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) { + return -ETIMEDOUT; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) { + return -ENOMSG; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) { + return -EINVAL; + } else if (unlikely(mbox_status != HSMP_STATUS_OK)) { + pr_err("Message ID %u unknown failure (status = 0x%X)\n", + msg->msg_id, mbox_status); + return -EIO; + } + + /* + * SMU has responded OK. Read response data. + * SMU reads the input arguments from eight 32 bit registers starting + * from SMN_HSMP_MSG_DATA and writes the response data to the same + * SMN_HSMP_MSG_DATA address. + * We copy the response data if any, back to the args[]. + */ + index = 0; + while (index < msg->response_sz) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_RD); + if (ret) { + pr_err("Error %d reading response %u for message ID:%u\n", + ret, index, msg->msg_id); + break; + } + index++; + } + + return ret; +} + +static int validate_message(struct hsmp_message *msg) +{ + /* msg_id against valid range of message IDs */ + if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + /* msg_id is a reserved message ID */ + if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD) + return -ENOMSG; + + /* num_args and response_sz against the HSMP spec */ + if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args || + msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + + return 0; +} + +int hsmp_send_message(struct hsmp_message *msg) +{ + struct amd_northbridge *nb; + int ret; + + if (!msg) + return -EINVAL; + + nb = node_to_amd_nb(msg->sock_ind); + if (!nb || !nb->root) + return -ENODEV; + + ret = validate_message(msg); + if (ret) + return ret; + + /* + * The time taken by smu operation to complete is between + * 10us to 1ms. Sometime it may take more time. + * In SMP system timeout of 100 millisecs should + * be enough for the previous thread to finish the operation + */ + ret = down_timeout(&hsmp_sem[msg->sock_ind], + msecs_to_jiffies(HSMP_MSG_TIMEOUT)); + if (ret < 0) + return ret; + + ret = __hsmp_send_message(nb->root, msg); + + up(&hsmp_sem[msg->sock_ind]); + + return ret; +} +EXPORT_SYMBOL_GPL(hsmp_send_message); + +static int hsmp_test(u16 sock_ind, u32 value) +{ + struct hsmp_message msg = { 0 }; + struct amd_northbridge *nb; + int ret = -ENODEV; + + nb = node_to_amd_nb(sock_ind); + if (!nb || !nb->root) + return ret; + + /* + * Test the hsmp port by performing TEST command. The test message + * takes one argument and returns the value of that argument + 1. + */ + msg.msg_id = HSMP_TEST; + msg.num_args = 1; + msg.response_sz = 1; + msg.args[0] = value; + msg.sock_ind = sock_ind; + + ret = __hsmp_send_message(nb->root, &msg); + if (ret) + return ret; + + /* Check the response value */ + if (msg.args[0] != (value + 1)) { + pr_err("Socket %d test message failed, Expected 0x%08X, received 0x%08X\n", + sock_ind, (value + 1), msg.args[0]); + return -EBADE; + } + + return ret; +} + +static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; + int ret; + + if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message))) + return -EFAULT; + + /* + * Check msg_id is within the range of supported msg ids + * i.e within the array bounds of hsmp_msg_desc_table + */ + if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { + case FMODE_WRITE: + /* + * Device is opened in O_WRONLY mode + * Execute only set/configure commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET) + return -EINVAL; + break; + case FMODE_READ: + /* + * Device is opened in O_RDONLY mode + * Execute only get/monitor commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET) + return -EINVAL; + break; + case FMODE_READ | FMODE_WRITE: + /* + * Device is opened in O_RDWR mode + * Execute both get/monitor and set/configure commands + */ + break; + default: + return -EINVAL; + } + + ret = hsmp_send_message(&msg); + if (ret) + return ret; + + if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) { + /* Copy results back to user for get/monitor commands */ + if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message))) + return -EFAULT; + } + + return 0; +} + +static const struct file_operations hsmp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = hsmp_ioctl, + .compat_ioctl = hsmp_ioctl, +}; + +static int hsmp_pltdrv_probe(struct platform_device *pdev) +{ + int i; + + hsmp_sem = devm_kzalloc(&pdev->dev, + (amd_nb_num() * sizeof(struct semaphore)), + GFP_KERNEL); + if (!hsmp_sem) + return -ENOMEM; + + for (i = 0; i < amd_nb_num(); i++) + sema_init(&hsmp_sem[i], 1); + + hsmp_device.name = "hsmp_cdev"; + hsmp_device.minor = MISC_DYNAMIC_MINOR; + hsmp_device.fops = &hsmp_fops; + hsmp_device.parent = &pdev->dev; + hsmp_device.nodename = "hsmp"; + hsmp_device.mode = 0644; + + return misc_register(&hsmp_device); +} + +static int hsmp_pltdrv_remove(struct platform_device *pdev) +{ + misc_deregister(&hsmp_device); + + return 0; +} + +static struct platform_driver amd_hsmp_driver = { + .probe = hsmp_pltdrv_probe, + .remove = hsmp_pltdrv_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static struct platform_device *amd_hsmp_platdev; + +static int __init hsmp_plt_init(void) +{ + int ret = -ENODEV; + u16 num_sockets; + int i; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) { + pr_err("HSMP is not supported on Family:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + return ret; + } + + /* + * amd_nb_num() returns number of SMN/DF interfaces present in the system + * if we have N SMN/DF interfaces that ideally means N sockets + */ + num_sockets = amd_nb_num(); + if (num_sockets == 0) + return ret; + + /* Test the hsmp interface on each socket */ + for (i = 0; i < num_sockets; i++) { + ret = hsmp_test(i, 0xDEADBEEF); + if (ret) { + pr_err("HSMP is not supported on Fam:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + pr_err("Or Is HSMP disabled in BIOS ?\n"); + return -EOPNOTSUPP; + } + } + + ret = platform_driver_register(&amd_hsmp_driver); + if (ret) + return ret; + + amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, -1); + if (!amd_hsmp_platdev) { + ret = -ENOMEM; + goto drv_unregister; + } + + ret = platform_device_add(amd_hsmp_platdev); + if (ret) { + platform_device_put(amd_hsmp_platdev); + goto drv_unregister; + } + + return 0; + +drv_unregister: + platform_driver_unregister(&amd_hsmp_driver); + return ret; +} + +static void __exit hsmp_plt_exit(void) +{ + platform_device_unregister(amd_hsmp_platdev); + platform_driver_unregister(&amd_hsmp_driver); +} + +device_initcall(hsmp_plt_init); +module_exit(hsmp_plt_exit); + +MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index 5e63d6225048..db3633fafbd5 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -284,6 +284,7 @@ int dcdbas_smi_request(struct smi_cmd *smi_cmd) return ret; } +EXPORT_SYMBOL(dcdbas_smi_request); /** * smi_request_store: @@ -351,7 +352,6 @@ static ssize_t smi_request_store(struct device *dev, mutex_unlock(&smi_data_lock); return ret; } -EXPORT_SYMBOL(dcdbas_smi_request); /** * host_control_smi: generate host control SMI diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 48a46466f086..0e9a25b56e0e 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -35,10 +35,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); -static int enable_tablet_mode_sw = -1; -module_param(enable_tablet_mode_sw, int, 0444); -MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=auto, 0=no, 1=yes)"); - #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" #define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 @@ -61,6 +57,14 @@ static const char * const omen_thermal_profile_boards[] = { "8917", "8918", "8949", "894A", "89EB" }; +/* DMI Board names of Omen laptops that are specifically set to be thermal + * profile version 0 by the Omen Command Center app, regardless of what + * the get system design information WMI call returns + */ +static const char *const omen_thermal_profile_force_v0_boards[] = { + "8607", "8746", "8747", "8749", "874A", "8748" +}; + enum hp_wmi_radio { HPWMI_WIFI = 0x0, HPWMI_BLUETOOTH = 0x1, @@ -86,12 +90,17 @@ enum hp_wmi_event_ids { HPWMI_BATTERY_CHARGE_PERIOD = 0x10, }; +/* + * 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_qurey function. + */ struct bios_args { u32 signature; u32 command; u32 commandtype; u32 datasize; - u8 data[128]; + u8 data[]; }; enum hp_wmi_commandtype { @@ -107,6 +116,7 @@ enum hp_wmi_commandtype { HPWMI_FEATURE2_QUERY = 0x0d, HPWMI_WIRELESS2_QUERY = 0x1b, HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, }; @@ -115,6 +125,7 @@ enum hp_wmi_gm_commandtype { HPWMI_SET_PERFORMANCE_MODE = 0x1A, HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, }; enum hp_wmi_command { @@ -149,10 +160,16 @@ enum hp_wireless2_bits { HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, }; -enum hp_thermal_profile_omen { - HP_OMEN_THERMAL_PROFILE_DEFAULT = 0x00, - HP_OMEN_THERMAL_PROFILE_PERFORMANCE = 0x01, - HP_OMEN_THERMAL_PROFILE_COOL = 0x02, +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, }; enum hp_thermal_profile { @@ -217,6 +234,19 @@ struct rfkill2_device { static int rfkill2_count; static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; +/* + * Chassis Types values were obtained from SMBIOS reference + * specification version 3.00. A complete list of system enclosures + * and chassis types is available on Table 17. + */ +static const char * const tablet_chassis_types[] = { + "30", /* Tablet*/ + "31", /* Convertible */ + "32" /* Detachable */ +}; + +#define DEVICE_MODE_TABLET 0x06 + /* map output size to the corresponding WMI method id */ static inline int encode_outsize_for_pvsz(int outsize) { @@ -256,37 +286,43 @@ static inline int encode_outsize_for_pvsz(int outsize) static int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer, int insize, int outsize) { - int mid; + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL }; struct bios_return *bios_return; - int actual_outsize; - union acpi_object *obj; - struct bios_args args = { - .signature = 0x55434553, - .command = command, - .commandtype = query, - .datasize = insize, - .data = { 0 }, - }; - struct acpi_buffer input = { sizeof(struct bios_args), &args }; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - int ret = 0; + union acpi_object *obj = NULL; + struct bios_args *args = NULL; + int mid, actual_outsize, ret; + size_t bios_args_size; mid = encode_outsize_for_pvsz(outsize); if (WARN_ON(mid < 0)) return mid; - if (WARN_ON(insize > sizeof(args.data))) - return -EINVAL; - memcpy(&args.data[0], buffer, insize); + bios_args_size = struct_size(args, data, insize); + args = kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; - wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + input.length = bios_args_size; + input.pointer = args; + + 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(HPWMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; obj = output.pointer; - - if (!obj) - return -EINVAL; + if (!obj) { + ret = -EINVAL; + goto out_free; + } if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); ret = -EINVAL; goto out_free; } @@ -311,6 +347,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, out_free: kfree(obj); + kfree(args); return ret; } @@ -320,7 +357,7 @@ static int hp_wmi_get_fan_speed(int fan) char fan_data[4] = { fan, 0, 0, 0 }; int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, - &fan_data, sizeof(fan_data), + &fan_data, sizeof(char), sizeof(fan_data)); if (ret != 0) @@ -337,7 +374,7 @@ static int hp_wmi_read_int(int query) int val = 0, ret; ret = hp_wmi_perform_query(query, HPWMI_READ, &val, - sizeof(val), sizeof(val)); + 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; @@ -345,14 +382,39 @@ static int hp_wmi_read_int(int query) return val; } -static int hp_wmi_hw_state(int mask) +static int hp_wmi_get_dock_state(void) { int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); if (state < 0) return state; - return !!(state & mask); + return !!(state & HPWMI_DOCK_MASK); +} + +static int hp_wmi_get_tablet_mode(void) +{ + char system_device_mode[4] = { 0 }; + const char *chassis_type; + bool tablet_found; + int ret; + + chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); + if (!chassis_type) + return -ENODEV; + + tablet_found = match_string(tablet_chassis_types, + ARRAY_SIZE(tablet_chassis_types), + chassis_type) >= 0; + if (!tablet_found) + return -ENODEV; + + ret = hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ, + system_device_mode, 0, sizeof(system_device_mode)); + if (ret < 0) + return ret; + + return system_device_mode[0] == DEVICE_MODE_TABLET; } static int omen_thermal_profile_set(int mode) @@ -360,11 +422,8 @@ static int omen_thermal_profile_set(int mode) char buffer[2] = {0, mode}; int ret; - if (mode < 0 || mode > 2) - return -EINVAL; - ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, - &buffer, sizeof(buffer), sizeof(buffer)); + &buffer, sizeof(buffer), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -384,6 +443,30 @@ static bool is_omen_thermal_profile(void) board_name) >= 0; } +static int omen_get_thermal_policy_version(void) +{ + unsigned char buffer[8] = { 0 }; + int ret; + + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name) { + int matches = match_string(omen_thermal_profile_force_v0_boards, + ARRAY_SIZE(omen_thermal_profile_force_v0_boards), + board_name); + if (matches >= 0) + return 0; + } + + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return buffer[3]; +} + static int omen_thermal_profile_get(void) { u8 data; @@ -401,7 +484,7 @@ static int hp_wmi_fan_speed_max_set(int enabled) int ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, - &enabled, sizeof(enabled), sizeof(enabled)); + &enabled, sizeof(enabled), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -414,7 +497,7 @@ static int hp_wmi_fan_speed_max_get(void) int val = 0, ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, - &val, sizeof(val), sizeof(val)); + &val, 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; @@ -426,7 +509,7 @@ static int __init hp_wmi_bios_2008_later(void) { int state = 0; int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -437,7 +520,7 @@ static int __init hp_wmi_bios_2009_later(void) { u8 state[128]; int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -515,7 +598,7 @@ static int hp_wmi_rfkill2_refresh(void) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err; @@ -568,7 +651,7 @@ static ssize_t als_show(struct device *dev, struct device_attribute *attr, static ssize_t dock_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_DOCK_MASK); + int value = hp_wmi_get_dock_state(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -577,7 +660,7 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr, static ssize_t tablet_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_TABLET_MASK); + int value = hp_wmi_get_tablet_mode(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -604,7 +687,7 @@ static ssize_t als_store(struct device *dev, struct device_attribute *attr, return ret; ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -625,9 +708,9 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, if (clear == false) return -EINVAL; - /* Clear the POST error code. It is kept until until cleared. */ + /* Clear the POST error code. It is kept until cleared. */ ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -699,10 +782,10 @@ static void hp_wmi_notify(u32 value, void *context) case HPWMI_DOCK_EVENT: if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); break; case HPWMI_PARK_HDD: @@ -780,19 +863,17 @@ static int __init hp_wmi_input_setup(void) __set_bit(EV_SW, hp_wmi_input_dev->evbit); /* Dock */ - val = hp_wmi_hw_state(HPWMI_DOCK_MASK); + val = hp_wmi_get_dock_state(); if (!(val < 0)) { __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); input_report_switch(hp_wmi_input_dev, SW_DOCK, val); } /* Tablet mode */ - if (enable_tablet_mode_sw > 0) { - val = hp_wmi_hw_state(HPWMI_TABLET_MASK); - if (val >= 0) { - __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); - input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); - } + val = hp_wmi_get_tablet_mode(); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); } err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); @@ -919,7 +1000,7 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err < 0 ? err : -EINVAL; @@ -1008,13 +1089,16 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, return tp; switch (tp) { - case HP_OMEN_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE: *profile = PLATFORM_PROFILE_PERFORMANCE; break; - case HP_OMEN_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT: *profile = PLATFORM_PROFILE_BALANCED; break; - case HP_OMEN_THERMAL_PROFILE_COOL: + case HP_OMEN_V0_THERMAL_PROFILE_COOL: + case HP_OMEN_V1_THERMAL_PROFILE_COOL: *profile = PLATFORM_PROFILE_COOL; break; default: @@ -1027,17 +1111,31 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, static int platform_profile_omen_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { - int err, tp; + int err, tp, tp_version; + + tp_version = omen_get_thermal_policy_version(); + + if (tp_version < 0 || tp_version > 1) + return -EOPNOTSUPP; switch (profile) { case PLATFORM_PROFILE_PERFORMANCE: - tp = HP_OMEN_THERMAL_PROFILE_PERFORMANCE; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - tp = HP_OMEN_THERMAL_PROFILE_DEFAULT; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_DEFAULT; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT; break; case PLATFORM_PROFILE_COOL: - tp = HP_OMEN_THERMAL_PROFILE_COOL; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_COOL; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_COOL; break; default: return -EOPNOTSUPP; @@ -1227,10 +1325,10 @@ static int hp_wmi_resume_handler(struct device *device) if (hp_wmi_input_dev) { if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); } diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index a2d846c4a7ee..eac3e6b4ea11 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -470,10 +470,17 @@ static DEVICE_ATTR_RW(charge_control_thresholds); static int huawei_wmi_battery_add(struct power_supply *battery) { - device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); - device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + int err = 0; - return 0; + err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); + if (err) + return err; + + err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + if (err) + device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold); + + return err; } static int huawei_wmi_battery_remove(struct power_supply *battery) diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 8e65086bb6c8..1f01a8a23c57 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -5,13 +5,14 @@ source "drivers/platform/x86/intel/atomisp2/Kconfig" source "drivers/platform/x86/intel/int1092/Kconfig" -source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" source "drivers/platform/x86/intel/wmi/Kconfig" +source "drivers/platform/x86/intel/uncore-frequency/Kconfig" + config INTEL_HID_EVENT tristate "Intel HID Event" @@ -89,6 +90,26 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. +config INTEL_CHTWC_INT33FE + tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver" + depends on X86 && ACPI && I2C && REGULATOR + depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) + depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) + depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) + help + This driver add support for the Intel Cherry Trail Whiskey Cove + INT33FE ACPI device found on the GPD win and the GPD pocket. + + The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2 + resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller + and PI3USB30532 USB switch. + This driver instantiates i2c-clients for these, so that standard + i2c drivers for these chips can bind to the them. + + If you enable this driver it is advised to also select + CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and + CONFIG_BATTERY_MAX17042=m. + config INTEL_ISHTP_ECLITE tristate "Intel ISHTP eclite controller Driver" depends on INTEL_ISH_HID @@ -134,6 +155,18 @@ config INTEL_RST firmware will copy the memory contents back to RAM and resume the OS as usual. +config INTEL_SDSI + tristate "Intel Software Defined Silicon Driver" + depends on INTEL_VSEC + depends on X86_64 + help + This driver enables access to the Intel Software Defined Silicon + interface used to provision silicon features with an authentication + certificate and capability license. + + To compile this driver as a module, choose M here: the module will + be called intel_sdsi. + config INTEL_SMARTCONNECT tristate "Intel Smart Connect disabling driver" depends on ACPI @@ -159,18 +192,6 @@ config INTEL_TURBO_MAX_3 This driver is only required when the system is not using Hardware P-States (HWP). In HWP mode, priority can be read from ACPI tables. -config INTEL_UNCORE_FREQ_CONTROL - tristate "Intel Uncore frequency control driver" - depends on X86_64 - help - This driver allows control of Uncore frequency limits on - supported server platforms. - - Uncore frequency controls RING/LLC (last-level cache) clocks. - - To compile this driver as a module, choose M here: the module - will be called intel-uncore-frequency. - config INTEL_VSEC tristate "Intel Vendor Specific Extended Capabilities Driver" depends on PCI diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 35f2066578b2..c61bc3e97121 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -6,13 +6,14 @@ obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ -obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ obj-$(CONFIG_INTEL_WMI) += wmi/ +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/ + # Intel input drivers intel-hid-y := hid.o @@ -26,6 +27,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o intel_oaktrail-y := oaktrail.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +intel_sdsi-y := sdsi.o +obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o intel_vsec-y := vsec.o obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o @@ -36,6 +39,8 @@ intel_crystal_cove_charger-y := crystal_cove_charger.o obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o +intel_chtwc_int33fe-y := chtwc_int33fe.o +obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o intel_punit_ipc-y := punit_ipc.o @@ -48,5 +53,3 @@ intel-smartconnect-y := smartconnect.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o intel_turbo_max_3-y := turbo_max_3.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o -intel-uncore-frequency-y := uncore-frequency.o -obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel/chtwc_int33fe.c similarity index 85% rename from drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c rename to drivers/platform/x86/intel/chtwc_int33fe.c index d59544167430..0de509fbf020 100644 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c +++ b/drivers/platform/x86/intel/chtwc_int33fe.c @@ -17,6 +17,7 @@ * for these chips can bind to the them. */ +#include #include #include #include @@ -26,7 +27,12 @@ #include #include -#include "intel_cht_int33fe_common.h" +struct cht_int33fe_data { + struct i2c_client *battery_fg; + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; + struct fwnode_handle *dp; +}; /* * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates @@ -272,15 +278,44 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) return PTR_ERR_OR_ZERO(data->battery_fg); } -int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +static const struct dmi_system_id cht_int33fe_typec_ids[] = { + { + /* + * GPD win / GPD pocket mini laptops + * + * This DMI match may not seem unique, but it is. In the 67000+ + * DMI decode dumps from linux-hardware.org only 116 have + * board_vendor set to "AMI Corporation" and of those 116 only + * the GPD win's and pocket's board_name is "Default string". + */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids); + +static int cht_int33fe_typec_probe(struct platform_device *pdev) { - struct device *dev = data->dev; struct i2c_board_info board_info; + struct device *dev = &pdev->dev; + struct cht_int33fe_data *data; struct fwnode_handle *fwnode; struct regulator *regulator; int fusb302_irq; int ret; + if (!dmi_check_system(cht_int33fe_typec_ids)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + /* * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. * We check for the bq24292i vbus regulator here, this has 2 purposes: @@ -368,8 +403,10 @@ int cht_int33fe_typec_probe(struct cht_int33fe_data *data) return ret; } -int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +static int cht_int33fe_typec_remove(struct platform_device *pdev) { + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + i2c_unregister_device(data->pi3usb30532); i2c_unregister_device(data->fusb302); i2c_unregister_device(data->battery_fg); @@ -378,3 +415,23 @@ int cht_int33fe_typec_remove(struct cht_int33fe_data *data) return 0; } + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; + +static struct platform_driver cht_int33fe_typec_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE Type-C driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_typec_probe, + .remove = cht_int33fe_typec_remove, +}; + +module_platform_driver(cht_int33fe_typec_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE Type-C pseudo device driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 13f8cf70b9ae..2def562c6e1d 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -726,12 +726,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-hid: created platform device\n"); diff --git a/drivers/platform/x86/intel/int33fe/Kconfig b/drivers/platform/x86/intel/int33fe/Kconfig deleted file mode 100644 index 2f7329a2e399..000000000000 --- a/drivers/platform/x86/intel/int33fe/Kconfig +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config INTEL_CHT_INT33FE - tristate "Intel Cherry Trail ACPI INT33FE Driver" - depends on X86 && ACPI && I2C && REGULATOR - depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) - depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) - depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) - help - This driver add support for the INT33FE ACPI device found on - some Intel Cherry Trail devices. - - There are two kinds of INT33FE ACPI device possible: for hardware - with USB Type-C and Micro-B connectors. This driver supports both. - - The INT33FE ACPI device has a CRS table with I2cSerialBusV2 - resources for Fuel Gauge Controller and (in the Type-C variant) - FUSB302 USB Type-C Controller and PI3USB30532 USB switch. - This driver instantiates i2c-clients for these, so that standard - i2c drivers for these chips can bind to the them. - - If you enable this driver it is advised to also select - CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile deleted file mode 100644 index 9456e3b37f6f..000000000000 --- a/drivers/platform/x86/intel/int33fe/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o -intel_cht_int33fe-y := intel_cht_int33fe_common.o \ - intel_cht_int33fe_typec.o \ - intel_cht_int33fe_microb.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c deleted file mode 100644 index 463222521e61..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants). - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -#define EXPECTED_PTYPE 4 - -static int cht_int33fe_check_hw_type(struct device *dev) -{ - unsigned long long ptyp; - acpi_status status; - int ret; - - status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Error getting PTYPE\n"); - return -ENODEV; - } - - /* - * The same ACPI HID is used for different configurations check PTYP - * to ensure that we are dealing with the expected config. - */ - if (ptyp != EXPECTED_PTYPE) - return -ENODEV; - - /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ - if (!acpi_dev_present("INT34D3", "1", 3)) { - dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", - EXPECTED_PTYPE); - return -ENODEV; - } - - ret = i2c_acpi_client_count(ACPI_COMPANION(dev)); - if (ret < 0) - return ret; - - switch (ret) { - case 2: - return INT33FE_HW_MICROB; - case 4: - return INT33FE_HW_TYPEC; - default: - return -ENODEV; - } -} - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct cht_int33fe_data *data; - struct device *dev = &pdev->dev; - int ret; - - ret = cht_int33fe_check_hw_type(dev); - if (ret < 0) - return ret; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->dev = dev; - - switch (ret) { - case INT33FE_HW_MICROB: - data->probe = cht_int33fe_microb_probe; - data->remove = cht_int33fe_microb_remove; - break; - - case INT33FE_HW_TYPEC: - data->probe = cht_int33fe_typec_probe; - data->remove = cht_int33fe_typec_remove; - break; - } - - platform_set_drvdata(pdev, data); - - return data->probe(data); -} - -static int cht_int33fe_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - return data->remove(data); -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; -MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); - -static struct platform_driver cht_int33fe_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_probe, - .remove = cht_int33fe_remove, -}; - -module_platform_driver(cht_int33fe_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); -MODULE_AUTHOR("Yauhen Kharuzhy "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h deleted file mode 100644 index 03cd45f4e8cb..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants), header file - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#ifndef _INTEL_CHT_INT33FE_COMMON_H -#define _INTEL_CHT_INT33FE_COMMON_H - -#include -#include -#include - -enum int33fe_hw_type { - INT33FE_HW_MICROB, - INT33FE_HW_TYPEC, -}; - -struct cht_int33fe_data { - struct device *dev; - - int (*probe)(struct cht_int33fe_data *data); - int (*remove)(struct cht_int33fe_data *data); - - struct i2c_client *battery_fg; - - /* Type-C only */ - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - - struct fwnode_handle *dp; -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data); -int cht_int33fe_microb_remove(struct cht_int33fe_data *data); -int cht_int33fe_typec_probe(struct cht_int33fe_data *data); -int cht_int33fe_typec_remove(struct cht_int33fe_data *data); - -#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c deleted file mode 100644 index 673f41cd14b5..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with - * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) - * - * Copyright (C) 2019 Yauhen Kharuzhy - * - * At least one Intel Cherry Trail based device which ship with Windows 10 - * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device - * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips - * attached to various i2c busses: - * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device - * 2. TI BQ27542 Fuel Gauge Controller - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-client for battery fuel gauge, so that standard i2c driver - * for these chip can bind to the it. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27xxx_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), - { } -}; - -static const struct software_node bq27xxx_node = { - .properties = bq27xxx_props, -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data) -{ - struct device *dev = data->dev; - struct i2c_board_info board_info; - - memset(&board_info, 0, sizeof(board_info)); - strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); - board_info.dev_name = "bq27542"; - board_info.swnode = &bq27xxx_node; - data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->battery_fg); -} - -int cht_int33fe_microb_remove(struct cht_int33fe_data *data) -{ - i2c_unregister_device(data->battery_fg); - - return 0; -} diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 5b514fa01a97..ed4c9d760757 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -112,7 +112,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 struct acpi_device *adev; acpi_handle handle; acpi_status status; - int ret; if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { dev_warn(int3472->dev, "Too many GPIOs mapped\n"); @@ -139,8 +138,8 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 if (ACPI_FAILURE(status)) return -EINVAL; - ret = acpi_bus_get_device(handle, &adev); - if (ret) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return -ENODEV; table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c new file mode 100644 index 000000000000..11d14cc0ff0a --- /dev/null +++ b/drivers/platform/x86/intel/sdsi.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Software Defined Silicon driver + * + * Copyright (c) 2022, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vsec.h" + +#define ACCESS_TYPE_BARID 2 +#define ACCESS_TYPE_LOCAL 3 + +#define SDSI_MIN_SIZE_DWORDS 276 +#define SDSI_SIZE_CONTROL 8 +#define SDSI_SIZE_MAILBOX 1024 +#define SDSI_SIZE_REGS 72 +#define SDSI_SIZE_CMD sizeof(u64) + +/* + * Write messages are currently up to the size of the mailbox + * while read messages are up to 4 times the size of the + * mailbox, sent in packets + */ +#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX +#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4) + +#define SDSI_ENABLED_FEATURES_OFFSET 16 +#define SDSI_ENABLED BIT(3) +#define SDSI_SOCKET_ID_OFFSET 64 +#define SDSI_SOCKET_ID GENMASK(3, 0) + +#define SDSI_MBOX_CMD_SUCCESS 0x40 +#define SDSI_MBOX_CMD_TIMEOUT 0x80 + +#define MBOX_TIMEOUT_US 2000 +#define MBOX_TIMEOUT_ACQUIRE_US 1000 +#define MBOX_POLLING_PERIOD_US 100 +#define MBOX_MAX_PACKETS 4 + +#define MBOX_OWNER_NONE 0x00 +#define MBOX_OWNER_INBAND 0x01 + +#define CTRL_RUN_BUSY BIT(0) +#define CTRL_READ_WRITE BIT(1) +#define CTRL_SOM BIT(2) +#define CTRL_EOM BIT(3) +#define CTRL_OWNER GENMASK(5, 4) +#define CTRL_COMPLETE BIT(6) +#define CTRL_READY BIT(7) +#define CTRL_STATUS GENMASK(15, 8) +#define CTRL_PACKET_SIZE GENMASK(31, 16) +#define CTRL_MSG_SIZE GENMASK(63, 48) + +#define DISC_TABLE_SIZE 12 +#define DT_ACCESS_TYPE GENMASK(3, 0) +#define DT_SIZE GENMASK(27, 12) +#define DT_TBIR GENMASK(2, 0) +#define DT_OFFSET(v) ((v) & GENMASK(31, 3)) + +enum sdsi_command { + SDSI_CMD_PROVISION_AKC = 0x04, + SDSI_CMD_PROVISION_CAP = 0x08, + SDSI_CMD_READ_STATE = 0x10, +}; + +struct sdsi_mbox_info { + u64 *payload; + u64 *buffer; + int size; +}; + +struct disc_table { + u32 access_info; + u32 guid; + u32 offset; +}; + +struct sdsi_priv { + struct mutex mb_lock; /* Mailbox access lock */ + struct device *dev; + void __iomem *control_addr; + void __iomem *mbox_addr; + void __iomem *regs_addr; + u32 guid; + bool sdsi_enabled; +}; + +/* SDSi mailbox operations must be performed using 64bit mov instructions */ +static __always_inline void +sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + writeq(from[i], &to[i]); +} + +static __always_inline void +sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + to[i] = readq(&from[i]); +} + +static inline void sdsi_complete_transaction(struct sdsi_priv *priv) +{ + u64 control = FIELD_PREP(CTRL_COMPLETE, 1); + + lockdep_assert_held(&priv->mb_lock); + writeq(control, priv->control_addr); +} + +static int sdsi_status_to_errno(u32 status) +{ + switch (status) { + case SDSI_MBOX_CMD_SUCCESS: + return 0; + case SDSI_MBOX_CMD_TIMEOUT: + return -ETIMEDOUT; + default: + return -EIO; + } +} + +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) +{ + struct device *dev = priv->dev; + u32 total, loop, eom, status, message_size; + u64 control; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Format and send the read command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* For reads, data sizes that are larger than the mailbox size are read in packets. */ + total = 0; + loop = 0; + do { + int offset = SDSI_SIZE_MAILBOX * loop; + void __iomem *addr = priv->mbox_addr + offset; + u64 *buf = info->buffer + offset / SDSI_SIZE_CMD; + u32 packet_size; + + /* Poll on ready bit */ + ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + if (ret) + break; + + eom = FIELD_GET(CTRL_EOM, control); + status = FIELD_GET(CTRL_STATUS, control); + packet_size = FIELD_GET(CTRL_PACKET_SIZE, control); + message_size = FIELD_GET(CTRL_MSG_SIZE, control); + + ret = sdsi_status_to_errno(status); + if (ret) + break; + + /* Only the last packet can be less than the mailbox size. */ + if (!eom && packet_size != SDSI_SIZE_MAILBOX) { + dev_err(dev, "Invalid packet size\n"); + ret = -EPROTO; + break; + } + + if (packet_size > SDSI_SIZE_MAILBOX) { + dev_err(dev, "Packet size too large\n"); + ret = -EPROTO; + break; + } + + sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD)); + + total += packet_size; + + sdsi_complete_transaction(priv); + } while (!eom && ++loop < MBOX_MAX_PACKETS); + + if (ret) { + sdsi_complete_transaction(priv); + return ret; + } + + if (!eom) { + dev_err(dev, "Exceeded read attempts\n"); + return -EPROTO; + } + + /* Message size check is only valid for multi-packet transfers */ + if (loop && total != message_size) + dev_warn(dev, "Read count %u differs from expected count %u\n", + total, message_size); + + *data_size = total; + + return 0; +} + +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 status; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Write rest of the payload */ + sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1, + info->size - SDSI_SIZE_CMD); + + /* Format and send the write command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_READ_WRITE, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* Poll on run_busy bit */ + ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY), + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + + if (ret) + goto release_mbox; + + status = FIELD_GET(CTRL_STATUS, control); + ret = sdsi_status_to_errno(status); + +release_mbox: + sdsi_complete_transaction(priv); + + return ret; +} + +static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 owner; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Check mailbox is available */ + control = readq(priv->control_addr); + owner = FIELD_GET(CTRL_OWNER, control); + if (owner != MBOX_OWNER_NONE) + return -EBUSY; + + /* Write first qword of payload */ + writeq(info->payload[0], priv->mbox_addr); + + /* Check for ownership */ + ret = readq_poll_timeout(priv->control_addr, control, + FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US); + + return ret; +} + +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_write(priv, info); +} + +static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_read(priv, info, data_size); +} + +static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, + enum sdsi_command command) +{ + struct sdsi_mbox_info info; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) + return -EOVERFLOW; + + /* Qword aligned message + command qword */ + info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; + + info.payload = kzalloc(info.size, GFP_KERNEL); + if (!info.payload) + return -ENOMEM; + + /* Copy message to payload buffer */ + memcpy(info.payload, buf, count); + + /* Command is last qword of payload buffer */ + info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command; + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_payload; + ret = sdsi_mbox_write(priv, &info); + mutex_unlock(&priv->mb_lock); + +free_payload: + kfree(info.payload); + + if (ret) + return ret; + + return count; +} + +static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC); +} +static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG); + +static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP); +} +static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); + +static long state_certificate_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + u64 command = SDSI_CMD_READ_STATE; + struct sdsi_mbox_info info; + size_t size; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (off) + return 0; + + /* Buffer for return data */ + info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL); + if (!info.buffer) + return -ENOMEM; + + info.payload = &command; + info.size = sizeof(command); + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_buffer; + ret = sdsi_mbox_read(priv, &info, &size); + mutex_unlock(&priv->mb_lock); + if (ret < 0) + goto free_buffer; + + if (size > count) + size = count; + + memcpy(buf, info.buffer, size); + +free_buffer: + kfree(info.buffer); + + if (ret) + return ret; + + return size; +} +static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG); + +static ssize_t registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + void __iomem *addr = priv->regs_addr; + + memcpy_fromio(buf, addr + off, count); + + return count; +} +static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS); + +static struct bin_attribute *sdsi_bin_attrs[] = { + &bin_attr_registers, + &bin_attr_state_certificate, + &bin_attr_provision_akc, + &bin_attr_provision_cap, + NULL +}; + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdsi_priv *priv = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%x\n", priv->guid); +} +static DEVICE_ATTR_RO(guid); + +static struct attribute *sdsi_attrs[] = { + &dev_attr_guid.attr, + NULL +}; + +static const struct attribute_group sdsi_group = { + .attrs = sdsi_attrs, + .bin_attrs = sdsi_bin_attrs, +}; +__ATTRIBUTE_GROUPS(sdsi); + +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, + struct disc_table *disc_table, struct resource *disc_res) +{ + u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); + u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); + u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); + u32 offset = DT_OFFSET(disc_table->offset); + u32 features_offset; + struct resource res = {}; + + /* Starting location of SDSi MMIO region based on access type */ + switch (access_type) { + case ACCESS_TYPE_LOCAL: + if (tbir) { + dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n", + tbir, access_type); + return -EINVAL; + } + + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + 1 + */ + res.start = disc_res->end + offset + 1; + break; + + case ACCESS_TYPE_BARID: + res.start = pci_resource_start(parent, tbir) + offset; + break; + + default: + dev_err(priv->dev, "Unrecognized access_type %u\n", access_type); + return -EINVAL; + } + + res.end = res.start + size * sizeof(u32) - 1; + res.flags = IORESOURCE_MEM; + + priv->control_addr = devm_ioremap_resource(priv->dev, &res); + if (IS_ERR(priv->control_addr)) + return PTR_ERR(priv->control_addr); + + priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL; + priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX; + + features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET); + priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED); + + return 0; +} + +static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) +{ + struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev); + struct disc_table disc_table; + struct resource *disc_res; + void __iomem *disc_addr; + struct sdsi_priv *priv; + int ret; + + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &auxdev->dev; + mutex_init(&priv->mb_lock); + auxiliary_set_drvdata(auxdev, priv); + + /* Get the SDSi discovery table */ + disc_res = &intel_cap_dev->resource[0]; + disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res); + if (IS_ERR(disc_addr)) + return PTR_ERR(disc_addr); + + memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE); + + priv->guid = disc_table.guid; + + /* Map the SDSi mailbox registers */ + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); + if (ret) + return ret; + + return 0; +} + +static const struct auxiliary_device_id sdsi_aux_id_table[] = { + { .name = "intel_vsec.sdsi" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table); + +static struct auxiliary_driver sdsi_aux_driver = { + .driver = { + .dev_groups = sdsi_groups, + }, + .id_table = sdsi_aux_id_table, + .probe = sdsi_probe, + /* No remove. All resources are handled under devm */ +}; +module_auxiliary_driver(sdsi_aux_driver); + +MODULE_AUTHOR("David E. Box "); +MODULE_DESCRIPTION("Intel Software Defined Silicon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency.c deleted file mode 100644 index 4cd8254f2e40..000000000000 --- a/drivers/platform/x86/intel/uncore-frequency.c +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Uncore Frequency Setting - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Provide interface to set MSR 620 at a granularity of per die. On CPU online, - * one control CPU is identified per die to read/write limit. This control CPU - * is changed, if the CPU state is changed to offline. When the last CPU is - * offline in a die then remove the sysfs object for that die. - * The majority of actual code is related to sysfs create and read/write - * attributes. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include - -#define MSR_UNCORE_RATIO_LIMIT 0x620 -#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 - -/** - * struct uncore_data - Encapsulate all uncore data - * @stored_uncore_data: Last user changed MSR 620 value, which will be restored - * on system resume. - * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init - * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init - * @control_cpu: Designated CPU for a die to read/write - * @valid: Mark the data valid/invalid - * - * This structure is used to encapsulate all data related to uncore sysfs - * settings for a die/package. - */ -struct uncore_data { - struct kobject kobj; - struct completion kobj_unregister; - u64 stored_uncore_data; - u32 initial_min_freq_khz; - u32 initial_max_freq_khz; - int control_cpu; - bool valid; -}; - -#define to_uncore_data(a) container_of(a, struct uncore_data, kobj) - -/* Max instances for uncore data, one for each die */ -static int uncore_max_entries __read_mostly; -/* Storage for uncore data for all instances */ -static struct uncore_data *uncore_instances; -/* Root of the all uncore sysfs kobjs */ -static struct kobject *uncore_root_kobj; -/* Stores the CPU mask of the target CPUs to use during uncore read/write */ -static cpumask_t uncore_cpu_mask; -/* CPU online callback register instance */ -static enum cpuhp_state uncore_hp_state __read_mostly; -/* Mutex to control all mutual exclusions */ -static DEFINE_MUTEX(uncore_lock); - -struct uncore_attr { - struct attribute attr; - ssize_t (*show)(struct kobject *kobj, - struct attribute *attr, char *buf); - ssize_t (*store)(struct kobject *kobj, - struct attribute *attr, const char *c, ssize_t count); -}; - -#define define_one_uncore_ro(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0444, show_##_name, NULL) - -#define define_one_uncore_rw(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0644, show_##_name, store_##_name) - -#define show_uncore_data(member_name) \ - static ssize_t show_##member_name(struct kobject *kobj, \ - struct attribute *attr, \ - char *buf) \ - { \ - struct uncore_data *data = to_uncore_data(kobj); \ - return scnprintf(buf, PAGE_SIZE, "%u\n", \ - data->member_name); \ - } \ - define_one_uncore_ro(member_name) - -show_uncore_data(initial_min_freq_khz); -show_uncore_data(initial_max_freq_khz); - -/* Common function to read MSR 0x620 and read min/max */ -static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, - unsigned int *max) -{ - u64 cap; - int ret; - - if (data->control_cpu < 0) - return -ENXIO; - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - return ret; - - *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; - *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; - - return 0; -} - -/* Common function to set min/max ratios to be used by sysfs callbacks */ -static int uncore_write_ratio(struct uncore_data *data, unsigned int input, - int set_max) -{ - int ret; - u64 cap; - - mutex_lock(&uncore_lock); - - if (data->control_cpu < 0) { - ret = -ENXIO; - goto finish_write; - } - - input /= UNCORE_FREQ_KHZ_MULTIPLIER; - if (!input || input > 0x7F) { - ret = -EINVAL; - goto finish_write; - } - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - goto finish_write; - - if (set_max) { - cap &= ~0x7F; - cap |= input; - } else { - cap &= ~GENMASK(14, 8); - cap |= (input << 8); - } - - ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); - if (ret) - goto finish_write; - - data->stored_uncore_data = cap; - -finish_write: - mutex_unlock(&uncore_lock); - - return ret; -} - -static ssize_t store_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - const char *buf, ssize_t count, - int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int input; - - if (kstrtouint(buf, 10, &input)) - return -EINVAL; - - uncore_write_ratio(data, input, min_max); - - return count; -} - -static ssize_t show_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - char *buf, int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int min, max; - int ret; - - mutex_lock(&uncore_lock); - ret = uncore_read_ratio(data, &min, &max); - mutex_unlock(&uncore_lock); - if (ret) - return ret; - - if (min_max) - return sprintf(buf, "%u\n", max); - - return sprintf(buf, "%u\n", min); -} - -#define store_uncore_min_max(name, min_max) \ - static ssize_t store_##name(struct kobject *kobj, \ - struct attribute *attr, \ - const char *buf, ssize_t count) \ - { \ - \ - return store_min_max_freq_khz(kobj, attr, buf, count, \ - min_max); \ - } - -#define show_uncore_min_max(name, min_max) \ - static ssize_t show_##name(struct kobject *kobj, \ - struct attribute *attr, char *buf) \ - { \ - \ - return show_min_max_freq_khz(kobj, attr, buf, min_max); \ - } - -store_uncore_min_max(min_freq_khz, 0); -store_uncore_min_max(max_freq_khz, 1); - -show_uncore_min_max(min_freq_khz, 0); -show_uncore_min_max(max_freq_khz, 1); - -define_one_uncore_rw(min_freq_khz); -define_one_uncore_rw(max_freq_khz); - -static struct attribute *uncore_attrs[] = { - &initial_min_freq_khz.attr, - &initial_max_freq_khz.attr, - &max_freq_khz.attr, - &min_freq_khz.attr, - NULL -}; -ATTRIBUTE_GROUPS(uncore); - -static void uncore_sysfs_entry_release(struct kobject *kobj) -{ - struct uncore_data *data = to_uncore_data(kobj); - - complete(&data->kobj_unregister); -} - -static struct kobj_type uncore_ktype = { - .release = uncore_sysfs_entry_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_groups = uncore_groups, -}; - -/* Caller provides protection */ -static struct uncore_data *uncore_get_instance(unsigned int cpu) -{ - int id = topology_logical_die_id(cpu); - - if (id >= 0 && id < uncore_max_entries) - return &uncore_instances[id]; - - return NULL; -} - -static void uncore_add_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (!data) { - mutex_unlock(&uncore_lock); - return; - } - - if (data->valid) { - /* control cpu changed */ - data->control_cpu = cpu; - } else { - char str[64]; - int ret; - - memset(data, 0, sizeof(*data)); - sprintf(str, "package_%02d_die_%02d", - topology_physical_package_id(cpu), - topology_die_id(cpu)); - - uncore_read_ratio(data, &data->initial_min_freq_khz, - &data->initial_max_freq_khz); - - init_completion(&data->kobj_unregister); - - ret = kobject_init_and_add(&data->kobj, &uncore_ktype, - uncore_root_kobj, str); - if (!ret) { - data->control_cpu = cpu; - data->valid = true; - } - } - mutex_unlock(&uncore_lock); -} - -/* Last CPU in this die is offline, make control cpu invalid */ -static void uncore_remove_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (data) - data->control_cpu = -1; - mutex_unlock(&uncore_lock); -} - -static int uncore_event_cpu_online(unsigned int cpu) -{ - int target; - - /* Check if there is an online cpu in the package for uncore MSR */ - target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); - if (target < nr_cpu_ids) - return 0; - - /* Use this CPU on this die as a control CPU */ - cpumask_set_cpu(cpu, &uncore_cpu_mask); - uncore_add_die_entry(cpu); - - return 0; -} - -static int uncore_event_cpu_offline(unsigned int cpu) -{ - int target; - - /* Check if existing cpu is used for uncore MSRs */ - if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) - return 0; - - /* Find a new cpu to set uncore MSR */ - target = cpumask_any_but(topology_die_cpumask(cpu), cpu); - - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &uncore_cpu_mask); - uncore_add_die_entry(target); - } else { - uncore_remove_die_entry(cpu); - } - - return 0; -} - -static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, - void *_unused) -{ - int cpu; - - switch (mode) { - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - case PM_POST_SUSPEND: - for_each_cpu(cpu, &uncore_cpu_mask) { - struct uncore_data *data; - int ret; - - data = uncore_get_instance(cpu); - if (!data || !data->valid || !data->stored_uncore_data) - continue; - - ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT, - data->stored_uncore_data); - if (ret) - return ret; - } - break; - default: - break; - } - return 0; -} - -static struct notifier_block uncore_pm_nb = { - .notifier_call = uncore_pm_notify, -}; - -static const struct x86_cpu_id intel_uncore_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), - {} -}; - -static int __init intel_uncore_init(void) -{ - const struct x86_cpu_id *id; - int ret; - - id = x86_match_cpu(intel_uncore_cpu_ids); - if (!id) - return -ENODEV; - - uncore_max_entries = topology_max_packages() * - topology_max_die_per_package(); - uncore_instances = kcalloc(uncore_max_entries, - sizeof(*uncore_instances), GFP_KERNEL); - if (!uncore_instances) - return -ENOMEM; - - uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", - &cpu_subsys.dev_root->kobj); - if (!uncore_root_kobj) { - ret = -ENOMEM; - goto err_free; - } - - ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, - "platform/x86/uncore-freq:online", - uncore_event_cpu_online, - uncore_event_cpu_offline); - if (ret < 0) - goto err_rem_kobj; - - uncore_hp_state = ret; - - ret = register_pm_notifier(&uncore_pm_nb); - if (ret) - goto err_rem_state; - - return 0; - -err_rem_state: - cpuhp_remove_state(uncore_hp_state); -err_rem_kobj: - kobject_put(uncore_root_kobj); -err_free: - kfree(uncore_instances); - - return ret; -} -module_init(intel_uncore_init) - -static void __exit intel_uncore_exit(void) -{ - int i; - - unregister_pm_notifier(&uncore_pm_nb); - cpuhp_remove_state(uncore_hp_state); - for (i = 0; i < uncore_max_entries; ++i) { - if (uncore_instances[i].valid) { - kobject_put(&uncore_instances[i].kobj); - wait_for_completion(&uncore_instances[i].kobj_unregister); - } - } - kobject_put(uncore_root_kobj); - kfree(uncore_instances); -} -module_exit(intel_uncore_exit) - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); diff --git a/drivers/platform/x86/intel/uncore-frequency/Kconfig b/drivers/platform/x86/intel/uncore-frequency/Kconfig new file mode 100644 index 000000000000..21b209124916 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Uncore Frquency control drivers +# + +menu "Intel Uncore Frequency Control" + depends on X86_64 || COMPILE_TEST + +config INTEL_UNCORE_FREQ_CONTROL + tristate "Intel Uncore frequency control driver" + depends on X86_64 + help + This driver allows control of Uncore frequency limits on + supported server platforms. + + Uncore frequency controls RING/LLC (last-level cache) clocks. + + To compile this driver as a module, choose M here: the module + will be called intel-uncore-frequency. + +endmenu diff --git a/drivers/platform/x86/intel/uncore-frequency/Makefile b/drivers/platform/x86/intel/uncore-frequency/Makefile new file mode 100644 index 000000000000..e0f7968e8285 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/intel/uncore-frequency +# + +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o +intel-uncore-frequency-y := uncore-frequency.o +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o +intel-uncore-frequency-common-y := uncore-frequency-common.o diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c new file mode 100644 index 000000000000..84eabd6156bb --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Control: Common code implementation + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ +#include +#include +#include "uncore-frequency-common.h" + +/* Mutex to control all mutual exclusions */ +static DEFINE_MUTEX(uncore_lock); +/* Root of the all uncore sysfs kobjs */ +static struct kobject *uncore_root_kobj; +/* uncore instance count */ +static int uncore_instance_count; + +/* callbacks for actual HW read/write */ +static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max); +static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max); +static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq); + +static ssize_t show_min_max_freq_khz(struct uncore_data *data, + char *buf, int min_max) +{ + unsigned int min, max; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read(data, &min, &max); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + if (min_max) + return sprintf(buf, "%u\n", max); + + return sprintf(buf, "%u\n", min); +} + +static ssize_t store_min_max_freq_khz(struct uncore_data *data, + const char *buf, ssize_t count, + int min_max) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&uncore_lock); + uncore_write(data, input, min_max); + mutex_unlock(&uncore_lock); + + return count; +} + +static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) +{ + unsigned int freq; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read_freq(data, &freq); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + return sprintf(buf, "%u\n", freq); +} + +#define store_uncore_min_max(name, min_max) \ + static ssize_t store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return store_min_max_freq_khz(data, buf, count, \ + min_max); \ + } + +#define show_uncore_min_max(name, min_max) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_min_max_freq_khz(data, buf, min_max); \ + } + +#define show_uncore_perf_status(name) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_perf_status_freq_khz(data, buf); \ + } + +store_uncore_min_max(min_freq_khz, 0); +store_uncore_min_max(max_freq_khz, 1); + +show_uncore_min_max(min_freq_khz, 0); +show_uncore_min_max(max_freq_khz, 1); + +show_uncore_perf_status(current_freq_khz); + +#define show_uncore_data(member_name) \ + static ssize_t show_##member_name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data,\ + member_name##_dev_attr);\ + \ + return scnprintf(buf, PAGE_SIZE, "%u\n", \ + data->member_name); \ + } \ + +show_uncore_data(initial_min_freq_khz); +show_uncore_data(initial_max_freq_khz); + +#define init_attribute_rw(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = store_##_name; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0644; \ + } while (0) + +#define init_attribute_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0444; \ + } while (0) + +#define init_attribute_root_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0400; \ + } while (0) + +static int create_attr_group(struct uncore_data *data, char *name) +{ + int ret, index = 0; + + init_attribute_rw(max_freq_khz); + init_attribute_rw(min_freq_khz); + init_attribute_ro(initial_min_freq_khz); + init_attribute_ro(initial_max_freq_khz); + init_attribute_root_ro(current_freq_khz); + + data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr; + data->uncore_attrs[index] = NULL; + + data->uncore_attr_group.name = name; + data->uncore_attr_group.attrs = data->uncore_attrs; + ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); + + return ret; +} + +static void delete_attr_group(struct uncore_data *data, char *name) +{ + sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); +} + +int uncore_freq_add_entry(struct uncore_data *data, int cpu) +{ + int ret = 0; + + mutex_lock(&uncore_lock); + if (data->valid) { + /* control cpu changed */ + data->control_cpu = cpu; + goto uncore_unlock; + } + + sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id); + + uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz); + + ret = create_attr_group(data, data->name); + if (!ret) { + data->control_cpu = cpu; + data->valid = true; + } + +uncore_unlock: + mutex_unlock(&uncore_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_remove_die_entry(struct uncore_data *data) +{ + mutex_lock(&uncore_lock); + delete_attr_group(data, data->name); + data->control_cpu = -1; + data->valid = false; + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY); + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max), + int (*read_freq)(struct uncore_data *data, unsigned int *freq)) +{ + mutex_lock(&uncore_lock); + + uncore_read = read_control_freq; + uncore_write = write_control_freq; + uncore_read_freq = read_freq; + + if (!uncore_root_kobj) + uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", + &cpu_subsys.dev_root->kobj); + if (uncore_root_kobj) + ++uncore_instance_count; + mutex_unlock(&uncore_lock); + + return uncore_root_kobj ? 0 : -ENOMEM; +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_common_exit(void) +{ + mutex_lock(&uncore_lock); + --uncore_instance_count; + if (!uncore_instance_count) { + kobject_put(uncore_root_kobj); + uncore_root_kobj = NULL; + } + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY); + + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Common Module"); diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h new file mode 100644 index 000000000000..f5dcfa2fb285 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Uncore Frequency Control: Common defines and prototypes + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ + +#ifndef __INTEL_UNCORE_FREQ_COMMON_H +#define __INTEL_UNCORE_FREQ_COMMON_H + +#include + +/** + * struct uncore_data - Encapsulate all uncore data + * @stored_uncore_data: Last user changed MSR 620 value, which will be restored + * on system resume. + * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init + * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init + * @control_cpu: Designated CPU for a die to read/write + * @valid: Mark the data valid/invalid + * @package_id: Package id for this instance + * @die_id: Die id for this instance + * @name: Sysfs entry name for this instance + * @uncore_attr_group: Attribute group storage + * @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz + * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz + * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz + * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz + * @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz + * @uncore_attrs: Attribute storage for group creation + * + * This structure is used to encapsulate all data related to uncore sysfs + * settings for a die/package. + */ +struct uncore_data { + u64 stored_uncore_data; + u32 initial_min_freq_khz; + u32 initial_max_freq_khz; + int control_cpu; + bool valid; + int package_id; + int die_id; + char name[32]; + + struct attribute_group uncore_attr_group; + struct device_attribute max_freq_khz_dev_attr; + struct device_attribute min_freq_khz_dev_attr; + struct device_attribute initial_max_freq_khz_dev_attr; + struct device_attribute initial_min_freq_khz_dev_attr; + struct device_attribute current_freq_khz_dev_attr; + struct attribute *uncore_attrs[6]; +}; + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max), + int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq)); +void uncore_freq_common_exit(void); +int uncore_freq_add_entry(struct uncore_data *data, int cpu); +void uncore_freq_remove_die_entry(struct uncore_data *data); + +#endif diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c new file mode 100644 index 000000000000..c61f804dd44e --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Setting + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + * Provide interface to set MSR 620 at a granularity of per die. On CPU online, + * one control CPU is identified per die to read/write limit. This control CPU + * is changed, if the CPU state is changed to offline. When the last CPU is + * offline in a die then remove the sysfs object for that die. + * The majority of actual code is related to sysfs create and read/write + * attributes. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include + +#include "uncore-frequency-common.h" + +/* Max instances for uncore data, one for each die */ +static int uncore_max_entries __read_mostly; +/* Storage for uncore data for all instances */ +static struct uncore_data *uncore_instances; +/* Stores the CPU mask of the target CPUs to use during uncore read/write */ +static cpumask_t uncore_cpu_mask; +/* CPU online callback register instance */ +static enum cpuhp_state uncore_hp_state __read_mostly; + +#define MSR_UNCORE_RATIO_LIMIT 0x620 +#define MSR_UNCORE_PERF_STATUS 0x621 +#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 + +static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min, + unsigned int *max) +{ + u64 cap; + int ret; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + return ret; + + *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +static int uncore_write_control_freq(struct uncore_data *data, unsigned int input, + unsigned int min_max) +{ + int ret; + u64 cap; + + input /= UNCORE_FREQ_KHZ_MULTIPLIER; + if (!input || input > 0x7F) + return -EINVAL; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + return ret; + + if (min_max) { + cap &= ~0x7F; + cap |= input; + } else { + cap &= ~GENMASK(14, 8); + cap |= (input << 8); + } + + ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); + if (ret) + return ret; + + data->stored_uncore_data = cap; + + return 0; +} + +static int uncore_read_freq(struct uncore_data *data, unsigned int *freq) +{ + u64 ratio; + int ret; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio); + if (ret) + return ret; + + *freq = (ratio & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +/* Caller provides protection */ +static struct uncore_data *uncore_get_instance(unsigned int cpu) +{ + int id = topology_logical_die_id(cpu); + + if (id >= 0 && id < uncore_max_entries) + return &uncore_instances[id]; + + return NULL; +} + +static int uncore_event_cpu_online(unsigned int cpu) +{ + struct uncore_data *data; + int target; + + /* Check if there is an online cpu in the package for uncore MSR */ + target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); + if (target < nr_cpu_ids) + return 0; + + /* Use this CPU on this die as a control CPU */ + cpumask_set_cpu(cpu, &uncore_cpu_mask); + + data = uncore_get_instance(cpu); + if (!data) + return 0; + + data->package_id = topology_physical_package_id(cpu); + data->die_id = topology_die_id(cpu); + + return uncore_freq_add_entry(data, cpu); +} + +static int uncore_event_cpu_offline(unsigned int cpu) +{ + struct uncore_data *data; + int target; + + data = uncore_get_instance(cpu); + if (!data) + return 0; + + /* Check if existing cpu is used for uncore MSRs */ + if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) + return 0; + + /* Find a new cpu to set uncore MSR */ + target = cpumask_any_but(topology_die_cpumask(cpu), cpu); + + if (target < nr_cpu_ids) { + cpumask_set_cpu(target, &uncore_cpu_mask); + uncore_freq_add_entry(data, target); + } else { + uncore_freq_remove_die_entry(data); + } + + return 0; +} + +static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, + void *_unused) +{ + int i; + + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + for (i = 0; i < uncore_max_entries; ++i) { + struct uncore_data *data = &uncore_instances[i]; + + if (!data || !data->valid || !data->stored_uncore_data) + return 0; + + wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, + data->stored_uncore_data); + } + break; + default: + break; + } + return 0; +} + +static struct notifier_block uncore_pm_nb = { + .notifier_call = uncore_pm_notify, +}; + +static const struct x86_cpu_id intel_uncore_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids); + +static int __init intel_uncore_init(void) +{ + const struct x86_cpu_id *id; + int ret; + + id = x86_match_cpu(intel_uncore_cpu_ids); + if (!id) + return -ENODEV; + + uncore_max_entries = topology_max_packages() * + topology_max_die_per_package(); + uncore_instances = kcalloc(uncore_max_entries, + sizeof(*uncore_instances), GFP_KERNEL); + if (!uncore_instances) + return -ENOMEM; + + ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq, + uncore_read_freq); + if (ret) + goto err_free; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "platform/x86/uncore-freq:online", + uncore_event_cpu_online, + uncore_event_cpu_offline); + if (ret < 0) + goto err_rem_kobj; + + uncore_hp_state = ret; + + ret = register_pm_notifier(&uncore_pm_nb); + if (ret) + goto err_rem_state; + + return 0; + +err_rem_state: + cpuhp_remove_state(uncore_hp_state); +err_rem_kobj: + uncore_freq_common_exit(); +err_free: + kfree(uncore_instances); + + return ret; +} +module_init(intel_uncore_init) + +static void __exit intel_uncore_exit(void) +{ + int i; + + unregister_pm_notifier(&uncore_pm_nb); + cpuhp_remove_state(uncore_hp_state); + for (i = 0; i < uncore_max_entries; ++i) + uncore_freq_remove_die_entry(&uncore_instances[i]); + uncore_freq_common_exit(); + kfree(uncore_instances); +} +module_exit(intel_uncore_exit) + +MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 15f013af9e62..c5e4e35c8d20 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -384,12 +384,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-vbtn: created platform device\n"); diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index c3bdd75ed690..bed436bf181f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -32,6 +32,7 @@ #define TABLE_OFFSET_SHIFT 3 static DEFINE_IDA(intel_vsec_ida); +static DEFINE_IDA(intel_vsec_sdsi_ida); /** * struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers. @@ -63,12 +64,14 @@ enum intel_vsec_id { VSEC_ID_TELEMETRY = 2, VSEC_ID_WATCHER = 3, VSEC_ID_CRASHLOG = 4, + VSEC_ID_SDSI = 65, }; static enum intel_vsec_id intel_vsec_allow_list[] = { VSEC_ID_TELEMETRY, VSEC_ID_WATCHER, VSEC_ID_CRASHLOG, + VSEC_ID_SDSI, }; static const char *intel_vsec_name(enum intel_vsec_id id) @@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) case VSEC_ID_CRASHLOG: return "crashlog"; + case VSEC_ID_SDSI: + return "sdsi"; + default: return NULL; } @@ -211,7 +217,11 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_dev->resource = res; intel_vsec_dev->num_resources = header->num_entries; intel_vsec_dev->quirks = quirks; - intel_vsec_dev->ida = &intel_vsec_ida; + + if (header->id == VSEC_ID_SDSI) + intel_vsec_dev->ida = &intel_vsec_sdsi_ida; + else + intel_vsec_dev->ida = &intel_vsec_ida; return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id)); } diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index a91847a551a7..332868b140ed 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -17,6 +17,8 @@ #include #include +#include + #define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \ .name = __stringify(_name), \ .max_brightness = max, \ @@ -458,14 +460,14 @@ static ssize_t fn_lock_show(struct device *dev, return sysfs_emit(buffer, "%d\n", status); } -static ssize_t battery_care_limit_store(struct device *dev, - struct device_attribute *attr, - const char *buffer, size_t count) +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { unsigned long value; int ret; - ret = kstrtoul(buffer, 10, &value); + ret = kstrtoul(buf, 10, &value); if (ret) return ret; @@ -486,9 +488,9 @@ static ssize_t battery_care_limit_store(struct device *dev, return -EINVAL; } -static ssize_t battery_care_limit_show(struct device *dev, - struct device_attribute *attr, - char *buffer) +static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) { unsigned int status; union acpi_object *r; @@ -520,15 +522,52 @@ static ssize_t battery_care_limit_show(struct device *dev, if (status != 80 && status != 100) status = 0; - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buf, "%d\n", status); +} + +static ssize_t battery_care_limit_show(struct device *dev, + struct device_attribute *attr, + char *buffer) +{ + return charge_control_end_threshold_show(dev, attr, buffer); +} + +static ssize_t battery_care_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + return charge_control_end_threshold_store(dev, attr, buffer, count); } static DEVICE_ATTR_RW(fan_mode); static DEVICE_ATTR_RW(usb_charge); static DEVICE_ATTR_RW(reader_mode); static DEVICE_ATTR_RW(fn_lock); +static DEVICE_ATTR_RW(charge_control_end_threshold); static DEVICE_ATTR_RW(battery_care_limit); +static int lg_battery_add(struct power_supply *battery) +{ + if (device_create_file(&battery->dev, + &dev_attr_charge_control_end_threshold)) + return -ENODEV; + + return 0; +} + +static int lg_battery_remove(struct power_supply *battery) +{ + device_remove_file(&battery->dev, + &dev_attr_charge_control_end_threshold); + return 0; +} + +static struct acpi_battery_hook battery_hook = { + .add_battery = lg_battery_add, + .remove_battery = lg_battery_remove, + .name = "LG Battery Extension", +}; + static struct attribute *dev_attributes[] = { &dev_attr_fan_mode.attr, &dev_attr_usb_charge.attr, @@ -711,6 +750,7 @@ static int acpi_add(struct acpi_device *device) led_classdev_register(&pf_device->dev, &tpad_led); wmi_input_setup(); + battery_hook_register(&battery_hook); return 0; @@ -728,6 +768,7 @@ static int acpi_remove(struct acpi_device *device) led_classdev_unregister(&tpad_led); led_classdev_unregister(&kbd_backlight); + battery_hook_unregister(&battery_hook); wmi_input_destroy(); platform_device_unregister(pf_device); pf_device = NULL; diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 0b73e16cccea..bce17ca97947 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "firmware_attributes_class.h" #include "think-lmi.h" @@ -25,95 +26,66 @@ module_param(debug_support, bool, 0444); MODULE_PARM_DESC(debug_support, "Enable debug command support"); /* - * Name: - * Lenovo_BiosSetting - * Description: - * Get item name and settings for current LMI instance. - * Type: - * Query - * Returns: - * "Item,Value" - * Example: - * "WakeOnLAN,Enable" + * Name: BiosSetting + * Description: Get item name and settings for current LMI instance. + * Type: Query + * Returns: "Item,Value" + * Example: "WakeOnLAN,Enable" */ #define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7" /* - * Name: - * Lenovo_SetBiosSetting - * Description: - * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting - * class. To save the settings, use the Lenovo_SaveBiosSetting class. + * Name: SetBiosSetting + * Description: Change the BIOS setting to the desired value using the SetBiosSetting + * class. To save the settings, use the SaveBiosSetting class. * BIOS settings and values are case sensitive. * After making changes to the BIOS settings, you must reboot the computer * before the changes will take effect. - * Type: - * Method - * Arguments: - * "Item,Value,Password,Encoding,KbdLang;" - * Example: - * "WakeOnLAN,Disable,pa55w0rd,ascii,us;" + * Type: Method + * Arguments: "Item,Value,Password,Encoding,KbdLang;" + * Example: "WakeOnLAN,Disable,pa55w0rd,ascii,us;" */ #define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1" /* - * Name: - * Lenovo_SaveBiosSettings - * Description: - * Save any pending changes in settings. - * Type: - * Method - * Arguments: - * "Password,Encoding,KbdLang;" - * Example: - * "pa55w0rd,ascii,us;" + * Name: SaveBiosSettings + * Description: Save any pending changes in settings. + * Type: Method + * Arguments: "Password,Encoding,KbdLang;" + * Example: "pa55w0rd,ascii,us;" */ #define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3" /* - * Name: - * Lenovo_BiosPasswordSettings - * Description: - * Return BIOS Password settings - * Type: - * Query - * Returns: - * PasswordMode, PasswordState, MinLength, MaxLength, + * Name: BiosPasswordSettings + * Description: Return BIOS Password settings + * Type: Query + * Returns: PasswordMode, PasswordState, MinLength, MaxLength, * SupportedEncoding, SupportedKeyboard */ #define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246" /* - * Name: - * Lenovo_SetBiosPassword - * Description: - * Change a specific password. + * Name: SetBiosPassword + * Description: Change a specific password. * - BIOS settings cannot be changed at the same boot as power-on * passwords (POP) and hard disk passwords (HDP). If you want to change * BIOS settings and POP or HDP, you must reboot the system after changing * one of them. * - A password cannot be set using this method when one does not already * exist. Passwords can only be updated or cleared. - * Type: - * Method - * Arguments: - * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" - * Example: - * "pop,pa55w0rd,newpa55w0rd,ascii,us;” + * Type: Method + * Arguments: "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" + * Example: "pop,pa55w0rd,newpa55w0rd,ascii,us;” */ #define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7" /* - * Name: - * Lenovo_GetBiosSelections - * Description: - * Return a list of valid settings for a given item. - * Type: - * Method - * Arguments: - * "Item" - * Returns: - * "Value1,Value2,Value3,..." + * Name: GetBiosSelections + * Description: Return a list of valid settings for a given item. + * Type: Method + * Arguments: "Item" + * Returns: "Value1,Value2,Value3,..." * Example: * -> "FlashOverLAN" * <- "Enabled,Disabled" @@ -121,18 +93,14 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); #define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" /* - * Name: - * Lenovo_DebugCmdGUID - * Description - * Debug entry GUID method for entering debug commands to the BIOS + * Name: DebugCmd + * Description: Debug entry method for entering debug commands to the BIOS */ #define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1" /* - * Name: - * Lenovo_OpcodeIF - * Description: - * Opcode interface which provides the ability to set multiple + * Name: OpcodeIF + * Description: Opcode interface which provides the ability to set multiple * parameters and then trigger an action with a final command. * This is particularly useful for simplifying setting passwords. * With this support comes the ability to set System, HDD and NVMe @@ -141,10 +109,71 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); */ #define LENOVO_OPCODE_IF_GUID "DFDDEF2C-57D4-48ce-B196-0FB787D90836" +/* + * Name: SetBiosCert + * Description: Install BIOS certificate. + * Type: Method + * Arguments: "Certificate,Password" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE" + +/* + * Name: UpdateBiosCert + * Description: Update BIOS certificate. + * Type: Method + * Format: "Certificate,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE" + +/* + * Name: ClearBiosCert + * Description: Uninstall BIOS certificate. + * Type: Method + * Format: "Serial,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890" +/* + * Name: CertToPassword + * Description: Switch from certificate to password authentication. + * Type: Method + * Format: "Password,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D" + +/* + * Name: SetBiosSettingCert + * Description: Set attribute using certificate authentication. + * Type: Method + * Format: "Item,Value,Signature" + */ +#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003" + +/* + * Name: SaveBiosSettingCert + * Description: Save any pending changes in settings. + * Type: Method + * Format: "Signature" + */ +#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551" + +/* + * Name: CertThumbprint + * Description: Display Certificate thumbprints + * Type: Query + * Returns: MD5, SHA1 & SHA256 thumbprints + */ +#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4" + #define TLMI_POP_PWD (1 << 0) #define TLMI_PAP_PWD (1 << 1) #define TLMI_HDD_PWD (1 << 2) #define TLMI_SYS_PWD (1 << 3) +#define TLMI_CERT (1 << 7) + #define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) #define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) @@ -168,6 +197,13 @@ static struct think_lmi tlmi_priv; static struct class *fw_attr_class; /* ------ Utility functions ------------*/ +/* Strip out CR if one is present */ +static void strip_cr(char *str) +{ + char *p = strchrnul(str, '\n'); + *p = '\0'; +} + /* Convert BIOS WMI error string to suitable error code */ static int tlmi_errstr_to_err(const char *errstr) { @@ -365,7 +401,6 @@ static ssize_t current_password_store(struct kobject *kobj, { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); size_t pwdlen; - char *p; pwdlen = strlen(buf); /* pwdlen == 0 is allowed to clear the password */ @@ -374,8 +409,7 @@ static ssize_t current_password_store(struct kobject *kobj, strscpy(setting->password, buf, setting->maxlen); /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(setting->password, '\n'); - *p = '\0'; + strip_cr(setting->password); return count; } @@ -386,7 +420,7 @@ static ssize_t new_password_store(struct kobject *kobj, const char *buf, size_t count) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - char *auth_str, *new_pwd, *p; + char *auth_str, *new_pwd; size_t pwdlen; int ret; @@ -401,8 +435,7 @@ static ssize_t new_password_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(new_pwd, '\n'); - *p = '\0'; + strip_cr(new_pwd); pwdlen = strlen(new_pwd); /* pwdlen == 0 is allowed to clear the password */ @@ -608,18 +641,258 @@ static ssize_t level_store(struct kobject *kobj, static struct kobj_attribute auth_level = __ATTR_RW(level); +static ssize_t cert_thumbprint(char *buf, const char *arg, int count) +{ + const struct acpi_buffer input = { strlen(arg), (char *)arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + const union acpi_object *obj; + acpi_status status; + + status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output); + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + obj = output.pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) { + kfree(output.pointer); + return -EIO; + } + count += sysfs_emit_at(buf, count, "%s : %s\n", arg, (char *)obj->string.pointer); + kfree(output.pointer); + + return count; +} + +static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + int count = 0; + + if (!tlmi_priv.certificate_support || !setting->cert_installed) + return -EOPNOTSUPP; + + count += cert_thumbprint(buf, "Md5", count); + count += cert_thumbprint(buf, "Sha1", count); + count += cert_thumbprint(buf, "Sha256", count); + return count; +} + +static struct kobj_attribute auth_cert_thumb = __ATTR_RO(certificate_thumbprint); + +static ssize_t cert_to_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *passwd; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + if (!setting->cert_installed) + return -EINVAL; + + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + passwd = kstrdup(buf, GFP_KERNEL); + if (!passwd) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(passwd); + + /* Format: 'Password,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature); + if (!auth_str) { + kfree(passwd); + return -ENOMEM; + } + ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str); + kfree(auth_str); + kfree(passwd); + + return ret ?: count; +} + +static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password); + +static ssize_t certificate_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *new_cert; + char *guid; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_cert = kstrdup(buf, GFP_KERNEL); + if (!new_cert) + return -ENOMEM; + /* Strip out CR if one is present */ + strip_cr(new_cert); + + /* If empty then clear installed certificate */ + if (new_cert[0] == '\0') { /* Clear installed certificate */ + kfree(new_cert); + + /* Check that signature is set */ + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + /* Format: 'serial#, signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + dmi_get_system_info(DMI_PRODUCT_SERIAL), + setting->signature); + if (!auth_str) + return -ENOMEM; + + ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str); + kfree(auth_str); + if (ret) + return ret; + + kfree(setting->certificate); + setting->certificate = NULL; + return count; + } + + if (setting->cert_installed) { + /* Certificate is installed so this is an update */ + if (!setting->signature || !setting->signature[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_UPDATE_BIOS_CERT_GUID; + /* Format: 'Certificate,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->signature); + } else { + /* This is a fresh install */ + if (!setting->valid || !setting->password[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_SET_BIOS_CERT_GUID; + /* Format: 'Certificate,Admin-password' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->password); + } + if (!auth_str) { + kfree(new_cert); + return -ENOMEM; + } + + ret = tlmi_simple_call(guid, auth_str); + kfree(auth_str); + if (ret) { + kfree(new_cert); + return ret; + } + + kfree(setting->certificate); + setting->certificate = new_cert; + return count; +} + +static struct kobj_attribute auth_certificate = __ATTR_WO(certificate); + +static ssize_t signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->signature); + setting->signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_signature = __ATTR_WO(signature); + +static ssize_t save_signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->save_signature); + setting->save_signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_save_signature = __ATTR_WO(save_signature); + static umode_t auth_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - /*We only want to display level and index settings on HDD/NVMe */ + /* We only want to display level and index settings on HDD/NVMe */ if ((attr == (struct attribute *)&auth_index) || (attr == (struct attribute *)&auth_level)) { if ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme)) return attr->mode; return 0; } + + /* We only display certificates on Admin account, if supported */ + if ((attr == (struct attribute *)&auth_certificate) || + (attr == (struct attribute *)&auth_signature) || + (attr == (struct attribute *)&auth_save_signature) || + (attr == (struct attribute *)&auth_cert_thumb) || + (attr == (struct attribute *)&auth_cert_to_password)) { + if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support) + return attr->mode; + return 0; + } + return attr->mode; } @@ -635,6 +908,11 @@ static struct attribute *auth_attrs[] = { &auth_kbdlang.attr, &auth_index.attr, &auth_level.attr, + &auth_certificate.attr, + &auth_signature.attr, + &auth_save_signature.attr, + &auth_cert_thumb.attr, + &auth_cert_to_password.attr, NULL }; @@ -689,7 +967,6 @@ static ssize_t current_value_store(struct kobject *kobj, struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_set_bios_settings) @@ -700,40 +977,60 @@ static ssize_t current_value_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { - auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", - tlmi_priv.pwd_admin->password, - encoding_options[tlmi_priv.pwd_admin->encoding], - tlmi_priv.pwd_admin->kbdlang); - if (!auth_str) { + /* Check if certificate authentication is enabled and active */ + if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) { + if (!tlmi_priv.pwd_admin->signature || !tlmi_priv.pwd_admin->save_signature) { + ret = -EINVAL; + goto out; + } + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, tlmi_priv.pwd_admin->signature); + if (!set_str) { ret = -ENOMEM; goto out; } + + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str); + if (ret) + goto out; + ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + tlmi_priv.pwd_admin->save_signature); + if (ret) + goto out; + } else { /* Non certiifcate based authentication */ + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", + tlmi_priv.pwd_admin->password, + encoding_options[tlmi_priv.pwd_admin->encoding], + tlmi_priv.pwd_admin->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + } + + if (auth_str) + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, auth_str); + else + set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, + new_setting); + if (!set_str) { + ret = -ENOMEM; + goto out; + } + + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); + if (ret) + goto out; + + if (auth_str) + ret = tlmi_save_bios_settings(auth_str); + else + ret = tlmi_save_bios_settings(""); } - - if (auth_str) - set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, - new_setting, auth_str); - else - set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, - new_setting); - if (!set_str) { - ret = -ENOMEM; - goto out; - } - - ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); - if (ret) - goto out; - - if (auth_str) - ret = tlmi_save_bios_settings(auth_str); - else - ret = tlmi_save_bios_settings(""); - if (!ret && !tlmi_priv.pending_changes) { tlmi_priv.pending_changes = true; /* let userland know it may need to check reboot pending again */ @@ -829,7 +1126,6 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr { char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_debug_cmd) @@ -840,8 +1136,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", @@ -896,6 +1191,7 @@ static void tlmi_release_attr(void) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); if (tlmi_priv.can_debug_cmd && debug_support) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); + kset_unregister(tlmi_priv.attribute_kset); /* Authentication structures */ @@ -914,6 +1210,11 @@ static void tlmi_release_attr(void) } kset_unregister(tlmi_priv.authentication_kset); + + /* Free up any saved certificates/signatures */ + kfree(tlmi_priv.pwd_admin->certificate); + kfree(tlmi_priv.pwd_admin->signature); + kfree(tlmi_priv.pwd_admin->save_signature); } static int tlmi_sysfs_init(void) @@ -975,6 +1276,7 @@ static int tlmi_sysfs_init(void) if (ret) goto fail_create_attr; } + /* Create authentication entries */ tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, &tlmi_priv.class_dev->kobj); @@ -1087,6 +1389,11 @@ static int tlmi_analyze(void) if (wmi_has_guid(LENOVO_OPCODE_IF_GUID)) tlmi_priv.opcode_support = true; + if (wmi_has_guid(LENOVO_SET_BIOS_CERT_GUID) && + wmi_has_guid(LENOVO_SET_BIOS_SETTING_CERT_GUID) && + wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID)) + tlmi_priv.certificate_support = true; + /* * Try to find the number of valid settings of this machine * and use it to create sysfs attributes. @@ -1198,6 +1505,11 @@ static int tlmi_analyze(void) } } } + + if (tlmi_priv.certificate_support && + (tlmi_priv.pwdcfg.core.password_state & TLMI_CERT)) + tlmi_priv.pwd_admin->cert_installed = true; + return 0; fail_clear_attr: diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h index e46c7f383353..4f69df6eed07 100644 --- a/drivers/platform/x86/think-lmi.h +++ b/drivers/platform/x86/think-lmi.h @@ -62,6 +62,10 @@ struct tlmi_pwd_setting { char kbdlang[TLMI_LANG_MAXLEN]; int index; /*Used for HDD and NVME auth */ enum level_option level; + bool cert_installed; + char *certificate; + char *signature; + char *save_signature; }; /* Attribute setting details */ @@ -82,6 +86,7 @@ struct think_lmi { bool pending_changes; bool can_debug_cmd; bool opcode_support; + bool certificate_support; struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; struct device *class_dev; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 7d0947b827e2..c568fae56db2 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -728,11 +728,10 @@ static void __init drv_acpi_handle_init(const char *name, static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, u32 level, void *context, void **return_value) { - struct acpi_device *dev; if (!strcmp(context, "video")) { - if (acpi_bus_get_device(handle, &dev)) - return AE_OK; - if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); + + if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) return AE_OK; } @@ -786,7 +785,6 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) static int __init setup_acpi_notify(struct ibm_struct *ibm) { acpi_status status; - int rc; BUG_ON(!ibm->acpi); @@ -796,9 +794,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) vdbg_printk(TPACPI_DBG_INIT, "setting up ACPI notify for %s\n", ibm->name); - rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); - if (rc < 0) { - pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc); + ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle); + if (!ibm->acpi->device) { + pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name); return -ENODEV; } @@ -6723,7 +6721,8 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle) struct acpi_device *device, *child; int rc; - if (acpi_bus_get_device(handle, &device)) + device = acpi_fetch_acpi_dev(handle); + if (!device) return 0; rc = 0; @@ -8286,7 +8285,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_FANS: case TPACPI_FAN_WR_TPEC: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; /* Don't go out of emergency fan mode */ @@ -8305,7 +8304,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_SFAN: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; s &= 0x07; @@ -8699,10 +8698,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */ TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */ TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */ - TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */ - TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */ TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */ - TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */ TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ }; @@ -8746,6 +8742,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) * ThinkPad ECs supports the fan control register */ if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { + int res; + unsigned int speed; + fan_status_access_mode = TPACPI_FAN_RD_TPEC; if (quirks & TPACPI_FAN_Q1) fan_quirk1_setup(); @@ -8758,6 +8757,15 @@ static int __init fan_init(struct ibm_init_struct *iibm) tp_features.second_fan_ctl = 1; pr_info("secondary fan control enabled\n"); } + /* Try and probe the 2nd fan */ + res = fan2_get_speed(&speed); + if (res >= 0) { + /* It responded - so let's assume it's there */ + tp_features.second_fan = 1; + tp_features.second_fan_ctl = 1; + pr_info("secondary fan control detected & enabled\n"); + } + } else { pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n"); return -ENODEV; @@ -8835,7 +8843,7 @@ static void fan_suspend(void) /* Store fan status in cache */ fan_control_resume_level = 0; rc = fan_get_status_safe(&fan_control_resume_level); - if (rc < 0) + if (rc) pr_notice("failed to read fan level for later restore during resume: %d\n", rc); @@ -8856,7 +8864,7 @@ static void fan_resume(void) if (!fan_control_allowed || !fan_control_resume_level || - (fan_get_status_safe(¤t_level) < 0)) + fan_get_status_safe(¤t_level)) return; switch (fan_control_access_mode) { @@ -8910,7 +8918,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n" @@ -8921,7 +8929,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n", @@ -10122,6 +10130,7 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_CMD_FUNC_CAP 3 /* To get DYTC capabilities */ #define DYTC_FC_MMC 27 /* MMC Mode supported */ +#define DYTC_FC_PSC 29 /* PSC Mode supported */ #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ @@ -10132,12 +10141,17 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ -#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, MMC mode */ +#define DYTC_FUNCTION_PSC 13 /* Function = 13, PSC mode */ -#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ -#define DYTC_MODE_LOWPOWER 3 /* Low power mode */ -#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ -#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */ +#define DYTC_MODE_MMC_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_MMC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_MMC_BALANCE 0xF /* Default mode aka balanced */ +#define DYTC_MODE_MMC_DEFAULT 0 /* Default mode from MMC_GET, aka balanced */ + +#define DYTC_MODE_PSC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_PSC_BALANCE 5 /* Default mode aka balanced */ +#define DYTC_MODE_PSC_PERFORM 7 /* High power mode aka performance */ #define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */ #define DYTC_ERR_SUCCESS 1 /* CMD completed successful */ @@ -10147,10 +10161,16 @@ static struct ibm_struct proxsensor_driver_data = { (mode) << DYTC_SET_MODE_BIT | \ (on) << DYTC_SET_VALID_BIT) -#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 0) +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 1) -#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) +enum dytc_profile_funcmode { + DYTC_FUNCMODE_NONE = 0, + DYTC_FUNCMODE_MMC, + DYTC_FUNCMODE_PSC, +}; +static enum dytc_profile_funcmode dytc_profile_available; static enum platform_profile_option dytc_current_profile; static atomic_t dytc_ignore_event = ATOMIC_INIT(0); static DEFINE_MUTEX(dytc_mutex); @@ -10158,19 +10178,37 @@ static bool dytc_mmc_get_available; static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) { - switch (dytcmode) { - case DYTC_MODE_LOWPOWER: - *profile = PLATFORM_PROFILE_LOW_POWER; - break; - case DYTC_MODE_BALANCE: - case DYTC_MODE_MMC_BALANCE: - *profile = PLATFORM_PROFILE_BALANCED; - break; - case DYTC_MODE_PERFORM: - *profile = PLATFORM_PROFILE_PERFORMANCE; - break; - default: /* Unknown mode */ - return -EINVAL; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + switch (dytcmode) { + case DYTC_MODE_MMC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_MMC_DEFAULT: + case DYTC_MODE_MMC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_MMC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { + switch (dytcmode) { + case DYTC_MODE_PSC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_PSC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PSC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } } return 0; } @@ -10179,13 +10217,22 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe { switch (profile) { case PLATFORM_PROFILE_LOW_POWER: - *perfmode = DYTC_MODE_LOWPOWER; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_LOWPOWER; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_LOWPOWER; break; case PLATFORM_PROFILE_BALANCED: - *perfmode = DYTC_MODE_BALANCE; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_BALANCE; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_BALANCE; break; case PLATFORM_PROFILE_PERFORMANCE: - *perfmode = DYTC_MODE_PERFORM; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_PERFORM; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_PERFORM; break; default: /* Unknown profile */ return -EOPNOTSUPP; @@ -10251,6 +10298,7 @@ static int dytc_cql_command(int command, int *output) static int dytc_profile_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { + int perfmode; int output; int err; @@ -10258,25 +10306,31 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, if (err) return err; - if (profile == PLATFORM_PROFILE_BALANCED) { - /* - * To get back to balanced mode we need to issue a reset command. - * Note we still need to disable CQL mode before hand and re-enable - * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays - * stuck at 0 for aprox. 30 minutes. - */ - err = dytc_cql_command(DYTC_CMD_RESET, &output); - if (err) - goto unlock; - } else { - int perfmode; + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; - err = convert_profile_to_dytc(profile, &perfmode); - if (err) - goto unlock; - - /* Determine if we are in CQL mode. This alters the commands we do */ - err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (profile == PLATFORM_PROFILE_BALANCED) { + /* + * To get back to balanced mode we need to issue a reset command. + * Note we still need to disable CQL mode before hand and re-enable + * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays + * stuck at 0 for aprox. 30 minutes. + */ + err = dytc_cql_command(DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + &output); + if (err) + goto unlock; + } + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { + err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) goto unlock; } @@ -10290,14 +10344,18 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, static void dytc_profile_refresh(void) { enum platform_profile_option profile; - int output, err; + int output, err = 0; int perfmode; mutex_lock(&dytc_mutex); - if (dytc_mmc_get_available) - err = dytc_command(DYTC_CMD_MMC_GET, &output); - else - err = dytc_cql_command(DYTC_CMD_GET, &output); + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (dytc_mmc_get_available) + err = dytc_command(DYTC_CMD_MMC_GET, &output); + else + err = dytc_cql_command(DYTC_CMD_GET, &output); + } else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + err = dytc_command(DYTC_CMD_GET, &output); + mutex_unlock(&dytc_mutex); if (err) return; @@ -10324,6 +10382,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices); set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices); + dytc_profile_available = DYTC_FUNCMODE_NONE; err = dytc_command(DYTC_CMD_QUERY, &output); if (err) return err; @@ -10335,27 +10394,34 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (dytc_version < 5) return -ENODEV; - /* Check what capabilities are supported. Currently MMC is needed */ + /* Check what capabilities are supported */ err = dytc_command(DYTC_CMD_FUNC_CAP, &output); if (err) return err; - if (!(output & BIT(DYTC_FC_MMC))) { - dbg_printk(TPACPI_DBG_INIT, " DYTC MMC mode not supported\n"); + + if (output & BIT(DYTC_FC_MMC)) { /* MMC MODE */ + dytc_profile_available = DYTC_FUNCMODE_MMC; + + /* + * Check if MMC_GET functionality available + * Version > 6 and return success from MMC_GET command + */ + dytc_mmc_get_available = false; + if (dytc_version >= 6) { + err = dytc_command(DYTC_CMD_MMC_GET, &output); + if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) + dytc_mmc_get_available = true; + } + } else if (output & BIT(DYTC_FC_PSC)) { /* PSC MODE */ + dytc_profile_available = DYTC_FUNCMODE_PSC; + } else { + dbg_printk(TPACPI_DBG_INIT, "No DYTC support available\n"); return -ENODEV; } dbg_printk(TPACPI_DBG_INIT, "DYTC version %d: thermal mode available\n", dytc_version); - /* - * Check if MMC_GET functionality available - * Version > 6 and return success from MMC_GET command - */ - dytc_mmc_get_available = false; - if (dytc_version >= 6) { - err = dytc_command(DYTC_CMD_MMC_GET, &output); - if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) - dytc_mmc_get_available = true; - } + /* Create platform_profile structure and register */ err = platform_profile_register(&dytc_profile); /* @@ -10373,6 +10439,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) static void dytc_profile_exit(void) { + dytc_profile_available = DYTC_FUNCMODE_NONE; platform_profile_remove(); } diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 9360a8a92486..f446be72e539 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -12,17 +12,26 @@ #include #include +#include +#include #include #include #include #include +#include #include #include #include #include +#include +#include +#include #include +#include #include +#include #include +#include #include /* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */ #include "../../gpio/gpiolib.h" @@ -53,13 +62,33 @@ static int gpiochip_find_match_label(struct gpio_chip *gc, void *data) return gc->label && !strcmp(gc->label, data); } +static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc) +{ + struct gpio_desc *gpiod; + struct gpio_chip *chip; + + chip = gpiochip_find(label, gpiochip_find_match_label); + if (!chip) { + pr_err("error cannot find GPIO chip %s\n", label); + return -ENODEV; + } + + gpiod = gpiochip_get_desc(chip, pin); + if (IS_ERR(gpiod)) { + pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin); + return PTR_ERR(gpiod); + } + + *desc = gpiod; + return 0; +} + static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) { struct irq_fwspec fwspec = { }; struct irq_domain *domain; struct acpi_device *adev; struct gpio_desc *gpiod; - struct gpio_chip *chip; unsigned int irq_type; acpi_handle handle; acpi_status status; @@ -67,6 +96,12 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) switch (data->type) { case X86_ACPI_IRQ_TYPE_APIC: + /* + * The DSDT may already reference the GSI in a device skipped by + * acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI + * to avoid EBUSY errors in this case. + */ + acpi_unregister_gsi(data->index); irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity); if (irq < 0) pr_err("error %d getting APIC IRQ %d\n", irq, data->index); @@ -74,18 +109,9 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return irq; case X86_ACPI_IRQ_TYPE_GPIOINT: /* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */ - chip = gpiochip_find(data->chip, gpiochip_find_match_label); - if (!chip) { - pr_err("error cannot find GPIO chip %s\n", data->chip); - return -ENODEV; - } - - gpiod = gpiochip_get_desc(chip, data->index); - if (IS_ERR(gpiod)) { - ret = PTR_ERR(gpiod); - pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index); + ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod); + if (ret) return ret; - } irq = gpiod_to_irq(gpiod); if (irq < 0) { @@ -105,7 +131,7 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return -ENODEV; } - acpi_bus_get_device(handle, &adev); + adev = acpi_fetch_acpi_dev(handle); if (!adev) { pr_err("error could not get %s adev\n", data->chip); return -ENODEV; @@ -146,6 +172,7 @@ struct x86_serdev_info { struct x86_dev_info { char *invalid_aei_gpiochip; const char * const *modules; + const struct software_node *bat_swnode; struct gpiod_lookup_table * const *gpiod_lookup_tables; const struct x86_i2c_client_info *i2c_client_info; const struct platform_device_info *pdev_info; @@ -157,21 +184,46 @@ struct x86_dev_info { void (*exit)(void); }; -/* Generic / shared bq24190 settings */ -static const char * const bq24190_suppliers[] = { "tusb1210-psy" }; +/* Generic / shared charger / battery settings */ +static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" }; +static const char * const bq24190_psy[] = { "bq24190-charger" }; +static const char * const bq25890_psy[] = { "bq25890-charger" }; -static const struct property_entry bq24190_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), - PROPERTY_ENTRY_BOOL("omit-battery-class"), - PROPERTY_ENTRY_BOOL("disable-reset"), +static const struct property_entry fg_bq24190_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), { } }; -static const struct software_node bq24190_node = { - .properties = bq24190_props, +static const struct software_node fg_bq24190_supply_node = { + .properties = fg_bq24190_supply_props, }; -/* For enableing the bq24190 5V boost based on id-pin */ +static const struct property_entry fg_bq25890_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy), + { } +}; + +static const struct software_node fg_bq25890_supply_node = { + .properties = fg_bq25890_supply_props, +}; + +/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */ +static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +static const struct software_node generic_lipo_hv_4v35_battery_node = { + .properties = generic_lipo_hv_4v35_battery_props, +}; + +/* For enabling the bq24190 5V boost based on id-pin */ static struct regulator_consumer_supply intel_int3496_consumer = { .supply = "vbus", .dev_name = "intel-int3496", @@ -213,6 +265,51 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = { }, }; +/* Asus ME176C and TF103C tablets shared data */ +static struct gpio_keys_button asus_me176c_tf103c_lid = { + .code = SW_LID, + /* .gpio gets filled in by asus_me176c_tf103c_init() */ + .active_low = true, + .desc = "lid_sw", + .type = EV_SW, + .wakeup = true, + .debounce_interval = 50, +}; + +static const struct gpio_keys_platform_data asus_me176c_tf103c_lid_pdata __initconst = { + .buttons = &asus_me176c_tf103c_lid, + .nbuttons = 1, + .name = "lid_sw", +}; + +static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = { + { + .name = "gpio-keys", + .id = PLATFORM_DEVID_AUTO, + .data = &asus_me176c_tf103c_lid_pdata, + .size_data = sizeof(asus_me176c_tf103c_lid_pdata), + }, + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + }, +}; + +static int __init asus_me176c_tf103c_init(void) +{ + struct gpio_desc *gpiod; + int ret; + + ret = x86_android_tablet_get_gpiod("INT33FC:02", 12, &gpiod); + if (ret < 0) + return ret; + asus_me176c_tf103c_lid.gpio = desc_to_gpio(gpiod); + + return 0; +} + + /* Asus ME176C tablets have an Android factory img with everything hardcoded */ static const char * const asus_me176c_accel_mount_matrix[] = { "-1", "0", "0", @@ -229,14 +326,38 @@ static const struct software_node asus_me176c_accel_node = { .properties = asus_me176c_accel_props, }; +static const struct property_entry asus_me176c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_me176c_bq24190_node = { + .properties = asus_me176c_bq24190_props, +}; + +static const struct property_entry asus_me176c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000), + { } +}; + +static const struct software_node asus_me176c_ug3105_node = { + .properties = asus_me176c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", - .swnode = &bq24190_node, + .dev_name = "bq24297", + .swnode = &asus_me176c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -252,6 +373,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_me176c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -271,6 +393,12 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .swnode = &asus_me176c_accel_node, }, .adapter_path = "\\_SB_.I2C5", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x44, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, }, { /* goodix touchscreen */ .board_info = { @@ -315,13 +443,15 @@ static struct gpiod_lookup_table * const asus_me176c_gpios[] = { static const struct x86_dev_info asus_me176c_info __initconst = { .i2c_client_info = asus_me176c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), .gpiod_lookup_tables = asus_me176c_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* Asus TF103C tablets have an Android factory img with everything hardcoded */ @@ -349,14 +479,53 @@ static const struct software_node asus_tf103c_touchscreen_node = { .properties = asus_tf103c_touchscreen_props, }; +static const struct property_entry asus_tf103c_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +static const struct software_node asus_tf103c_battery_node = { + .properties = asus_tf103c_battery_props, +}; + +static const struct property_entry asus_tf103c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_tf103c_bq24190_node = { + .properties = asus_tf103c_bq24190_props, +}; + +static const struct property_entry asus_tf103c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000), + { } +}; + +static const struct software_node asus_tf103c_ug3105_node = { + .properties = asus_tf103c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", - .swnode = &bq24190_node, + .dev_name = "bq24297", + .swnode = &asus_tf103c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -372,6 +541,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_tf103c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -418,11 +588,13 @@ static struct gpiod_lookup_table * const asus_tf103c_gpios[] = { static const struct x86_dev_info asus_tf103c_info __initconst = { .i2c_client_info = asus_tf103c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .gpiod_lookup_tables = asus_tf103c_gpios, + .bat_swnode = &asus_tf103c_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* @@ -529,6 +701,347 @@ static const struct x86_dev_info czc_p10t __initconst = { .init = czc_p10t_init, }; +/* Lenovo Yoga Book X90F / X91F / X91L need manual instantiation of the fg client */ +static const struct x86_i2c_client_info lenovo_yogabook_x9x_i2c_clients[] __initconst = { + { + /* BQ27542 fuel-gauge */ + .board_info = { + .type = "bq27542", + .addr = 0x55, + .dev_name = "bq27542", + .swnode = &fg_bq25890_supply_node, + }, + .adapter_path = "\\_SB_.PCI0.I2C1", + }, +}; + +static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = { + .i2c_client_info = lenovo_yogabook_x9x_i2c_clients, + .i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients), +}; + +/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */ +static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = { + .properties = lenovo_yoga_tab2_830_1050_bq24190_props, +}; + +/* This gets filled by lenovo_yoga_tab2_830_1050_init() */ +static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { }; + +static struct lp855x_platform_data lenovo_yoga_tab2_830_1050_lp8557_pdata = { + .device_control = 0x86, + .initial_brightness = 128, +}; + +static const struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initconst = { + { + /* bq24292i battery charger */ + .board_info = { + .type = "bq24190", + .addr = 0x6b, + .dev_name = "bq24292i", + .swnode = &lenovo_yoga_tab2_830_1050_bq24190_node, + .platform_data = &bq24190_pdata, + }, + .adapter_path = "\\_SB_.I2C1", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 2, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* BQ27541 fuel-gauge */ + .board_info = { + .type = "bq27541", + .addr = 0x55, + .dev_name = "bq27541", + .swnode = &fg_bq24190_supply_node, + }, + .adapter_path = "\\_SB_.I2C1", + }, { + /* Synaptics RMI touchscreen */ + .board_info = { + .type = "rmi4_i2c", + .addr = 0x38, + .dev_name = "rmi4_i2c", + .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata, + }, + .adapter_path = "\\_SB_.I2C6", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x45, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* LP8557 Backlight controller */ + .board_info = { + .type = "lp8557", + .addr = 0x2c, + .dev_name = "lp8557", + .platform_data = &lenovo_yoga_tab2_830_1050_lp8557_pdata, + }, + .adapter_path = "\\_SB_.I2C3", + }, +}; + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW), + GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = { + .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, + .table = { + GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW), + { } + }, +}; + +static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { + &lenovo_yoga_tab2_830_1050_int3496_gpios, + &lenovo_yoga_tab2_830_1050_codec_gpios, + NULL +}; + +static int __init lenovo_yoga_tab2_830_1050_init(void); +static void lenovo_yoga_tab2_830_1050_exit(void); + +static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = { + .i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients, + /* i2c_client_count gets set by lenovo_yoga_tab2_830_1050_init() */ + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, + .modules = bq24190_modules, + .invalid_aei_gpiochip = "INT33FC:02", + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +}; + +/* + * The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same + * mainboard, but they need some different treatment related to the display: + * 1. The 830 uses a portrait LCD panel with a landscape touchscreen, requiring + * the touchscreen driver to adjust the touch-coords to match the LCD. + * 2. Both use an TI LP8557 LED backlight controller. On the 1050 the LP8557's + * PWM input is connected to the PMIC's PWM output and everything works fine + * with the defaults programmed into the LP8557 by the BIOS. + * But on the 830 the LP8557's PWM input is connected to a PWM output coming + * from the LCD panel's controller. The Android code has a hack in the i915 + * driver to write the non-standard DSI reg 0x9f with the desired backlight + * level to set the duty-cycle of the LCD's PWM output. + * + * To avoid having to have a similar hack in the mainline kernel the LP8557 + * entry in lenovo_yoga_tab2_830_1050_i2c_clients instead just programs the + * LP8557 to directly set the level, ignoring the PWM input. This means that + * the LP8557 i2c_client should only be instantiated on the 830. + */ +static int __init lenovo_yoga_tab2_830_1050_init_display(void) +{ + struct gpio_desc *gpiod; + int ret; + + /* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */ + ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod); + if (ret) + return ret; + + ret = gpiod_get_value_cansleep(gpiod); + if (ret) { + pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n"); + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients) - 1; + } else { + pr_info("detected Lenovo Yoga Tablet 2 830F/L\n"); + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true; + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true; + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients); + } + + return 0; +} + +/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */ +static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map = + PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk", + "INT33FC:02", "pmu_clk2_grp", "pmu_clk"); + +static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl; + +static int __init lenovo_yoga_tab2_830_1050_init_codec(void) +{ + struct device *codec_dev; + struct pinctrl *pinctrl; + int ret; + + codec_dev = bus_find_device_by_name(&spi_bus_type, NULL, + LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + if (!codec_dev) { + pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + return -ENODEV; + } + + ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1); + if (ret) + goto err_put_device; + + pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk"); + if (IS_ERR(pinctrl)) { + ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n"); + goto err_unregister_mappings; + } + + /* We're done with the codec_dev now */ + put_device(codec_dev); + + lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl; + return 0; + +err_unregister_mappings: + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); +err_put_device: + put_device(codec_dev); + return ret; +} + +/* + * These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off + * gets used as pm_power_off handler. This causes "poweroff" on these tablets + * to hang hard. Requiring pressing the powerbutton for 30 seconds *twice* + * followed by a normal 3 second press to recover. Avoid this by doing an EFI + * poweroff instead. + */ +static void lenovo_yoga_tab2_830_1050_power_off(void) +{ + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL); +} + +static int __init lenovo_yoga_tab2_830_1050_init(void) +{ + int ret; + + ret = lenovo_yoga_tab2_830_1050_init_display(); + if (ret) + return ret; + + ret = lenovo_yoga_tab2_830_1050_init_codec(); + if (ret) + return ret; + + pm_power_off = lenovo_yoga_tab2_830_1050_power_off; + return 0; +} + +static void lenovo_yoga_tab2_830_1050_exit(void) +{ + pm_power_off = NULL; /* Just turn poweroff into halt on module unload */ + + if (lenovo_yoga_tab2_830_1050_codec_pinctrl) { + pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); + } +} + +/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ +static const char * const nextbook_ares8_accel_mount_matrix[] = { + "0", "-1", "0", + "-1", "0", "0", + "0", "0", "1" +}; + +static const struct property_entry nextbook_ares8_accel_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix), + { } +}; + +static const struct software_node nextbook_ares8_accel_node = { + .properties = nextbook_ares8_accel_props, +}; + +static const struct property_entry nextbook_ares8_touchscreen_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 800), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + { } +}; + +static const struct software_node nextbook_ares8_touchscreen_node = { + .properties = nextbook_ares8_touchscreen_props, +}; + +static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = { + { + /* Freescale MMA8653FC accel */ + .board_info = { + .type = "mma8653", + .addr = 0x1d, + .dev_name = "mma8653", + .swnode = &nextbook_ares8_accel_node, + }, + .adapter_path = "\\_SB_.I2C3", + }, { + /* FT5416DQ9 touchscreen controller */ + .board_info = { + .type = "edt-ft5x06", + .addr = 0x38, + .dev_name = "ft5416", + .swnode = &nextbook_ares8_touchscreen_node, + }, + .adapter_path = "\\_SB_.I2C4", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 3, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, + }, +}; + +static struct gpiod_lookup_table nextbook_ares8_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = { + &nextbook_ares8_int3496_gpios, + NULL +}; + +static const struct x86_dev_info nextbook_ares8_info __initconst = { + .i2c_client_info = nextbook_ares8_i2c_clients, + .i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients), + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = nextbook_ares8_gpios, + .invalid_aei_gpiochip = "INT33FC:02", +}; + /* * Whitelabel (sold as various brands) TM800A550L tablets. * These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices @@ -616,17 +1129,6 @@ static const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { * * This takes care of instantiating the hidden devices manually. */ -static const char * const bq27520_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27520_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers), - { } -}; - -static const struct software_node bq27520_node = { - .properties = bq27520_props, -}; - static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = { { /* BQ27520 fuel-gauge */ @@ -634,7 +1136,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst .type = "bq27520", .addr = 0x55, .dev_name = "bq27520", - .swnode = &bq27520_node, + .swnode = &fg_bq25890_supply_node, }, .adapter_path = "\\_SB_.PCI0.I2C1", }, { @@ -690,7 +1192,7 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { .driver_data = (void *)&czc_p10t, }, { - /* A variant of CZC P10T */ + /* CZC P10T variant */ .ident = "ViewSonic ViewPad 10", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"), @@ -698,6 +1200,36 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, .driver_data = (void *)&czc_p10t, }, + { + /* Lenovo Yoga Book X90F / X91F / X91L */ + .matches = { + /* Non exact match to match all versions */ + DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), + }, + .driver_data = (void *)&lenovo_yogabook_x9x_info, + }, + { + /* + * Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10" + * Lenovo Yoga Tablet 2 use the same mainboard) + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), + DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), + /* Partial match on beginning of BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"), + }, + .driver_data = (void *)&lenovo_yoga_tab2_830_1050_info, + }, + { + /* Nextbook Ares 8 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"), + }, + .driver_data = (void *)&nextbook_ares8_info, + }, { /* Whitelabel (sold as various brands) TM800A550L */ .matches = { @@ -727,6 +1259,7 @@ static struct i2c_client **i2c_clients; static struct platform_device **pdevs; static struct serdev_device **serdevs; static struct gpiod_lookup_table * const *gpiod_lookup_tables; +static const struct software_node *bat_swnode; static void (*exit_handler)(void); static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info, @@ -850,6 +1383,8 @@ static void x86_android_tablet_cleanup(void) for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_remove_lookup_table(gpiod_lookup_tables[i]); + + software_node_unregister(bat_swnode); } static __init int x86_android_tablet_init(void) @@ -886,6 +1421,13 @@ static __init int x86_android_tablet_init(void) for (i = 0; dev_info->modules && dev_info->modules[i]; i++) request_module(dev_info->modules[i]); + bat_swnode = dev_info->bat_swnode; + if (bat_swnode) { + ret = software_node_register(bat_swnode); + if (ret) + return ret; + } + gpiod_lookup_tables = dev_info->gpiod_lookup_tables; for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_add_lookup_table(gpiod_lookup_tables[i]); diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 35413793a4d4..d7136d13aa44 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1024,7 +1024,15 @@ void acpi_os_set_prepare_extended_sleep(int (*func)(u8 sleep_state, acpi_status acpi_os_prepare_extended_sleep(u8 sleep_state, u32 val_a, u32 val_b); - +#ifdef CONFIG_X86 +struct acpi_s2idle_dev_ops { + struct list_head list_node; + void (*prepare)(void); + void (*restore)(void); +}; +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg); +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg); +#endif /* CONFIG_X86 */ #ifndef CONFIG_IA64 void arch_reserve_mem_area(acpi_physical_address addr, size_t size); #else diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 3ad67b4a72be..c3aa8b330e1c 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -8,27 +8,16 @@ #include struct device; - -/** - * Opaque descriptor for a GPIO. These are obtained using gpiod_get() and are - * preferable to the old integer-based handles. - * - * Contrary to integers, a pointer to a gpio_desc is guaranteed to be valid - * until the GPIO is released. - */ struct gpio_desc; - -/** - * Opaque descriptor for a structure of GPIO array attributes. This structure - * is attached to struct gpiod_descs obtained from gpiod_get_array() and can be - * passed back to get/set array functions in order to activate fast processing - * path if applicable. - */ struct gpio_array; /** - * Struct containing an array of descriptors that can be obtained using - * gpiod_get_array(). + * struct gpio_descs - Struct containing an array of descriptors that can be + * obtained using gpiod_get_array() + * + * @info: Pointer to the opaque gpio_array structure + * @ndescs: Number of held descriptors + * @desc: Array of pointers to GPIO descriptors */ struct gpio_descs { struct gpio_array *info; @@ -43,8 +32,16 @@ struct gpio_descs { #define GPIOD_FLAGS_BIT_NONEXCLUSIVE BIT(4) /** - * Optional flags that can be passed to one of gpiod_* to configure direction - * and output value. These values cannot be OR'd. + * enum gpiod_flags - Optional flags that can be passed to one of gpiod_* to + * configure direction and output value. These values + * cannot be OR'd. + * + * @GPIOD_ASIS: Don't change anything + * @GPIOD_IN: Set lines to input mode + * @GPIOD_OUT_LOW: Set lines to output and drive them low + * @GPIOD_OUT_HIGH: Set lines to output and drive them high + * @GPIOD_OUT_LOW_OPEN_DRAIN: Set lines to open-drain output and drive them low + * @GPIOD_OUT_HIGH_OPEN_DRAIN: Set lines to open-drain output and drive them high */ enum gpiod_flags { GPIOD_ASIS = 0, diff --git a/include/linux/hid.h b/include/linux/hid.h index 7487b0586fe6..4363a63b9775 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -342,12 +342,12 @@ struct hid_item { * HID device quirks. */ -/* +/* * Increase this if you need to configure more HID quirks at module load time */ #define MAX_USBHID_BOOT_QUIRKS 4 -#define HID_QUIRK_INVERT BIT(0) +/* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */ #define HID_QUIRK_NOTOUCH BIT(1) #define HID_QUIRK_IGNORE BIT(2) #define HID_QUIRK_NOGET BIT(3) @@ -476,31 +476,50 @@ struct hid_field { unsigned report_count; /* number of this field in the report */ unsigned report_type; /* (input,output,feature) */ __s32 *value; /* last known value(s) */ + __s32 *new_value; /* newly read value(s) */ + __s32 *usages_priorities; /* priority of each usage when reading the report + * bits 8-16 are reserved for hid-input usage + */ __s32 logical_minimum; __s32 logical_maximum; __s32 physical_minimum; __s32 physical_maximum; __s32 unit_exponent; unsigned unit; + bool ignored; /* this field is ignored in this event */ struct hid_report *report; /* associated report */ unsigned index; /* index into report->field[] */ /* hidinput data */ struct hid_input *hidinput; /* associated input structure */ __u16 dpad; /* dpad input code */ + unsigned int slot_idx; /* slot index in a report */ }; #define HID_MAX_FIELDS 256 +struct hid_field_entry { + struct list_head list; + struct hid_field *field; + unsigned int index; + __s32 priority; +}; + struct hid_report { struct list_head list; struct list_head hidinput_list; + struct list_head field_entry_list; /* ordered list of input fields */ unsigned int id; /* id of this report */ unsigned int type; /* report type */ unsigned int application; /* application usage for this report */ struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */ + struct hid_field_entry *field_entries; /* allocated memory of input field_entry */ unsigned maxfield; /* maximum valid field index */ unsigned size; /* size of the report (bits) */ struct hid_device *device; /* associated device */ + + /* tool related state */ + bool tool_active; /* whether the current tool is active */ + unsigned int tool; /* BTN_TOOL_* */ }; #define HID_MAX_IDS 256 diff --git a/tools/arch/x86/intel_sdsi/Makefile b/tools/arch/x86/intel_sdsi/Makefile new file mode 100644 index 000000000000..5de2288cda79 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Intel Software Defined Silicon provisioning tool + +intel_sdsi: intel_sdsi.c + +CFLAGS = -Wextra + +BINDIR ?= /usr/sbin + +override CFLAGS += -O2 -Wall + +%: %.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +.PHONY : clean +clean : + @rm -f intel_sdsi + +install : intel_sdsi + install -d $(DESTDIR)$(BINDIR) + install -m 755 -p intel_sdsi $(DESTDIR)$(BINDIR)/intel_sdsi diff --git a/tools/arch/x86/intel_sdsi/intel_sdsi.c b/tools/arch/x86/intel_sdsi/intel_sdsi.c new file mode 100644 index 000000000000..c0e2f2349db4 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/intel_sdsi.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sdsi: Intel Software Defined Silicon tool for provisioning certificates + * and activation payloads on supported cpus. + * + * See https://github.com/intel/intel-sdsi/blob/master/os-interface.rst + * for register descriptions. + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SDSI_DEV "intel_vsec.sdsi" +#define AUX_DEV_PATH "/sys/bus/auxiliary/devices/" +#define SDSI_PATH (AUX_DEV_DIR SDSI_DEV) +#define GUID 0x6dd191 +#define REGISTERS_MIN_SIZE 72 + +#define __round_mask(x, y) ((__typeof__(x))((y) - 1)) +#define round_up(x, y) ((((x) - 1) | __round_mask(x, y)) + 1) + +struct enabled_features { + uint64_t reserved:3; + uint64_t sdsi:1; + uint64_t reserved1:60; +}; + +struct auth_fail_count { + uint64_t key_failure_count:3; + uint64_t key_failure_threshold:3; + uint64_t auth_failure_count:3; + uint64_t auth_failure_threshold:3; + uint64_t reserved:52; +}; + +struct availability { + uint64_t reserved:48; + uint64_t available:3; + uint64_t threshold:3; +}; + +struct sdsi_regs { + uint64_t ppin; + uint64_t reserved; + struct enabled_features en_features; + uint64_t reserved1; + struct auth_fail_count auth_fail_count; + struct availability prov_avail; + uint64_t reserved2; + uint64_t reserved3; + uint64_t socket_id; +}; + +struct sdsi_dev { + struct sdsi_regs regs; + char *dev_name; + char *dev_path; + int guid; +}; + +enum command { + CMD_NONE, + CMD_SOCKET_INFO, + CMD_DUMP_CERT, + CMD_PROV_AKC, + CMD_PROV_CAP, +}; + +static void sdsi_list_devices(void) +{ + struct dirent *entry; + DIR *aux_dir; + bool found = false; + + aux_dir = opendir(AUX_DEV_PATH); + if (!aux_dir) { + fprintf(stderr, "Cannot open directory %s\n", AUX_DEV_PATH); + return; + } + + while ((entry = readdir(aux_dir))) { + if (!strncmp(SDSI_DEV, entry->d_name, strlen(SDSI_DEV))) { + found = true; + printf("%s\n", entry->d_name); + } + } + + if (!found) + fprintf(stderr, "No sdsi devices found.\n"); +} + +static int sdsi_update_registers(struct sdsi_dev *s) +{ + FILE *regs_ptr; + int ret; + + memset(&s->regs, 0, sizeof(s->regs)); + + /* Open the registers file */ + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + return ret; + } + + regs_ptr = fopen("registers", "r"); + if (!regs_ptr) { + perror("Could not open 'registers' file"); + return -1; + } + + if (s->guid != GUID) { + fprintf(stderr, "Unrecognized guid, 0x%x\n", s->guid); + fclose(regs_ptr); + return -1; + } + + /* Update register info for this guid */ + ret = fread(&s->regs, sizeof(uint8_t), sizeof(s->regs), regs_ptr); + if (ret != sizeof(s->regs)) { + fprintf(stderr, "Could not read 'registers' file\n"); + fclose(regs_ptr); + return -1; + } + + fclose(regs_ptr); + + return 0; +} + +static int sdsi_read_reg(struct sdsi_dev *s) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + /* Print register info for this guid */ + printf("\n"); + printf("Socket information for device %s\n", s->dev_name); + printf("\n"); + printf("PPIN: 0x%lx\n", s->regs.ppin); + printf("Enabled Features\n"); + printf(" SDSi: %s\n", !!s->regs.en_features.sdsi ? "Enabled" : "Disabled"); + printf("Authorization Failure Count\n"); + printf(" AKC Failure Count: %d\n", s->regs.auth_fail_count.key_failure_count); + printf(" AKC Failure Threshold: %d\n", s->regs.auth_fail_count.key_failure_threshold); + printf(" CAP Failure Count: %d\n", s->regs.auth_fail_count.auth_failure_count); + printf(" CAP Failure Threshold: %d\n", s->regs.auth_fail_count.auth_failure_threshold); + printf("Provisioning Availability\n"); + printf(" Updates Available: %d\n", s->regs.prov_avail.available); + printf(" Updates Threshold: %d\n", s->regs.prov_avail.threshold); + printf("Socket ID: %ld\n", s->regs.socket_id & 0xF); + + return 0; +} + +static int sdsi_certificate_dump(struct sdsi_dev *s) +{ + uint64_t state_certificate[512] = {0}; + bool first_instance; + uint64_t previous; + FILE *cert_ptr; + int i, ret, size; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled."); + fprintf(stderr, " Unable to read state certificate"); + return -1; + } + + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + return ret; + } + + cert_ptr = fopen("state_certificate", "r"); + if (!cert_ptr) { + perror("Could not open 'state_certificate' file"); + return -1; + } + + size = fread(state_certificate, 1, sizeof(state_certificate), cert_ptr); + if (!size) { + fprintf(stderr, "Could not read 'state_certificate' file\n"); + fclose(cert_ptr); + return -1; + } + + printf("%3d: 0x%lx\n", 0, state_certificate[0]); + previous = state_certificate[0]; + first_instance = true; + + for (i = 1; i < (int)(round_up(size, sizeof(uint64_t))/sizeof(uint64_t)); i++) { + if (state_certificate[i] == previous) { + if (first_instance) { + puts("*"); + first_instance = false; + } + continue; + } + printf("%3d: 0x%lx\n", i, state_certificate[i]); + previous = state_certificate[i]; + first_instance = true; + } + printf("%3d\n", i); + + fclose(cert_ptr); + + return 0; +} + +static int sdsi_provision(struct sdsi_dev *s, char *bin_file, enum command command) +{ + int bin_fd, prov_fd, size, ret; + char buf[4096] = { 0 }; + char cap[] = "provision_cap"; + char akc[] = "provision_akc"; + char *prov_file; + + if (!bin_file) { + fprintf(stderr, "No binary file provided\n"); + return -1; + } + + /* Open the binary */ + bin_fd = open(bin_file, O_RDONLY); + if (bin_fd == -1) { + fprintf(stderr, "Could not open file %s: %s\n", bin_file, strerror(errno)); + return bin_fd; + } + + prov_file = (command == CMD_PROV_AKC) ? akc : cap; + + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + close(bin_fd); + return ret; + } + + /* Open the provision file */ + prov_fd = open(prov_file, O_WRONLY); + if (prov_fd == -1) { + fprintf(stderr, "Could not open file %s: %s\n", prov_file, strerror(errno)); + close(bin_fd); + return prov_fd; + } + + /* Read the binary file into the buffer */ + size = read(bin_fd, buf, 4096); + if (size == -1) { + close(bin_fd); + close(prov_fd); + return -1; + } + + ret = write(prov_fd, buf, size); + if (ret == -1) { + close(bin_fd); + close(prov_fd); + perror("Provisioning failed"); + return ret; + } + + printf("Provisioned %s file %s successfully\n", prov_file, bin_file); + + close(bin_fd); + close(prov_fd); + + return 0; +} + +static int sdsi_provision_akc(struct sdsi_dev *s, char *bin_file) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision"); + return -1; + } + + if (!s->regs.prov_avail.available) { + fprintf(stderr, "Maximum number of updates (%d) has been reached.\n", + s->regs.prov_avail.threshold); + return -1; + } + + if (s->regs.auth_fail_count.key_failure_count == + s->regs.auth_fail_count.key_failure_threshold) { + fprintf(stderr, "Maximum number of AKC provision failures (%d) has been reached.\n", + s->regs.auth_fail_count.key_failure_threshold); + fprintf(stderr, "Power cycle the system to reset the counter\n"); + return -1; + } + + return sdsi_provision(s, bin_file, CMD_PROV_AKC); +} + +static int sdsi_provision_cap(struct sdsi_dev *s, char *bin_file) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision"); + return -1; + } + + if (!s->regs.prov_avail.available) { + fprintf(stderr, "Maximum number of updates (%d) has been reached.\n", + s->regs.prov_avail.threshold); + return -1; + } + + if (s->regs.auth_fail_count.auth_failure_count == + s->regs.auth_fail_count.auth_failure_threshold) { + fprintf(stderr, "Maximum number of CAP provision failures (%d) has been reached.\n", + s->regs.auth_fail_count.auth_failure_threshold); + fprintf(stderr, "Power cycle the system to reset the counter\n"); + return -1; + } + + return sdsi_provision(s, bin_file, CMD_PROV_CAP); +} + +static int read_sysfs_data(const char *file, int *value) +{ + char buff[16]; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) { + perror(file); + return -1; + } + + if (!fgets(buff, 16, fp)) { + fprintf(stderr, "Failed to read file '%s'", file); + fclose(fp); + return -1; + } + + fclose(fp); + *value = strtol(buff, NULL, 0); + + return 0; +} + +static struct sdsi_dev *sdsi_create_dev(char *dev_no) +{ + int dev_name_len = sizeof(SDSI_DEV) + strlen(dev_no) + 1; + struct sdsi_dev *s; + int guid; + DIR *dir; + + s = (struct sdsi_dev *)malloc(sizeof(*s)); + if (!s) { + perror("malloc"); + return NULL; + } + + s->dev_name = (char *)malloc(sizeof(SDSI_DEV) + strlen(dev_no) + 1); + if (!s->dev_name) { + perror("malloc"); + free(s); + return NULL; + } + + snprintf(s->dev_name, dev_name_len, "%s.%s", SDSI_DEV, dev_no); + + s->dev_path = (char *)malloc(sizeof(AUX_DEV_PATH) + dev_name_len); + if (!s->dev_path) { + perror("malloc"); + free(s->dev_name); + free(s); + return NULL; + } + + snprintf(s->dev_path, sizeof(AUX_DEV_PATH) + dev_name_len, "%s%s", AUX_DEV_PATH, + s->dev_name); + dir = opendir(s->dev_path); + if (!dir) { + fprintf(stderr, "Could not open directory '%s': %s\n", s->dev_path, + strerror(errno)); + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + if (chdir(s->dev_path) == -1) { + perror("chdir"); + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + if (read_sysfs_data("guid", &guid)) { + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + s->guid = guid; + + return s; +} + +static void sdsi_free_dev(struct sdsi_dev *s) +{ + free(s->dev_path); + free(s->dev_name); + free(s); +} + +static void usage(char *prog) +{ + printf("Usage: %s [-l] [-d DEVNO [-iD] [-a FILE] [-c FILE]]\n", prog); +} + +static void show_help(void) +{ + printf("Commands:\n"); + printf(" %-18s\t%s\n", "-l, --list", "list available sdsi devices"); + printf(" %-18s\t%s\n", "-d, --devno DEVNO", "sdsi device number"); + printf(" %-18s\t%s\n", "-i --info", "show socket information"); + printf(" %-18s\t%s\n", "-D --dump", "dump state certificate data"); + printf(" %-18s\t%s\n", "-a --akc FILE", "provision socket with AKC FILE"); + printf(" %-18s\t%s\n", "-c --cap FILE>", "provision socket with CAP FILE"); +} + +int main(int argc, char *argv[]) +{ + char bin_file[PATH_MAX], *dev_no = NULL; + char *progname; + enum command command = CMD_NONE; + struct sdsi_dev *s; + int ret = 0, opt; + int option_index = 0; + + static struct option long_options[] = { + {"akc", required_argument, 0, 'a'}, + {"cap", required_argument, 0, 'c'}, + {"devno", required_argument, 0, 'd'}, + {"dump", no_argument, 0, 'D'}, + {"help", no_argument, 0, 'h'}, + {"info", no_argument, 0, 'i'}, + {"list", no_argument, 0, 'l'}, + {0, 0, 0, 0 } + }; + + + progname = argv[0]; + + while ((opt = getopt_long_only(argc, argv, "+a:c:d:Da:c:h", long_options, + &option_index)) != -1) { + switch (opt) { + case 'd': + dev_no = optarg; + break; + case 'l': + sdsi_list_devices(); + return 0; + case 'i': + command = CMD_SOCKET_INFO; + break; + case 'D': + command = CMD_DUMP_CERT; + break; + case 'a': + case 'c': + if (!access(optarg, F_OK) == 0) { + fprintf(stderr, "Could not open file '%s': %s\n", optarg, + strerror(errno)); + return -1; + } + + if (!realpath(optarg, bin_file)) { + perror("realpath"); + return -1; + } + + command = (opt == 'a') ? CMD_PROV_AKC : CMD_PROV_CAP; + break; + case 'h': + usage(progname); + show_help(); + return 0; + default: + usage(progname); + return -1; + } + } + + if (!dev_no) { + if (command != CMD_NONE) + fprintf(stderr, "Missing device number, DEVNO, for this command\n"); + usage(progname); + return -1; + } + + s = sdsi_create_dev(dev_no); + if (!s) + return -1; + + /* Run the command */ + switch (command) { + case CMD_NONE: + fprintf(stderr, "Missing command for device %s\n", dev_no); + usage(progname); + break; + case CMD_SOCKET_INFO: + ret = sdsi_read_reg(s); + break; + case CMD_DUMP_CERT: + ret = sdsi_certificate_dump(s); + break; + case CMD_PROV_AKC: + ret = sdsi_provision_akc(s, bin_file); + break; + case CMD_PROV_CAP: + ret = sdsi_provision_cap(s, bin_file); + break; + } + + + sdsi_free_dev(s); + + return ret; +} diff --git a/tools/testing/selftests/drivers/sdsi/sdsi.sh b/tools/testing/selftests/drivers/sdsi/sdsi.sh new file mode 100755 index 000000000000..9b84b9b82b49 --- /dev/null +++ b/tools/testing/selftests/drivers/sdsi/sdsi.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Runs tests for the intel_sdsi driver + +if ! command -v python3 > /dev/null 2>&1; then + echo "drivers/sdsi: [SKIP] python3 not installed" + exit 77 +fi + +if ! python3 -c "import pytest" > /dev/null 2>&1; then + echo "drivers/sdsi: [SKIP] pytest module not installed" + exit 77 +fi + +if ! /sbin/modprobe -q -r intel_sdsi; then + echo "drivers/sdsi: [SKIP]" + exit 77 +fi + +if /sbin/modprobe -q intel_sdsi && python3 -m pytest sdsi_test.py; then + echo "drivers/sdsi: [OK]" +else + echo "drivers/sdsi: [FAIL]" + exit 1 +fi diff --git a/tools/testing/selftests/drivers/sdsi/sdsi_test.py b/tools/testing/selftests/drivers/sdsi/sdsi_test.py new file mode 100644 index 000000000000..5efb29feee70 --- /dev/null +++ b/tools/testing/selftests/drivers/sdsi/sdsi_test.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +from struct import pack +from time import sleep + +import errno +import glob +import os +import subprocess + +try: + import pytest +except ImportError: + print("Unable to import pytest python module.") + print("\nIf not already installed, you may do so with:") + print("\t\tpip3 install pytest") + exit(1) + +SOCKETS = glob.glob('/sys/bus/auxiliary/devices/intel_vsec.sdsi.*') +NUM_SOCKETS = len(SOCKETS) + +MODULE_NAME = 'intel_sdsi' +DEV_PREFIX = 'intel_vsec.sdsi' +CLASS_DIR = '/sys/bus/auxiliary/devices' +GUID = "0x6dd191" + +def read_bin_file(file): + with open(file, mode='rb') as f: + content = f.read() + return content + +def get_dev_file_path(socket, file): + return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/' + file + +def kmemleak_enabled(): + kmemleak = "/sys/kernel/debug/kmemleak" + return os.path.isfile(kmemleak) + +class TestSDSiDriver: + def test_driver_loaded(self): + lsmod_p = subprocess.Popen(('lsmod'), stdout=subprocess.PIPE) + result = subprocess.check_output(('grep', '-q', MODULE_NAME), stdin=lsmod_p.stdout) + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSDSiFilesClass: + + def read_value(self, file): + f = open(file, "r") + value = f.read().strip("\n") + return value + + def get_dev_folder(self, socket): + return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/' + + def test_sysfs_files_exist(self, socket): + folder = self.get_dev_folder(socket) + print (folder) + assert os.path.isfile(folder + "guid") == True + assert os.path.isfile(folder + "provision_akc") == True + assert os.path.isfile(folder + "provision_cap") == True + assert os.path.isfile(folder + "state_certificate") == True + assert os.path.isfile(folder + "registers") == True + + def test_sysfs_file_permissions(self, socket): + folder = self.get_dev_folder(socket) + mode = os.stat(folder + "guid").st_mode & 0o777 + assert mode == 0o444 # Read all + mode = os.stat(folder + "registers").st_mode & 0o777 + assert mode == 0o400 # Read owner + mode = os.stat(folder + "provision_akc").st_mode & 0o777 + assert mode == 0o200 # Read owner + mode = os.stat(folder + "provision_cap").st_mode & 0o777 + assert mode == 0o200 # Read owner + mode = os.stat(folder + "state_certificate").st_mode & 0o777 + assert mode == 0o400 # Read owner + + def test_sysfs_file_ownership(self, socket): + folder = self.get_dev_folder(socket) + + st = os.stat(folder + "guid") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "registers") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "provision_akc") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "provision_cap") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "state_certificate") + assert st.st_uid == 0 + assert st.st_gid == 0 + + def test_sysfs_file_sizes(self, socket): + folder = self.get_dev_folder(socket) + + if self.read_value(folder + "guid") == GUID: + st = os.stat(folder + "registers") + assert st.st_size == 72 + + st = os.stat(folder + "provision_akc") + assert st.st_size == 1024 + + st = os.stat(folder + "provision_cap") + assert st.st_size == 1024 + + st = os.stat(folder + "state_certificate") + assert st.st_size == 4096 + + def test_no_seek_allowed(self, socket): + folder = self.get_dev_folder(socket) + rand_file = bytes(os.urandom(8)) + + f = open(folder + "provision_cap", "wb", 0) + f.seek(1) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ESPIPE + f.close() + + f = open(folder + "provision_akc", "wb", 0) + f.seek(1) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ESPIPE + f.close() + + def test_registers_seek(self, socket): + folder = self.get_dev_folder(socket) + + # Check that the value read from an offset of the entire + # file is none-zero and the same as the value read + # from seeking to the same location + f = open(folder + "registers", "rb") + data = f.read() + f.seek(64) + id = f.read() + assert id != bytes(0) + assert data[64:] == id + f.close() + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSDSiMailboxCmdsClass: + def test_provision_akc_eoverflow_1017_bytes(self, socket): + + # The buffer for writes is 1k, of with 8 bytes must be + # reserved for the command, leaving 1016 bytes max. + # Check that we get an overflow error for 1017 bytes. + node = get_dev_file_path(socket, "provision_akc") + rand_file = bytes(os.urandom(1017)) + + f = open(node, 'wb', 0) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.EOVERFLOW + f.close() + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSdsiDriverLocksClass: + def test_enodev_when_pci_device_removed(self, socket): + node = get_dev_file_path(socket, "provision_akc") + dev_name = DEV_PREFIX + '.' + str(socket) + driver_dir = CLASS_DIR + '/' + dev_name + "/driver/" + rand_file = bytes(os.urandom(8)) + + f = open(node, 'wb', 0) + g = open(node, 'wb', 0) + + with open(driver_dir + 'unbind', 'w') as k: + print(dev_name, file = k) + + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ENODEV + + with pytest.raises(OSError) as error: + g.write(rand_file) + assert error.value.errno == errno.ENODEV + + f.close() + g.close() + + # Short wait needed to allow file to close before pulling driver + sleep(1) + + p = subprocess.Popen(('modprobe', '-r', 'intel_sdsi')) + p.wait() + p = subprocess.Popen(('modprobe', '-r', 'intel_vsec')) + p.wait() + p = subprocess.Popen(('modprobe', 'intel_vsec')) + p.wait() + + # Short wait needed to allow driver time to get inserted + # before continuing tests + sleep(1) + + def test_memory_leak(self, socket): + if not kmemleak_enabled(): + pytest.skip("kmemleak not enabled in kernel") + + dev_name = DEV_PREFIX + '.' + str(socket) + driver_dir = CLASS_DIR + '/' + dev_name + "/driver/" + + with open(driver_dir + 'unbind', 'w') as k: + print(dev_name, file = k) + + sleep(1) + + subprocess.check_output(('modprobe', '-r', 'intel_sdsi')) + subprocess.check_output(('modprobe', '-r', 'intel_vsec')) + + with open('/sys/kernel/debug/kmemleak', 'w') as f: + print('scan', file = f) + sleep(5) + + assert os.stat('/sys/kernel/debug/kmemleak').st_size == 0 + + subprocess.check_output(('modprobe', 'intel_vsec')) + sleep(1)