diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-18 10:26:57 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-18 10:26:57 -0800 |
commit | 07c455ee222f3ad219c2835d05a175a326a138fb (patch) | |
tree | c3fdcd89a4fe87877963162de8bc428bb265bb8e | |
parent | 1deab8ce2c91e3b16563b7a7ea150f82334262ec (diff) | |
parent | aaa40965d2342137d756121993c395e2a7463a8d (diff) | |
download | linux-07c455ee222f3ad219c2835d05a175a326a138fb.tar.bz2 |
Merge tag 'platform-drivers-x86-v4.15-1' of git://git.infradead.org/linux-platform-drivers-x86
Pull x86 platform driver updates from Andy Shevchenko:
"Here is the collected material against Platform Drivers x86 subsystem.
It's rather bit busy cycle for PDx86, mostly due to Dell SMBIOS driver
activity
For this cycle we have quite an update for the Dell SMBIOS driver
including WMI work to provide an interface for SMBIOS tokens via sysfs
and WMI support for 2017+ Dell laptop models. SMM dispatcher code is
split into a separate driver followed by a new WMI dispatcher. The
latter provides a character device interface to user space.
The git history also contains a merge of immutable branch from Wolfram
Sang in order to apply a dependent fix to the Intel CherryTrail
Battery Management driver.
Other Intel drivers got a lot of cleanups. The Turbo Boost Max 3.0
support is added for Intel Skylake.
Peaq WMI hotkeys driver gets its own maintainer and white list of
supported models.
Silead DMI is expanded to support few additional platforms.
Tablet mode via GMMS ACPI method is added to support some ThinkPad
tablets.
new driver:
- Add driver to force WMI Thunderbolt controller power status
asus-wmi:
- Add lightbar led support
dell-laptop:
- Allocate buffer before rfkill use
dell-smbios:
- fix string overflow
- Add filtering support
- Introduce dispatcher for SMM calls
- Add a sysfs interface for SMBIOS tokens
- only run if proper oem string is detected
- Prefix class/select with cmd_
- Add pr_fmt definition to driver
dell-smbios-smm:
- test for WSMT
dell-smbios-wmi:
- release mutex lock on WMI call failure
- introduce userspace interface
- Add new WMI dispatcher driver
dell-smo8800:
- remove redundant assignments to byte_data
dell-wmi:
- don't check length returned
- clean up wmi descriptor check
- increase severity of some failures
- Do not match on descriptor GUID modalias
- Label driver as handling notifications
dell-*wmi*:
- Relay failed initial probe to dependent drivers
dell-wmi-descriptor:
- check if memory was allocated
- split WMI descriptor into it's own driver
fujitsu-laptop:
- Fix radio LED detection
- Don't oops when FUJ02E3 is not presnt
hp_accel:
- Add quirk for HP ProBook 440 G4
hp-wmi:
- Fix tablet mode detection for convertibles
ideapad-laptop:
- Add Lenovo Yoga 920-13IKB to no_hw_rfkill dmi list
intel_cht_int33fe:
- Update fusb302 type string, add properties
- make a couple of local functions static
- Work around BIOS bug on some devices
intel-hid:
- Power button suspend on Dell Latitude 7275
intel_ips:
- Convert timers to use timer_setup()
- Remove FSF address from GPL notice
- Remove unneeded fields and label
- Keep pointer to struct device
- Use PCI_VDEVICE() macro
- Switch to new PCI IRQ allocation API
- Simplify error handling via devres API
intel_pmc_ipc:
- Revert Use MFD framework to create dependent devices
- Use MFD framework to create dependent devices
- Use spin_lock to protect GCR updates
- Use devm_* calls in driver probe function
intel_punit_ipc:
- Fix resource ioremap warning
intel_telemetry:
- Remove useless default in Kconfig
- Add needed inclusion
- cleanup redundant headers
- Fix typos
- Fix load failure info
intel_telemetry_debugfs:
- Use standard ARRAY_SIZE() macro
intel_turbo_max_3:
- Add Skylake platform
intel-wmi-thunderbolt:
- Silence error cases
mlx-platform:
- make a couple of structures static
peaq_wmi:
- Fix missing terminating entry for peaq_dmi_table
peaq-wmi:
- Remove unnecessary checks from peaq_wmi_exit
- Add DMI check before binding to the WMI interface
- Revert Blacklist Lenovo ideapad 700-15ISK
- Blacklist Lenovo ideapad 700-15ISK
silead_dmi:
- Add silead, home-button property to some tablets
- Add entry for the Digma e200 tablet
- Fix GP-electronic T701 entry
- Add entry for the Chuwi Hi8 Pro tablet
sony-laptop:
- Drop variable assignment in sony_nc_setup_rfkill()
- Fix error handling in sony_nc_setup_rfkill()
thinkpad_acpi:
- Implement tablet mode using GMMS method
tools/wmi:
- add a sample for dell smbios communication over WMI
wmi:
- release mutex on module acquistion failure
- create userspace interface for drivers
- Don't allow drivers to get each other's GUIDs
- Add new method wmidev_evaluate_method
- Destroy on cleanup rather than unregister
- Cleanup exit routine in reverse order of init
- Sort include list"
* tag 'platform-drivers-x86-v4.15-1' of git://git.infradead.org/linux-platform-drivers-x86: (74 commits)
platform/x86: silead_dmi: Add silead, home-button property to some tablets
platform/x86: dell-laptop: Allocate buffer before rfkill use
platform/x86: dell-*wmi*: Relay failed initial probe to dependent drivers
platform/x86: dell-wmi-descriptor: check if memory was allocated
platform/x86: Revert intel_pmc_ipc: Use MFD framework to create dependent devices
platform/x86: dell-smbios-wmi: release mutex lock on WMI call failure
platform/x86: wmi: release mutex on module acquistion failure
platform/x86: dell-smbios: fix string overflow
platform/x86: intel_pmc_ipc: Use MFD framework to create dependent devices
platform/x86: intel_punit_ipc: Fix resource ioremap warning
platform/x86: dell-smo8800: remove redundant assignments to byte_data
platform/x86: hp-wmi: Fix tablet mode detection for convertibles
platform/x86: intel_ips: Convert timers to use timer_setup()
platform/x86: sony-laptop: Drop variable assignment in sony_nc_setup_rfkill()
platform/x86: sony-laptop: Fix error handling in sony_nc_setup_rfkill()
tools/wmi: add a sample for dell smbios communication over WMI
platform/x86: dell-smbios-wmi: introduce userspace interface
platform/x86: wmi: create userspace interface for drivers
platform/x86: dell-smbios: Add filtering support
platform/x86: dell-smbios-smm: test for WSMT
...
42 files changed, 2613 insertions, 552 deletions
diff --git a/Documentation/ABI/testing/dell-smbios-wmi b/Documentation/ABI/testing/dell-smbios-wmi new file mode 100644 index 000000000000..fc919ce16008 --- /dev/null +++ b/Documentation/ABI/testing/dell-smbios-wmi @@ -0,0 +1,41 @@ +What: /dev/wmi/dell-smbios +Date: November 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" <mario.limonciello@dell.com> +Description: + Perform SMBIOS calls on supported Dell machines. + through the Dell ACPI-WMI interface. + + IOCTL's and buffer formats are defined in: + <uapi/linux/wmi.h> + + 1) To perform an SMBIOS call from userspace, you'll need to + first determine the minimum size of the calling interface + buffer for your machine. + Platforms that contain larger buffers can return larger + objects from the system firmware. + Commonly this size is either 4k or 32k. + + To determine the size of the buffer read() a u64 dword from + the WMI character device /dev/wmi/dell-smbios. + + 2) After you've determined the minimum size of the calling + interface buffer, you can allocate a structure that represents + the structure documented above. + + 3) In the 'length' object store the size of the buffer you + determined above and allocated. + + 4) In this buffer object, prepare as necessary for the SMBIOS + call you're interested in. Typically SMBIOS buffers have + "class", "select", and "input" defined to values that coincide + with the data you are interested in. + Documenting class/select/input values is outside of the scope + of this documentation. Check with the libsmbios project for + further documentation on these values. + + 6) Run the call by using ioctl() as described in the header. + + 7) The output will be returned in the buffer object. + + 8) Be sure to free up your allocated object. diff --git a/Documentation/ABI/testing/sysfs-platform-dell-smbios b/Documentation/ABI/testing/sysfs-platform-dell-smbios new file mode 100644 index 000000000000..205d3b6361e0 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dell-smbios @@ -0,0 +1,21 @@ +What: /sys/devices/platform/<platform>/tokens/* +Date: November 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" <mario.limonciello@dell.com> +Description: + A read-only description of Dell platform tokens + available on the machine. + + Each token attribute is available as a pair of + sysfs attributes readable by a process with + CAP_SYS_ADMIN. + + For example the token ID "5" would be available + as the following attributes: + + 0005_location + 0005_value + + Tokens will vary from machine to machine, and + only tokens available on that machine will be + displayed. diff --git a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt new file mode 100644 index 000000000000..8af65059d519 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt @@ -0,0 +1,11 @@ +What: /sys/devices/platform/<platform>/force_power +Date: September 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" <mario.limonciello@dell.com> +Description: + Modify the platform force power state, influencing + Thunderbolt controllers to turn on or off when no + devices are connected (write-only) + There are two available states: + * 0 -> Force power disabled + * 1 -> Force power enabled diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 5c62d11d77e8..de50a8561774 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -221,3 +221,18 @@ The driver will create one virtual ethernet interface per Thunderbolt port which are named like ``thunderbolt0`` and so on. From this point you can either use standard userspace tools like ``ifconfig`` to configure the interface or let your GUI to handle it automatically. + +Forcing power +------------- +Many OEMs include a method that can be used to force the power of a +thunderbolt controller to an "On" state even if nothing is connected. +If supported by your machine this will be exposed by the WMI bus with +a sysfs attribute called "force_power". + +For example the intel-wmi-thunderbolt driver exposes this attribute in: + /sys/devices/platform/PNP0C14:00/wmi_bus/wmi_bus-PNP0C14:00/86CCFD48-205E-4A77-9C48-2021CBEDE341/force_power + + To force the power to on, write 1 to this attribute file. + To disable force power, write 0 to this attribute file. + +Note: it's currently not possible to query the force power state of a platform. diff --git a/MAINTAINERS b/MAINTAINERS index 16137acd7f2f..bf3cf8a8974f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -384,6 +384,7 @@ ACPI WMI DRIVER L: platform-driver-x86@vger.kernel.org S: Orphan F: drivers/platform/x86/wmi.c +F: include/uapi/linux/wmi.h AD1889 ALSA SOUND DRIVER M: Thibaut Varene <T-Bone@parisc-linux.org> @@ -4030,6 +4031,26 @@ M: "Maciej W. Rozycki" <macro@linux-mips.org> S: Maintained F: drivers/net/fddi/defxx.* +DELL SMBIOS DRIVER +M: Pali Rohár <pali.rohar@gmail.com> +M: Mario Limonciello <mario.limonciello@dell.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios.* + +DELL SMBIOS SMM DRIVER +M: Mario Limonciello <mario.limonciello@dell.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios-smm.c + +DELL SMBIOS WMI DRIVER +M: Mario Limonciello <mario.limonciello@dell.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios-wmi.c +F: tools/wmi/dell-smbios-example.c + DELL LAPTOP DRIVER M: Matthew Garrett <mjg59@srcf.ucam.org> M: Pali Rohár <pali.rohar@gmail.com> @@ -4059,12 +4080,17 @@ S: Maintained F: Documentation/dcdbas.txt F: drivers/firmware/dcdbas.* -DELL WMI EXTRAS DRIVER +DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett <mjg59@srcf.ucam.org> M: Pali Rohár <pali.rohar@gmail.com> S: Maintained F: drivers/platform/x86/dell-wmi.c +DELL WMI DESCRIPTOR DRIVER +M: Mario Limonciello <mario.limonciello@dell.com> +S: Maintained +F: drivers/platform/x86/dell-wmi-descriptor.c + DELTA ST MEDIA DRIVER M: Hugues Fruchet <hugues.fruchet@st.com> L: linux-media@vger.kernel.org @@ -7181,6 +7207,11 @@ F: Documentation/wimax/README.i2400m F: drivers/net/wimax/i2400m/ F: include/uapi/linux/wimax/i2400m.h +INTEL WMI THUNDERBOLT FORCE POWER DRIVER +M: Mario Limonciello <mario.limonciello@dell.com> +S: Maintained +F: drivers/platform/x86/intel-wmi-thunderbolt.c + INTEL(R) TRACE HUB M: Alexander Shishkin <alexander.shishkin@linux.intel.com> S: Supported @@ -10630,6 +10661,12 @@ S: Maintained F: crypto/pcrypt.c F: include/crypto/pcrypt.h +PEAQ WMI HOTKEYS DRIVER +M: Hans de Goede <hdegoede@redhat.com> +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/peaq-wmi.c + PER-CPU MEMORY ALLOCATOR M: Tejun Heo <tj@kernel.org> M: Christoph Lameter <cl@linux.com> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 09dac11337d1..344c78f0a5c4 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -93,12 +93,33 @@ config ASUS_LAPTOP config DELL_SMBIOS tristate - select DCDBAS + +config DELL_SMBIOS_WMI + tristate "Dell SMBIOS calling interface (WMI implementation)" + depends on ACPI_WMI + select DELL_WMI_DESCRIPTOR + default ACPI_WMI + select DELL_SMBIOS + ---help--- + This provides an implementation for the Dell SMBIOS calling interface + communicated over ACPI-WMI. + + If you have a Dell computer from >2007 you should say Y or M here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMBIOS_SMM + tristate "Dell SMBIOS calling interface (SMM implementation)" + depends on DCDBAS + default DCDBAS + select DELL_SMBIOS ---help--- - This module provides common functions for kernel modules using - Dell SMBIOS. + This provides an implementation for the Dell SMBIOS calling interface + communicated over SMI/SMM. - If you have a Dell laptop, say Y or M here. + If you have a Dell computer from <=2017 you should say Y or M here. + If you aren't sure and this module doesn't work for your computer + it just won't load. config DELL_LAPTOP tristate "Dell Laptop Extras" @@ -116,11 +137,12 @@ config DELL_LAPTOP laptops (except for some models covered by the Compal driver). config DELL_WMI - tristate "Dell WMI extras" + tristate "Dell WMI notifications" depends on ACPI_WMI depends on DMI depends on INPUT depends on ACPI_VIDEO || ACPI_VIDEO = n + select DELL_WMI_DESCRIPTOR select DELL_SMBIOS select INPUT_SPARSEKMAP ---help--- @@ -129,6 +151,10 @@ config DELL_WMI To compile this driver as a module, choose M here: the module will be called dell-wmi. +config DELL_WMI_DESCRIPTOR + tristate + depends on ACPI_WMI + config DELL_WMI_AIO tristate "WMI Hotkeys for Dell All-In-One series" depends on ACPI_WMI @@ -658,6 +684,19 @@ config WMI_BMOF To compile this driver as a module, choose M here: the module will be called wmi-bmof. +config INTEL_WMI_THUNDERBOLT + tristate "Intel WMI thunderbolt force power driver" + depends on ACPI_WMI + default ACPI_WMI + ---help--- + Say Y here if you want to be able to use the WMI interface on select + systems to force the power control of Intel Thunderbolt controllers. + This is useful for updating the firmware when devices are not plugged + into the controller. + + To compile this driver as a module, choose M here: the module will + be called intel-wmi-thunderbolt. + config MSI_WMI tristate "MSI WMI extras" depends on ACPI_WMI @@ -793,7 +832,7 @@ config ACPI_CMPC config INTEL_CHT_INT33FE tristate "Intel Cherry Trail ACPI INT33FE Driver" - depends on X86 && ACPI && I2C + depends on X86 && ACPI && I2C && REGULATOR ---help--- This driver add support for the INT33FE ACPI device found on some Intel Cherry Trail devices. @@ -804,6 +843,10 @@ config INTEL_CHT_INT33FE 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_CHARGER_BQ24190=m and + CONFIG_BATTERY_MAX17042=m. + config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" depends on GPIOLIB && ACPI @@ -1088,7 +1131,6 @@ config INTEL_PUNIT_IPC config INTEL_TELEMETRY tristate "Intel SoC Telemetry Driver" - default n depends on INTEL_PMC_IPC && INTEL_PUNIT_IPC && X86_64 ---help--- This driver provides interfaces to configure and use diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index f9e3ae683bbe..c32b34a72467 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -13,8 +13,11 @@ obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o +obj-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o +obj-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o +obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o @@ -40,6 +43,7 @@ obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o +obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o # toshiba_acpi must link after wmi to ensure that wmi devices are found # before toshiba_acpi initializes diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 48e1541dc8d4..a32c5c00e0e7 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -119,6 +119,7 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012 #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ +#define ASUS_WMI_DEVID_LIGHTBAR 0x00050025 /* Misc */ #define ASUS_WMI_DEVID_CAMERA 0x00060013 @@ -148,6 +149,7 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_DSTS_BIOS_BIT 0x00040000 #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 +#define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F #define ASUS_FAN_DESC "cpu_fan" #define ASUS_FAN_MFUN 0x13 @@ -222,10 +224,13 @@ struct asus_wmi { int tpd_led_wk; struct led_classdev kbd_led; int kbd_led_wk; + struct led_classdev lightbar_led; + int lightbar_led_wk; struct workqueue_struct *led_workqueue; struct work_struct tpd_led_work; struct work_struct kbd_led_work; struct work_struct wlan_led_work; + struct work_struct lightbar_led_work; struct asus_rfkill wlan; struct asus_rfkill bluetooth; @@ -567,6 +572,48 @@ static enum led_brightness wlan_led_get(struct led_classdev *led_cdev) return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK; } +static void lightbar_led_update(struct work_struct *work) +{ + struct asus_wmi *asus; + int ctrl_param; + + asus = container_of(work, struct asus_wmi, lightbar_led_work); + + ctrl_param = asus->lightbar_led_wk; + asus_wmi_set_devstate(ASUS_WMI_DEVID_LIGHTBAR, ctrl_param, NULL); +} + +static void lightbar_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct asus_wmi *asus; + + asus = container_of(led_cdev, struct asus_wmi, lightbar_led); + + asus->lightbar_led_wk = !!value; + queue_work(asus->led_workqueue, &asus->lightbar_led_work); +} + +static enum led_brightness lightbar_led_get(struct led_classdev *led_cdev) +{ + struct asus_wmi *asus; + u32 result; + + asus = container_of(led_cdev, struct asus_wmi, lightbar_led); + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_LIGHTBAR, &result); + + return result & ASUS_WMI_DSTS_LIGHTBAR_MASK; +} + +static int lightbar_led_presence(struct asus_wmi *asus) +{ + u32 result; + + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_LIGHTBAR, &result); + + return result & ASUS_WMI_DSTS_PRESENCE_BIT; +} + static void asus_wmi_led_exit(struct asus_wmi *asus) { if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) @@ -575,6 +622,8 @@ static void asus_wmi_led_exit(struct asus_wmi *asus) led_classdev_unregister(&asus->tpd_led); if (!IS_ERR_OR_NULL(asus->wlan_led.dev)) led_classdev_unregister(&asus->wlan_led); + if (!IS_ERR_OR_NULL(asus->lightbar_led.dev)) + led_classdev_unregister(&asus->lightbar_led); if (asus->led_workqueue) destroy_workqueue(asus->led_workqueue); } @@ -630,6 +679,20 @@ static int asus_wmi_led_init(struct asus_wmi *asus) rv = led_classdev_register(&asus->platform_device->dev, &asus->wlan_led); + if (rv) + goto error; + } + + if (lightbar_led_presence(asus)) { + INIT_WORK(&asus->lightbar_led_work, lightbar_led_update); + + asus->lightbar_led.name = "asus::lightbar"; + asus->lightbar_led.brightness_set = lightbar_led_set; + asus->lightbar_led.brightness_get = lightbar_led_get; + asus->lightbar_led.max_brightness = 1; + + rv = led_classdev_register(&asus->platform_device->dev, + &asus->lightbar_led); } error: diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index f42159fd2031..2d704361f672 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -35,18 +35,6 @@ #include "dell-rbtn.h" #include "dell-smbios.h" -#define BRIGHTNESS_TOKEN 0x7d -#define KBD_LED_OFF_TOKEN 0x01E1 -#define KBD_LED_ON_TOKEN 0x01E2 -#define KBD_LED_AUTO_TOKEN 0x01E3 -#define KBD_LED_AUTO_25_TOKEN 0x02EA -#define KBD_LED_AUTO_50_TOKEN 0x02EB -#define KBD_LED_AUTO_75_TOKEN 0x02EC -#define KBD_LED_AUTO_100_TOKEN 0x02F6 -#define GLOBAL_MIC_MUTE_ENABLE 0x0364 -#define GLOBAL_MIC_MUTE_DISABLE 0x0365 -#define KBD_LED_AC_TOKEN 0x0451 - struct quirk_entry { u8 touchpad_led; @@ -85,6 +73,7 @@ static struct platform_driver platform_driver = { } }; +static struct calling_interface_buffer *buffer; static struct platform_device *platform_device; static struct backlight_device *dell_backlight_device; static struct rfkill *wifi_rfkill; @@ -283,6 +272,27 @@ static const struct dmi_system_id dell_quirks[] __initconst = { { } }; +void dell_set_arguments(u32 arg0, u32 arg1, u32 arg2, u32 arg3) +{ + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = arg0; + buffer->input[1] = arg1; + buffer->input[2] = arg2; + buffer->input[3] = arg3; +} + +int dell_send_request(u16 class, u16 select) +{ + int ret; + + buffer->cmd_class = class; + buffer->cmd_select = select; + ret = dell_smbios_call(buffer); + if (ret != 0) + return ret; + return dell_smbios_error(buffer->output[0]); +} + /* * Derived from information in smbios-wireless-ctl: * @@ -405,7 +415,6 @@ static const struct dmi_system_id dell_quirks[] __initconst = { static int dell_rfkill_set(void *data, bool blocked) { - struct calling_interface_buffer *buffer; int disable = blocked ? 1 : 0; unsigned long radio = (unsigned long)data; int hwswitch_bit = (unsigned long)data - 1; @@ -413,20 +422,16 @@ static int dell_rfkill_set(void *data, bool blocked) int status; int ret; - buffer = dell_smbios_get_buffer(); - - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; status = buffer->output[1]; - if (ret != 0) - goto out; - - dell_smbios_clear_buffer(); - - buffer->input[0] = 0x2; - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0x2, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; hwswitch = buffer->output[1]; /* If the hardware switch controls this radio, and the hardware @@ -435,28 +440,19 @@ static int dell_rfkill_set(void *data, bool blocked) (status & BIT(0)) && !(status & BIT(16))) disable = 1; - dell_smbios_clear_buffer(); - - buffer->input[0] = (1 | (radio<<8) | (disable << 16)); - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; - - out: - dell_smbios_release_buffer(); - return dell_smbios_error(ret); + dell_set_arguments(1 | (radio<<8) | (disable << 16), 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + return ret; } -/* Must be called with the buffer held */ static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, - int status, - struct calling_interface_buffer *buffer) + int status) { if (status & BIT(0)) { /* Has hw-switch, sync sw_state to BIOS */ int block = rfkill_blocked(rfkill); - dell_smbios_clear_buffer(); - buffer->input[0] = (1 | (radio << 8) | (block << 16)); - dell_smbios_send_request(17, 11); + dell_set_arguments(1 | (radio << 8) | (block << 16), 0, 0, 0); + dell_send_request(CLASS_INFO, SELECT_RFKILL); } else { /* No hw-switch, sync BIOS state to sw_state */ rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); @@ -472,32 +468,23 @@ static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, static void dell_rfkill_query(struct rfkill *rfkill, void *data) { - struct calling_interface_buffer *buffer; int radio = ((unsigned long)data & 0xF); int hwswitch; int status; int ret; - buffer = dell_smbios_get_buffer(); - - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); status = buffer->output[1]; if (ret != 0 || !(status & BIT(0))) { - dell_smbios_release_buffer(); return; } - dell_smbios_clear_buffer(); - - buffer->input[0] = 0x2; - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0x2, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); hwswitch = buffer->output[1]; - dell_smbios_release_buffer(); - if (ret != 0) return; @@ -513,27 +500,23 @@ static struct dentry *dell_laptop_dir; static int dell_debugfs_show(struct seq_file *s, void *data) { - struct calling_interface_buffer *buffer; int hwswitch_state; int hwswitch_ret; int status; int ret; - buffer = dell_smbios_get_buffer(); - - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; status = buffer->output[1]; - dell_smbios_clear_buffer(); - - buffer->input[0] = 0x2; - dell_smbios_send_request(17, 11); - hwswitch_ret = buffer->output[0]; + dell_set_arguments(0, 0x2, 0, 0); + hwswitch_ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + if (hwswitch_ret) + return hwswitch_ret; hwswitch_state = buffer->output[1]; - dell_smbios_release_buffer(); - seq_printf(s, "return:\t%d\n", ret); seq_printf(s, "status:\t0x%X\n", status); seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", @@ -613,46 +596,36 @@ static const struct file_operations dell_debugfs_fops = { static void dell_update_rfkill(struct work_struct *ignored) { - struct calling_interface_buffer *buffer; int hwswitch = 0; int status; int ret; - buffer = dell_smbios_get_buffer(); - - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); status = buffer->output[1]; if (ret != 0) - goto out; - - dell_smbios_clear_buffer(); + return; - buffer->input[0] = 0x2; - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0x2, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); if (ret == 0 && (status & BIT(0))) hwswitch = buffer->output[1]; if (wifi_rfkill) { dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); - dell_rfkill_update_sw_state(wifi_rfkill, 1, status, buffer); + dell_rfkill_update_sw_state(wifi_rfkill, 1, status); } if (bluetooth_rfkill) { dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status, hwswitch); - dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status, - buffer); + dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); } if (wwan_rfkill) { dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch); - dell_rfkill_update_sw_state(wwan_rfkill, 3, status, buffer); + dell_rfkill_update_sw_state(wwan_rfkill, 3, status); } - - out: - dell_smbios_release_buffer(); } static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); @@ -696,7 +669,6 @@ static struct notifier_block dell_laptop_rbtn_notifier = { static int __init dell_setup_rfkill(void) { - struct calling_interface_buffer *buffer; int status, ret, whitelisted; const char *product; @@ -712,11 +684,9 @@ static int __init dell_setup_rfkill(void) if (!force_rfkill && !whitelisted) return 0; - buffer = dell_smbios_get_buffer(); - dell_smbios_send_request(17, 11); - ret = buffer->output[0]; + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); status = buffer->output[1]; - dell_smbios_release_buffer(); /* dell wireless info smbios call is not supported */ if (ret != 0) @@ -869,7 +839,6 @@ static void dell_cleanup_rfkill(void) static int dell_send_intensity(struct backlight_device *bd) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; int ret; @@ -877,24 +846,17 @@ static int dell_send_intensity(struct backlight_device *bd) if (!token) return -ENODEV; - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - buffer->input[1] = bd->props.brightness; - + dell_set_arguments(token->location, bd->props.brightness, 0, 0); if (power_supply_is_system_supplied() > 0) - dell_smbios_send_request(1, 2); + ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); else - dell_smbios_send_request(1, 1); + ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); - ret = dell_smbios_error(buffer->output[0]); - - dell_smbios_release_buffer(); return ret; } static int dell_get_intensity(struct backlight_device *bd) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; int ret; @@ -902,20 +864,14 @@ static int dell_get_intensity(struct backlight_device *bd) if (!token) return -ENODEV; - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - + dell_set_arguments(token->location, 0, 0, 0); if (power_supply_is_system_supplied() > 0) - dell_smbios_send_request(0, 2); + ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_AC); else - dell_smbios_send_request(0, 1); + ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_BAT); - if (buffer->output[0]) - ret = dell_smbios_error(buffer->output[0]); - else + if (ret == 0) ret = buffer->output[1]; - - dell_smbios_release_buffer(); return ret; } @@ -1179,20 +1135,13 @@ static DEFINE_MUTEX(kbd_led_mutex); static int kbd_get_info(struct kbd_info *info) { - struct calling_interface_buffer *buffer; u8 units; int ret; - buffer = dell_smbios_get_buffer(); - - buffer->input[0] = 0x0; - dell_smbios_send_request(4, 11); - ret = buffer->output[0]; - - if (ret) { - ret = dell_smbios_error(ret); - goto out; - } + dell_set_arguments(0, 0, 0, 0); + ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; info->modes = buffer->output[1] & 0xFFFF; info->type = (buffer->output[1] >> 24) & 0xFF; @@ -1209,8 +1158,6 @@ static int kbd_get_info(struct kbd_info *info) if (units & BIT(3)) info->days = (buffer->output[3] >> 24) & 0xFF; - out: - dell_smbios_release_buffer(); return ret; } @@ -1269,19 +1216,12 @@ static int kbd_set_level(struct kbd_state *state, u8 level) static int kbd_get_state(struct kbd_state *state) { - struct calling_interface_buffer *buffer; int ret; - buffer = dell_smbios_get_buffer(); - - buffer->input[0] = 0x1; - dell_smbios_send_request(4, 11); - ret = buffer->output[0]; - - if (ret) { - ret = dell_smbios_error(ret); - goto out; - } + dell_set_arguments(0x1, 0, 0, 0); + ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; state->mode_bit = ffs(buffer->output[1] & 0xFFFF); if (state->mode_bit != 0) @@ -1296,31 +1236,27 @@ static int kbd_get_state(struct kbd_state *state) state->timeout_value_ac = (buffer->output[2] >> 24) & 0x3F; state->timeout_unit_ac = (buffer->output[2] >> 30) & 0x3; - out: - dell_smbios_release_buffer(); return ret; } static int kbd_set_state(struct kbd_state *state) { - struct calling_interface_buffer *buffer; int ret; + u32 input1; + u32 input2; + + input1 = BIT(state->mode_bit) & 0xFFFF; + input1 |= (state->triggers & 0xFF) << 16; + input1 |= (state->timeout_value & 0x3F) << 24; + input1 |= (state->timeout_unit & 0x3) << 30; + input2 = state->als_setting & 0xFF; + input2 |= (state->level & 0xFF) << 16; + input2 |= (state->timeout_value_ac & 0x3F) << 24; + input2 |= (state->timeout_unit_ac & 0x3) << 30; + dell_set_arguments(0x2, input1, input2, 0); + ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - buffer = dell_smbios_get_buffer(); - buffer->input[0] = 0x2; - buffer->input[1] = BIT(state->mode_bit) & 0xFFFF; - buffer->input[1] |= (state->triggers & 0xFF) << 16; - buffer->input[1] |= (state->timeout_value & 0x3F) << 24; - buffer->input[1] |= (state->timeout_unit & 0x3) << 30; - buffer->input[2] = state->als_setting & 0xFF; - buffer->input[2] |= (state->level & 0xFF) << 16; - buffer->input[2] |= (state->timeout_value_ac & 0x3F) << 24; - buffer->input[2] |= (state->timeout_unit_ac & 0x3) << 30; - dell_smbios_send_request(4, 11); - ret = buffer->output[0]; - dell_smbios_release_buffer(); - - return dell_smbios_error(ret); + return ret; } static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) @@ -1345,7 +1281,6 @@ static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) static int kbd_set_token_bit(u8 bit) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; int ret; @@ -1356,19 +1291,14 @@ static int kbd_set_token_bit(u8 bit) if (!token) return -EINVAL; - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - buffer->input[1] = token->value; - dell_smbios_send_request(1, 0); - ret = buffer->output[0]; - dell_smbios_release_buffer(); + dell_set_arguments(token->location, token->value, 0, 0); + ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - return dell_smbios_error(ret); + return ret; } static int kbd_get_token_bit(u8 bit) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; int ret; int val; @@ -1380,15 +1310,12 @@ static int kbd_get_token_bit(u8 bit) if (!token) return -EINVAL; - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - dell_smbios_send_request(0, 0); - ret = buffer->output[0]; + dell_set_arguments(token->location, 0, 0, 0); + ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_STD); val = buffer->output[1]; - dell_smbios_release_buffer(); if (ret) - return dell_smbios_error(ret); + return ret; return (val == token->value); } @@ -2102,7 +2029,6 @@ static struct notifier_block dell_laptop_notifier = { int dell_micmute_led_set(int state) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; if (state == 0) @@ -2115,11 +2041,8 @@ int dell_micmute_led_set(int state) if (!token) return -ENODEV; - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - buffer->input[1] = token->value; - dell_smbios_send_request(1, 0); - dell_smbios_release_buffer(); + dell_set_arguments(token->location, token->value, 0, 0); + dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); return state; } @@ -2127,7 +2050,6 @@ EXPORT_SYMBOL_GPL(dell_micmute_led_set); static int __init dell_init(void) { - struct calling_interface_buffer *buffer; struct calling_interface_token *token; int max_intensity = 0; int ret; @@ -2151,6 +2073,11 @@ static int __init dell_init(void) if (ret) goto fail_platform_device2; + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + if (!buffer) + goto fail_buffer; + + ret = dell_setup_rfkill(); if (ret) { @@ -2175,12 +2102,10 @@ static int __init dell_init(void) token = dell_smbios_find_token(BRIGHTNESS_TOKEN); if (token) { - buffer = dell_smbios_get_buffer(); - buffer->input[0] = token->location; - dell_smbios_send_request(0, 2); - if (buffer->output[0] == 0) + dell_set_arguments(token->location, 0, 0, 0); + ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_AC); + if (ret) max_intensity = buffer->output[3]; - dell_smbios_release_buffer(); } if (max_intensity) { @@ -2214,6 +2139,8 @@ static int __init dell_init(void) fail_get_brightness: backlight_device_unregister(dell_backlight_device); fail_backlight: + kfree(buffer); +fail_buffer: dell_cleanup_rfkill(); fail_rfkill: platform_device_del(platform_device); @@ -2233,6 +2160,7 @@ static void __exit dell_exit(void) touchpad_led_exit(); kbd_led_exit(); backlight_device_unregister(dell_backlight_device); + kfree(buffer); dell_cleanup_rfkill(); if (platform_device) { platform_device_unregister(platform_device); diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c new file mode 100644 index 000000000000..89f65c4651a0 --- /dev/null +++ b/drivers/platform/x86/dell-smbios-smm.c @@ -0,0 +1,196 @@ +/* + * SMI methods for use with dell-smbios + * + * Copyright (c) Red Hat <mjg@redhat.com> + * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com> + * Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com> + * Copyright (c) 2017 Dell Inc. + * + * 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. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/dmi.h> +#include <linux/gfp.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include "../../firmware/dcdbas.h" +#include "dell-smbios.h" + +static int da_command_address; +static int da_command_code; +static struct calling_interface_buffer *buffer; +struct platform_device *platform_device; +static DEFINE_MUTEX(smm_mutex); + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static void __init parse_da_table(const struct dmi_header *dm) +{ + struct calling_interface_structure *table = + container_of(dm, struct calling_interface_structure, header); + + /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least + * 6 bytes of entry + */ + if (dm->length < 17) + return; + + da_command_address = table->cmdIOAddress; + da_command_code = table->cmdIOCode; +} + +static void __init find_cmd_address(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xda: /* Calling interface */ + parse_da_table(dm); + break; + } +} + +int dell_smbios_smm_call(struct calling_interface_buffer *input) +{ + struct smi_cmd command; + size_t size; + + size = sizeof(struct calling_interface_buffer); + command.magic = SMI_CMD_MAGIC; + command.command_address = da_command_address; + command.command_code = da_command_code; + command.ebx = virt_to_phys(buffer); + command.ecx = 0x42534931; + + mutex_lock(&smm_mutex); + memcpy(buffer, input, size); + dcdbas_smi_request(&command); + memcpy(input, buffer, size); + mutex_unlock(&smm_mutex); + return 0; +} + +/* When enabled this indicates that SMM won't work */ +static bool test_wsmt_enabled(void) +{ + struct calling_interface_token *wsmt; + + /* if token doesn't exist, SMM will work */ + wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); + if (!wsmt) + return false; + + /* If token exists, try to access over SMM but set a dummy return. + * - If WSMT disabled it will be overwritten by SMM + * - If WSMT enabled then dummy value will remain + */ + buffer->cmd_class = CLASS_TOKEN_READ; + buffer->cmd_select = SELECT_TOKEN_STD; + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; + dell_smbios_smm_call(buffer); + if (buffer->output[0] == 99) + return true; + + return false; +} + +static int __init dell_smbios_smm_init(void) +{ + int ret; + /* + * Allocate buffer below 4GB for SMI data--only 32-bit physical addr + * is passed to SMI handler. + */ + buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); + if (!buffer) + return -ENOMEM; + + dmi_walk(find_cmd_address, NULL); + + if (test_wsmt_enabled()) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; + } + + platform_device = platform_device_alloc("dell-smbios", 1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + ret = dell_smbios_register_device(&platform_device->dev, + &dell_smbios_smm_call); + if (ret) + goto fail_register; + + return 0; + +fail_register: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_wsmt: +fail_platform_device_alloc: + free_page((unsigned long)buffer); + return ret; +} + +static void __exit dell_smbios_smm_exit(void) +{ + if (platform_device) { + dell_smbios_unregister_device(&platform_device->dev); + platform_device_unregister(platform_device); + free_page((unsigned long)buffer); + } +} + +subsys_initcall(dell_smbios_smm_init); +module_exit(dell_smbios_smm_exit); + +MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); +MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_DESCRIPTION("Dell SMBIOS communications over SMI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c new file mode 100644 index 000000000000..0cab1f9c35af --- /dev/null +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -0,0 +1,272 @@ +/* + * WMI methods for use with dell-smbios + * + * Copyright (c) 2017 Dell Inc. + * + * 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. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/dmi.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/wmi.h> +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +static DEFINE_MUTEX(call_mutex); +static DEFINE_MUTEX(list_mutex); +static int wmi_supported; + +struct misc_bios_flags_structure { + struct dmi_header header; + u16 flags0; +} __packed; +#define FLAG_HAS_ACPI_WMI 0x02 + +#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + +struct wmi_smbios_priv { + struct dell_wmi_smbios_buffer *buf; + struct list_head list; + struct wmi_device *wdev; + struct device *child; + u32 req_buf_size; +}; +static LIST_HEAD(wmi_list); + +static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +{ + return list_first_entry_or_null(&wmi_list, + struct wmi_smbios_priv, + list); +} + +static int run_smbios_call(struct wmi_device *wdev) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct wmi_smbios_priv *priv; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + + priv = dev_get_drvdata(&wdev->dev); + input.length = priv->req_buf_size - sizeof(u64); + input.pointer = &priv->buf->std; + + dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", + priv->buf->std.cmd_class, priv->buf->std.cmd_select, + priv->buf->std.input[0], priv->buf->std.input[1], + priv->buf->std.input[2], priv->buf->std.input[3]); + + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type != ACPI_TYPE_BUFFER) { + dev_dbg(&wdev->dev, "received type: %d\n", obj->type); + if (obj->type == ACPI_TYPE_INTEGER) + dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", + obj->integer.value); + return -EIO; + } + memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); + dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", + priv->buf->std.output[0], priv->buf->std.output[1], + priv->buf->std.output[2], priv->buf->std.output[3]); + + return 0; +} + +int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) +{ + struct wmi_smbios_priv *priv; + size_t difference; + size_t size; + int ret; + + mutex_lock(&call_mutex); + priv = get_first_smbios_priv(); + if (!priv) { + ret = -ENODEV; + goto out_wmi_call; + } + + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +out_wmi_call: + mutex_unlock(&call_mutex); + + return ret; +} + +static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, + struct wmi_ioctl_buffer *arg) +{ + struct wmi_smbios_priv *priv; + int ret = 0; + + switch (cmd) { + case DELL_WMI_SMBIOS_CMD: + mutex_lock(&call_mutex); + priv = dev_get_drvdata(&wdev->dev); + if (!priv) { + ret = -ENODEV; + goto fail_smbios_cmd; + } + memcpy(priv->buf, arg, priv->req_buf_size); + if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { + dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", + priv->buf->std.cmd_class, + priv->buf->std.cmd_select, + priv->buf->std.input[0]); + ret = -EFAULT; + goto fail_smbios_cmd; + } + ret = run_smbios_call(priv->wdev); + if (ret) + goto fail_smbios_cmd; + memcpy(arg, priv->buf, priv->req_buf_size); +fail_smbios_cmd: + mutex_unlock(&call_mutex); + break; + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + +static int dell_smbios_wmi_probe(struct wmi_device *wdev) +{ + struct wmi_smbios_priv *priv; + int count; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* WMI buffer size will be either 4k or 32k depending on machine */ + if (!dell_wmi_get_size(&priv->req_buf_size)) + return -EPROBE_DEFER; + + /* add in the length object we will use internally with ioctl */ + priv->req_buf_size += sizeof(u64); + ret = set_required_buffer_size(wdev, priv->req_buf_size); + if (ret) + return ret; + + count = get_order(priv->req_buf_size); + priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); + if (!priv->buf) + return -ENOMEM; + + /* ID is used by dell-smbios to set priority of drivers */ + wdev->dev.id = 1; + ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call); + if (ret) + goto fail_register; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + return 0; + +fail_register: + free_pages((unsigned long)priv->buf, count); + return ret; +} + +static int dell_smbios_wmi_remove(struct wmi_device *wdev) +{ + struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); + int count; + + mutex_lock(&call_mutex); + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); + count = get_order(priv->req_buf_size); + free_pages((unsigned long)priv->buf, count); + mutex_unlock(&call_mutex); + return 0; +} + +static const struct wmi_device_id dell_smbios_wmi_id_table[] = { + { .guid_string = DELL_WMI_SMBIOS_GUID }, + { }, +}; + +static void __init parse_b1_table(const struct dmi_header *dm) +{ + struct misc_bios_flags_structure *flags = + container_of(dm, struct misc_bios_flags_structure, header); + + /* 4 bytes header, 8 bytes flags */ + if (dm->length < 12) + return; + if (dm->handle != 0xb100) + return; + if ((flags->flags0 & FLAG_HAS_ACPI_WMI)) + wmi_supported = 1; +} + +static void __init find_b1(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xb1: /* misc bios flags */ + parse_b1_table(dm); + break; + } +} + +static struct wmi_driver dell_smbios_wmi_driver = { + .driver = { + .name = "dell-smbios", + }, + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, + .filter_callback = dell_smbios_wmi_filter, +}; + +static int __init init_dell_smbios_wmi(void) +{ + dmi_walk(find_b1, NULL); + + if (!wmi_supported) + return -ENODEV; + + return wmi_driver_register(&dell_smbios_wmi_driver); +} + +static void __exit exit_dell_smbios_wmi(void) +{ + wmi_driver_unregister(&dell_smbios_wmi_driver); +} + +module_init(init_dell_smbios_wmi); +module_exit(exit_dell_smbios_wmi); + +MODULE_ALIAS("wmi:" DELL_WMI_SMBIOS_GUID); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_DESCRIPTION("Dell SMBIOS communications over WMI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 0a5723468bff..6a60db515bda 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -12,33 +12,119 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> #include <linux/module.h> +#include <linux/capability.h> #include <linux/dmi.h> #include <linux/err.h> -#include <linux/gfp.h> #include <linux/mutex.h> +#include <linux/platform_device.h> #include <linux/slab.h> -#include <linux/io.h> -#include "../../firmware/dcdbas.h" #include "dell-smbios.h" -struct calling_interface_structure { - struct dmi_header header; - u16 cmdIOAddress; - u8 cmdIOCode; - u32 supportedCmds; - struct calling_interface_token tokens[]; -} __packed; - -static struct calling_interface_buffer *buffer; -static DEFINE_MUTEX(buffer_mutex); - -static int da_command_address; -static int da_command_code; +static u32 da_supported_commands; static int da_num_tokens; +static struct platform_device *platform_device; static struct calling_interface_token *da_tokens; +static struct device_attribute *token_location_attrs; +static struct device_attribute *token_value_attrs; +static struct attribute **token_attrs; +static DEFINE_MUTEX(smbios_mutex); + +struct smbios_device { + struct list_head list; + struct device *device; + int (*call_fn)(struct calling_interface_buffer *); +}; + +struct smbios_call { + u32 need_capability; + int cmd_class; + int cmd_select; +}; + +/* calls that are whitelisted for given capabilities */ +static struct smbios_call call_whitelist[] = { + /* generally tokens are allowed, but may be further filtered or + * restricted by token blacklist or whitelist + */ + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, + /* used by userspace: fwupd */ + {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, + {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, +}; + +/* calls that are explicitly blacklisted */ +static struct smbios_call call_blacklist[] = { + {0x0000, 01, 07}, /* manufacturing use */ + {0x0000, 06, 05}, /* manufacturing use */ + {0x0000, 11, 03}, /* write once */ + {0x0000, 11, 07}, /* write once */ + {0x0000, 11, 11}, /* write once */ + {0x0000, 19, -1}, /* diagnostics */ + /* handled by kernel: dell-laptop */ + {0x0000, CLASS_INFO, SELECT_RFKILL}, + {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, +}; + +struct token_range { + u32 need_capability; + u16 min; + u16 max; +}; + +/* tokens that are whitelisted for given capabilities */ +static struct token_range token_whitelist[] = { + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, + /* can indicate to userspace that WMI is needed */ + {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} +}; + +/* tokens that are explicitly blacklisted */ +static struct token_range token_blacklist[] = { + {0x0000, 0x0058, 0x0059}, /* ME use */ + {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ + {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ + {0x0000, 0x0175, 0x0176}, /* write once */ + {0x0000, 0x0195, 0x0197}, /* diagnostics */ + {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ + {0x0000, 0x027D, 0x0284}, /* diagnostics */ + {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ + {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ + {0x0000, 0x0300, 0x0302}, /* manufacturing use */ + {0x0000, 0x0325, 0x0326}, /* manufacturing use */ + {0x0000, 0x0332, 0x0335}, /* fan control */ + {0x0000, 0x0350, 0x0350}, /* manufacturing use */ + {0x0000, 0x0363, 0x0363}, /* manufacturing use */ + {0x0000, 0x0368, 0x0368}, /* manufacturing use */ + {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ + {0x0000, 0x049E, 0x049F}, /* manufacturing use */ + {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ + {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ + {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ + {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ + {0x0000, 0xA000, 0xBFFF}, /* write only */ + {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ + /* handled by kernel: dell-laptop */ + {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, + {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, + {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, + {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, + {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, + {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, +}; + +static LIST_HEAD(smbios_device_list); int dell_smbios_error(int value) { @@ -55,42 +141,175 @@ int dell_smbios_error(int value) } EXPORT_SYMBOL_GPL(dell_smbios_error); -struct calling_interface_buffer *dell_smbios_get_buffer(void) +int dell_smbios_register_device(struct device *d, void *call_fn) { - mutex_lock(&buffer_mutex); - dell_smbios_clear_buffer(); - return buffer; + struct smbios_device *priv; + + priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); + if (!priv) + return -ENOMEM; + get_device(d); + priv->device = d; + priv->call_fn = call_fn; + mutex_lock(&smbios_mutex); + list_add_tail(&priv->list, &smbios_device_list); + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Added device: %s\n", d->driver->name); + return 0; } -EXPORT_SYMBOL_GPL(dell_smbios_get_buffer); +EXPORT_SYMBOL_GPL(dell_smbios_register_device); -void dell_smbios_clear_buffer(void) +void dell_smbios_unregister_device(struct device *d) { - memset(buffer, 0, sizeof(struct calling_interface_buffer)); + struct smbios_device *priv; + + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (priv->device == d) { + list_del(&priv->list); + put_device(d); + break; + } + } + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Remove device: %s\n", d->driver->name); } -EXPORT_SYMBOL_GPL(dell_smbios_clear_buffer); +EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); -void dell_smbios_release_buffer(void) +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer) { - mutex_unlock(&buffer_mutex); + u16 t = 0; + int i; + + /* can't make calls over 30 */ + if (buffer->cmd_class > 30) { + dev_dbg(d, "class too big: %u\n", buffer->cmd_class); + return -EINVAL; + } + + /* supported calls on the particular system */ + if (!(da_supported_commands & (1 << buffer->cmd_class))) { + dev_dbg(d, "invalid command, supported commands: 0x%8x\n", + da_supported_commands); + return -EINVAL; + } + + /* match against call blacklist */ + for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { + if (buffer->cmd_class != call_blacklist[i].cmd_class) + continue; + if (buffer->cmd_select != call_blacklist[i].cmd_select && + call_blacklist[i].cmd_select != -1) + continue; + dev_dbg(d, "blacklisted command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return -EINVAL; + } + + /* if a token call, find token ID */ + + if ((buffer->cmd_class == CLASS_TOKEN_READ || + buffer->cmd_class == CLASS_TOKEN_WRITE) && + buffer->cmd_select < 3) { + /* find the matching token ID */ + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].location != buffer->input[0]) + continue; + t = da_tokens[i].tokenID; + break; + } + + /* token call; but token didn't exist */ + if (!t) { + dev_dbg(d, "token at location %04x doesn't exist\n", + buffer->input[0]); + return -EINVAL; + } + + /* match against token blacklist */ + for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { + if (!token_blacklist[i].min || !token_blacklist[i].max) + continue; + if (t >= token_blacklist[i].min && + t <= token_blacklist[i].max) + return -EINVAL; + } + + /* match against token whitelist */ + for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { + if (!token_whitelist[i].min || !token_whitelist[i].max) + continue; + if (t < token_whitelist[i].min || + t > token_whitelist[i].max) + continue; + if (!token_whitelist[i].need_capability || + capable(token_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted token: %x\n", t); + return 0; + } + + } + } + /* match against call whitelist */ + for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { + if (buffer->cmd_class != call_whitelist[i].cmd_class) + continue; + if (buffer->cmd_select != call_whitelist[i].cmd_select) + continue; + if (!call_whitelist[i].need_capability || + capable(call_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted capable command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + dev_dbg(d, "missing capability %d for %u/%u\n", + call_whitelist[i].need_capability, + buffer->cmd_class, buffer->cmd_select); + + } + + /* not in a whitelist, only allow processes with capabilities */ + if (capable(CAP_SYS_RAWIO)) { + dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + + return -EACCES; } -EXPORT_SYMBOL_GPL(dell_smbios_release_buffer); +EXPORT_SYMBOL_GPL(dell_smbios_call_filter); -void dell_smbios_send_request(int class, int select) +int dell_smbios_call(struct calling_interface_buffer *buffer) { - struct smi_cmd command; + int (*call_fn)(struct calling_interface_buffer *) = NULL; + struct device *selected_dev = NULL; + struct smbios_device *priv; + int ret; - command.magic = SMI_CMD_MAGIC; - command.command_address = da_command_address; - command.command_code = da_command_code; - command.ebx = virt_to_phys(buffer); - command.ecx = 0x42534931; + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (!selected_dev || priv->device->id >= selected_dev->id) { + dev_dbg(priv->device, "Trying device ID: %d\n", + priv->device->id); + call_fn = priv->call_fn; + selected_dev = priv->device; + } + } + + if (!selected_dev) { + ret = -ENODEV; + pr_err("No dell-smbios drivers are loaded\n"); + goto out_smbios_call; + } - buffer->class = class; - buffer->select = select; + ret = call_fn(buffer); - dcdbas_smi_request(&command); +out_smbios_call: + mutex_unlock(&smbios_mutex); + return ret; } -EXPORT_SYMBOL_GPL(dell_smbios_send_request); +EXPORT_SYMBOL_GPL(dell_smbios_call); struct calling_interface_token *dell_smbios_find_token(int tokenid) { @@ -139,8 +358,7 @@ static void __init parse_da_table(const struct dmi_header *dm) if (dm->length < 17) return; - da_command_address = table->cmdIOAddress; - da_command_code = table->cmdIOCode; + da_supported_commands = table->supportedCmds; new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * sizeof(struct calling_interface_token), @@ -156,6 +374,27 @@ static void __init parse_da_table(const struct dmi_header *dm) da_num_tokens += tokens; } +static void zero_duplicates(struct device *dev) +{ + int i, j; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == 0) + continue; + for (j = i+1; j < da_num_tokens; j++) { + if (da_tokens[j].tokenID == 0) + continue; + if (da_tokens[i].tokenID == da_tokens[j].tokenID) { + dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", + da_tokens[j].tokenID, + da_tokens[j].location, + da_tokens[j].value); + da_tokens[j].tokenID = 0; + } + } + } +} + static void __init find_tokens(const struct dmi_header *dm, void *dummy) { switch (dm->type) { @@ -169,10 +408,160 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) } } +static int match_attribute(struct device *dev, + struct device_attribute *attr) +{ + int i; + + for (i = 0; i < da_num_tokens * 2; i++) { + if (!token_attrs[i]) + continue; + if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) + return i/2; + } + dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); + return -EINVAL; +} + +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); + return 0; +} + +static ssize_t value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); + return 0; +} + +static struct attribute_group smbios_attribute_group = { + .name = "tokens" +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-smbios", + }, +}; + +static int build_tokens_sysfs(struct platform_device *dev) +{ + char *location_name; + char *value_name; + size_t size; + int ret; + int i, j; + + /* (number of tokens + 1 for null terminated */ + size = sizeof(struct device_attribute) * (da_num_tokens + 1); + token_location_attrs = kzalloc(size, GFP_KERNEL); + if (!token_location_attrs) + return -ENOMEM; + token_value_attrs = kzalloc(size, GFP_KERNEL); + if (!token_value_attrs) + goto out_allocate_value; + + /* need to store both location and value + terminator*/ + size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); + token_attrs = kzalloc(size, GFP_KERNEL); + if (!token_attrs) + goto out_allocate_attrs; + + for (i = 0, j = 0; i < da_num_tokens; i++) { + /* skip empty */ + if (da_tokens[i].tokenID == 0) + continue; + /* add location */ + location_name = kasprintf(GFP_KERNEL, "%04x_location", + da_tokens[i].tokenID); + if (location_name == NULL) + goto out_unwind_strings; + sysfs_attr_init(&token_location_attrs[i].attr); + token_location_attrs[i].attr.name = location_name; + token_location_attrs[i].attr.mode = 0444; + token_location_attrs[i].show = location_show; + token_attrs[j++] = &token_location_attrs[i].attr; + + /* add value */ + value_name = kasprintf(GFP_KERNEL, "%04x_value", + da_tokens[i].tokenID); + if (value_name == NULL) + goto loop_fail_create_value; + sysfs_attr_init(&token_value_attrs[i].attr); + token_value_attrs[i].attr.name = value_name; + token_value_attrs[i].attr.mode = 0444; + token_value_attrs[i].show = value_show; + token_attrs[j++] = &token_value_attrs[i].attr; + continue; + +loop_fail_create_value: + kfree(value_name); + goto out_unwind_strings; + } + smbios_attribute_group.attrs = token_attrs; + + ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); + if (ret) + goto out_unwind_strings; + return 0; + +out_unwind_strings: + for (i = i-1; i > 0; i--) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); +out_allocate_attrs: + kfree(token_value_attrs); +out_allocate_value: + kfree(token_location_attrs); + + return -ENOMEM; +} + +static void free_group(struct platform_device *pdev) +{ + int i; + + sysfs_remove_group(&pdev->dev.kobj, + &smbios_attribute_group); + for (i = 0; i < da_num_tokens; i++) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); + kfree(token_value_attrs); + kfree(token_location_attrs); +} + static int __init dell_smbios_init(void) { + const struct dmi_device *valid; int ret; + valid = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL); + if (!valid) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + dmi_walk(find_tokens, NULL); if (!da_tokens) { @@ -180,27 +569,52 @@ static int __init dell_smbios_init(void) return -ENODEV; } - /* - * Allocate buffer below 4GB for SMI data--only 32-bit physical addr - * is passed to SMI handler. - */ - buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); - if (!buffer) { + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + + platform_device = platform_device_alloc("dell-smbios", 0); + if (!platform_device) { ret = -ENOMEM; - goto fail_buffer; + goto fail_platform_device_alloc; } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + /* duplicate tokens will cause problems building sysfs files */ + zero_duplicates(&platform_device->dev); + + ret = build_tokens_sysfs(platform_device); + if (ret) + goto fail_create_group; return 0; -fail_buffer: +fail_create_group: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_platform_device_alloc: + platform_driver_unregister(&platform_driver); + +fail_platform_driver: kfree(da_tokens); return ret; } static void __exit dell_smbios_exit(void) { + mutex_lock(&smbios_mutex); + if (platform_device) { + free_group(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } kfree(da_tokens); - free_page((unsigned long)buffer); + mutex_unlock(&smbios_mutex); } subsys_initcall(dell_smbios_init); diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index 45cbc2292cd3..138d478d9adc 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -16,17 +16,29 @@ #ifndef _DELL_SMBIOS_H_ #define _DELL_SMBIOS_H_ -struct notifier_block; +#include <linux/device.h> +#include <uapi/linux/wmi.h> -/* This structure will be modified by the firmware when we enter - * system management mode, hence the volatiles */ +/* Classes and selects used only in kernel drivers */ +#define CLASS_KBD_BACKLIGHT 4 +#define SELECT_KBD_BACKLIGHT 11 -struct calling_interface_buffer { - u16 class; - u16 select; - volatile u32 input[4]; - volatile u32 output[4]; -} __packed; +/* Tokens used in kernel drivers, any of these + * should be filtered from userspace access + */ +#define BRIGHTNESS_TOKEN 0x007d +#define KBD_LED_AC_TOKEN 0x0451 +#define KBD_LED_OFF_TOKEN 0x01E1 +#define KBD_LED_ON_TOKEN 0x01E2 +#define KBD_LED_AUTO_TOKEN 0x01E3 +#define KBD_LED_AUTO_25_TOKEN 0x02EA +#define KBD_LED_AUTO_50_TOKEN 0x02EB +#define KBD_LED_AUTO_75_TOKEN 0x02EC +#define KBD_LED_AUTO_100_TOKEN 0x02F6 +#define GLOBAL_MIC_MUTE_ENABLE 0x0364 +#define GLOBAL_MIC_MUTE_DISABLE 0x0365 + +struct notifier_block; struct calling_interface_token { u16 tokenID; @@ -37,12 +49,21 @@ struct calling_interface_token { }; }; -int dell_smbios_error(int value); +struct calling_interface_structure { + struct dmi_header header; + u16 cmdIOAddress; + u8 cmdIOCode; + u32 supportedCmds; + struct calling_interface_token tokens[]; +} __packed; -struct calling_interface_buffer *dell_smbios_get_buffer(void); -void dell_smbios_clear_buffer(void); -void dell_smbios_release_buffer(void); -void dell_smbios_send_request(int class, int select); +int dell_smbios_register_device(struct device *d, void *call_fn); +void dell_smbios_unregister_device(struct device *d); + +int dell_smbios_error(int value); +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer); +int dell_smbios_call(struct calling_interface_buffer *buffer); struct calling_interface_token *dell_smbios_find_token(int tokenid); diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c index 37e646034ef8..1d87237bc731 100644 --- a/drivers/platform/x86/dell-smo8800.c +++ b/drivers/platform/x86/dell-smo8800.c @@ -90,7 +90,7 @@ static ssize_t smo8800_misc_read(struct file *file, char __user *buf, struct smo8800_device, miscdev); u32 data = 0; - unsigned char byte_data = 0; + unsigned char byte_data; ssize_t retval = 1; if (count < 1) @@ -103,7 +103,6 @@ static ssize_t smo8800_misc_read(struct file *file, char __user *buf, if (retval) return retval; - byte_data = 1; retval = 1; if (data < 255) diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c new file mode 100644 index 000000000000..4dfef1f53481 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -0,0 +1,191 @@ +/* + * Dell WMI descriptor driver + * + * Copyright (C) 2017 Dell Inc. All Rights Reserved. + * + * 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 in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/wmi.h> +#include "dell-wmi-descriptor.h" + +#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" + +struct descriptor_priv { + struct list_head list; + u32 interface_version; + u32 size; +}; +static int descriptor_valid = -EPROBE_DEFER; +static LIST_HEAD(wmi_list); +static DEFINE_MUTEX(list_mutex); + +int dell_wmi_get_descriptor_valid(void) +{ + if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) + return -ENODEV; + + return descriptor_valid; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); + +bool dell_wmi_get_interface_version(u32 *version) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *version = priv->interface_version; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); + +bool dell_wmi_get_size(u32 *size) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *size = priv->size; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_size); + +/* + * Descriptor buffer is 128 byte long and contains: + * + * Name Offset Length Value + * Vendor Signature 0 4 "DELL" + * Object Signature 4 4 " WMI" + * WMI Interface Version 8 4 <version> + * WMI buffer length 12 4 <length> + */ +static int dell_wmi_descriptor_probe(struct wmi_device *wdev) +{ + union acpi_object *obj = NULL; + struct descriptor_priv *priv; + u32 *buffer; + int ret; + + obj = wmidev_block_query(wdev, 0); + if (!obj) { + dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); + ret = -EIO; + goto out; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + /* Although it's not technically a failure, this would lead to + * unexpected behavior + */ + if (obj->buffer.length != 128) { + dev_err(&wdev->dev, + "Dell descriptor buffer has unexpected length (%d)\n", + obj->buffer.length); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + buffer = (u32 *)obj->buffer.pointer; + + if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", + buffer); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + descriptor_valid = 0; + + if (buffer[2] != 0 && buffer[2] != 1) + dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", + (unsigned long) buffer[2]); + + priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), + GFP_KERNEL); + + if (!priv) { + ret = -ENOMEM; + goto out; + } + + priv->interface_version = buffer[2]; + priv->size = buffer[3]; + ret = 0; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu and buffer size %lu\n", + (unsigned long) priv->interface_version, + (unsigned long) priv->size); + +out: + kfree(obj); + return ret; +} + +static int dell_wmi_descriptor_remove(struct wmi_device *wdev) +{ + struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); + + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + return 0; +} + +static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { + { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_descriptor_driver = { + .driver = { + .name = "dell-wmi-descriptor", + }, + .probe = dell_wmi_descriptor_probe, + .remove = dell_wmi_descriptor_remove, + .id_table = dell_wmi_descriptor_id_table, +}; + +module_wmi_driver(dell_wmi_descriptor_driver); + +MODULE_ALIAS("wmi:" DELL_WMI_DESCRIPTOR_GUID); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_DESCRIPTION("Dell WMI descriptor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell-wmi-descriptor.h new file mode 100644 index 000000000000..1e8cb96ffd78 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-descriptor.h @@ -0,0 +1,27 @@ +/* + * Dell WMI descriptor driver + * + * Copyright (c) 2017 Dell Inc. + * + * 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. + */ + +#ifndef _DELL_WMI_DESCRIPTOR_H_ +#define _DELL_WMI_DESCRIPTOR_H_ + +#include <linux/wmi.h> + +/* possible return values: + * -ENODEV: Descriptor GUID missing from WMI bus + * -EPROBE_DEFER: probing for dell-wmi-descriptor not yet run + * 0: valid descriptor, successfully probed + * < 0: invalid descriptor, don't probe dependent devices + */ +int dell_wmi_get_descriptor_valid(void); + +bool dell_wmi_get_interface_version(u32 *version); +bool dell_wmi_get_size(u32 *size); + +#endif diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 28d9f8696081..39d2f4518483 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -39,6 +39,7 @@ #include <linux/wmi.h> #include <acpi/video.h> #include "dell-smbios.h" +#include "dell-wmi-descriptor.h" MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); @@ -46,12 +47,10 @@ MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); MODULE_LICENSE("GPL"); #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" -#define DELL_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" static bool wmi_requires_smbios_request; MODULE_ALIAS("wmi:"DELL_EVENT_GUID); -MODULE_ALIAS("wmi:"DELL_DESCRIPTOR_GUID); struct dell_wmi_priv { struct input_dev *input_dev; @@ -619,78 +618,6 @@ static void dell_wmi_input_destroy(struct wmi_device *wdev) } /* - * Descriptor buffer is 128 byte long and contains: - * - * Name Offset Length Value - * Vendor Signature 0 4 "DELL" - * Object Signature 4 4 " WMI" - * WMI Interface Version 8 4 <version> - * WMI buffer length 12 4 4096 - */ -static int dell_wmi_check_descriptor_buffer(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - union acpi_object *obj = NULL; - struct wmi_device *desc_dev; - u32 *buffer; - int ret; - - desc_dev = wmidev_get_other_guid(wdev, DELL_DESCRIPTOR_GUID); - if (!desc_dev) { - dev_err(&wdev->dev, "Dell WMI descriptor does not exist\n"); - return -ENODEV; - } - - obj = wmidev_block_query(desc_dev, 0); - if (!obj) { - dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); - ret = -EIO; - goto out; - } - - if (obj->type != ACPI_TYPE_BUFFER) { - dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); - ret = -EINVAL; - goto out; - } - - if (obj->buffer.length != 128) { - dev_err(&wdev->dev, - "Dell descriptor buffer has invalid length (%d)\n", - obj->buffer.length); - if (obj->buffer.length < 16) { - ret = -EINVAL; - goto out; - } - } - - buffer = (u32 *)obj->buffer.pointer; - - if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720) - dev_warn(&wdev->dev, "Dell descriptor buffer has invalid signature (%*ph)\n", - 8, buffer); - - if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%d)\n", - buffer[2]); - - if (buffer[3] != 4096) - dev_warn(&wdev->dev, "Dell descriptor buffer has invalid buffer length (%d)\n", - buffer[3]); - - priv->interface_version = buffer[2]; - ret = 0; - - dev_info(&wdev->dev, "Detected Dell WMI interface version %u\n", - priv->interface_version); - -out: - kfree(obj); - put_device(&desc_dev->dev); - return ret; -} - -/* * According to Dell SMBIOS documentation: * * 17 3 Application Program Registration @@ -711,13 +638,16 @@ static int dell_wmi_events_set_enabled(bool enable) struct calling_interface_buffer *buffer; int ret; - buffer = dell_smbios_get_buffer(); + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + buffer->cmd_class = CLASS_INFO; + buffer->cmd_select = SELECT_APP_REGISTRATION; buffer->input[0] = 0x10000; buffer->input[1] = 0x51534554; buffer->input[3] = enable; - dell_smbios_send_request(17, 3); - ret = buffer->output[0]; - dell_smbios_release_buffer(); + ret = dell_smbios_call(buffer); + if (ret == 0) + ret = buffer->output[0]; + kfree(buffer); return dell_smbios_error(ret); } @@ -725,7 +655,11 @@ static int dell_wmi_events_set_enabled(bool enable) static int dell_wmi_probe(struct wmi_device *wdev) { struct dell_wmi_priv *priv; - int err; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; priv = devm_kzalloc( &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); @@ -733,9 +667,8 @@ static int dell_wmi_probe(struct wmi_device *wdev) return -ENOMEM; dev_set_drvdata(&wdev->dev, priv); - err = dell_wmi_check_descriptor_buffer(wdev); - if (err) - return err; + if (!dell_wmi_get_interface_version(&priv->interface_version)) + return -EPROBE_DEFER; return dell_wmi_input_setup(wdev); } diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 56a8195096a2..2cfbd3fa5136 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -691,6 +691,7 @@ static enum led_brightness eco_led_get(struct led_classdev *cdev) static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) { + struct fujitsu_laptop *priv = acpi_driver_data(device); struct led_classdev *led; int result; @@ -724,12 +725,15 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) } /* - * BTNI bit 24 seems to indicate the presence of a radio toggle - * button in place of a slide switch, and all such machines appear - * to also have an RF LED. Therefore use bit 24 as an indicator - * that an RF LED is present. + * Some Fujitsu laptops have a radio toggle button in place of a slide + * switch and all such machines appear to also have an RF LED. Based on + * comparing DSDT tables of four Fujitsu Lifebook models (E744, E751, + * S7110, S8420; the first one has a radio toggle button, the other + * three have slide switches), bit 17 of flags_supported (the value + * returned by method S000 of ACPI device FUJ02E3) seems to indicate + * whether given model has a radio toggle button. */ - if (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { + if (priv->flags_supported & BIT(17)) { led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index b4ed3dc983d5..b4224389febe 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -297,7 +297,7 @@ static int hp_wmi_hw_state(int mask) if (state < 0) return state; - return state & 0x1; + return !!(state & mask); } static int __init hp_wmi_bios_2008_later(void) diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 493d8910a74e..7b12abe86b94 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -240,6 +240,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted), AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left), AXIS_DMI_MATCH("HPB440G3", "HP ProBook 440 G3", x_inverted_usd), + AXIS_DMI_MATCH("HPB440G4", "HP ProBook 440 G4", x_inverted), AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index fe98d4ac0df3..53ab4e0f8962 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1166,6 +1166,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 910-13IKB"), }, }, + { + .ident = "Lenovo YOGA 920-13IKB", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 920-13IKB"), + }, + }, {} }; diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index e34fd70b67af..f470279c4c10 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -226,6 +226,24 @@ wakeup: return; } + /* + * Needed for suspend to work on some platforms that don't expose + * the 5-button array, but still send notifies with power button + * event code to this device object on power button actions. + * + * Report the power button press; catch and ignore the button release. + */ + if (!priv->array) { + if (event == 0xce) { + input_report_key(priv->input_dev, KEY_POWER, 1); + input_sync(priv->input_dev); + return; + } + + if (event == 0xcf) + return; + } + /* 0xC0 is for HID events, other values are for 5 button array */ if (event != 0xc0) { if (!priv->array || diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c new file mode 100644 index 000000000000..c2257bd06f18 --- /dev/null +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -0,0 +1,98 @@ +/* + * WMI Thunderbolt driver + * + * Copyright (C) 2017 Dell Inc. All Rights Reserved. + * + * 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 in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/wmi.h> + +#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" + +static ssize_t force_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_buffer input; + acpi_status status; + u8 mode; + + input.length = sizeof(u8); + input.pointer = &mode; + mode = hex_to_bin(buf[0]); + if (mode == 0 || mode == 1) { + status = wmi_evaluate_method(INTEL_WMI_THUNDERBOLT_GUID, 0, 1, + &input, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + } else { + return -EINVAL; + } + return count; +} + +static DEVICE_ATTR_WO(force_power); + +static struct attribute *tbt_attrs[] = { + &dev_attr_force_power.attr, + NULL +}; + +static const struct attribute_group tbt_attribute_group = { + .attrs = tbt_attrs, +}; + +static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev) +{ + int ret; + + ret = sysfs_create_group(&wdev->dev.kobj, &tbt_attribute_group); + kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); + return ret; +} + +static int intel_wmi_thunderbolt_remove(struct wmi_device *wdev) +{ + sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group); + kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); + return 0; +} + +static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = { + { .guid_string = INTEL_WMI_THUNDERBOLT_GUID }, + { }, +}; + +static struct wmi_driver intel_wmi_thunderbolt_driver = { + .driver = { + .name = "intel-wmi-thunderbolt", + }, + .probe = intel_wmi_thunderbolt_probe, + .remove = intel_wmi_thunderbolt_remove, + .id_table = intel_wmi_thunderbolt_id_table, +}; + +module_wmi_driver(intel_wmi_thunderbolt_driver); + +MODULE_ALIAS("wmi:" INTEL_WMI_THUNDERBOLT_GUID); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c index da706e2c4232..380ef7ec094f 100644 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -24,6 +24,7 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/module.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #define EXPECTED_PTYPE 4 @@ -34,6 +35,42 @@ struct cht_int33fe_data { struct i2c_client *pi3usb30532; }; +/* + * Grrr I severly dislike buggy BIOS-es. At least one BIOS enumerates + * the max17047 both through the INT33FE ACPI device (it is right there + * in the resources table) as well as through a separate MAX17047 device. + * + * These helpers are used to work around this by checking if an i2c-client + * for the max17047 has already been registered. + */ +static int cht_int33fe_check_for_max17047(struct device *dev, void *data) +{ + struct i2c_client **max17047 = data; + struct acpi_device *adev; + const char *hid; + + adev = ACPI_COMPANION(dev); + if (!adev) + return 0; + + hid = acpi_device_hid(adev); + + /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ + if (strcmp(hid, "MAX17047")) + return 0; + + *max17047 = to_i2c_client(dev); + return 1; +} + +static struct i2c_client *cht_int33fe_find_max17047(void) +{ + struct i2c_client *max17047 = NULL; + + i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); + return max17047; +} + static const char * const max17047_suppliers[] = { "bq24190-charger" }; static const struct property_entry max17047_props[] = { @@ -41,14 +78,25 @@ static const struct property_entry max17047_props[] = { { } }; +static const struct property_entry fusb302_props[] = { + PROPERTY_ENTRY_STRING("fcs,extcon-name", "cht_wcove_pwrsrc"), + PROPERTY_ENTRY_U32("fcs,max-sink-microvolt", 12000000), + PROPERTY_ENTRY_U32("fcs,max-sink-microamp", 3000000), + PROPERTY_ENTRY_U32("fcs,max-sink-microwatt", 36000000), + { } +}; + static int cht_int33fe_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct i2c_board_info board_info; struct cht_int33fe_data *data; + struct i2c_client *max17047; + struct regulator *regulator; unsigned long long ptyp; acpi_status status; int fusb302_irq; + int ret; status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); if (ACPI_FAILURE(status)) { @@ -63,6 +111,34 @@ static int cht_int33fe_probe(struct i2c_client *client) 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; + } + + /* + * 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: + * 1) The bq24292i allows charging with up to 12V, setting the fusb302's + * max-snk voltage to 12V with another charger-IC is not good. + * 2) For the fusb302 driver to get the bq24292i vbus regulator, the + * regulator-map, which is part of the bq24292i regulator_init_data, + * must be registered before the fusb302 is instantiated, otherwise + * it will end up with a dummy-regulator. + * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data + * which is defined in i2c-cht-wc.c from where the bq24292i i2c-client + * gets instantiated. We use regulator_get_optional here so that we + * don't end up getting a dummy-regulator ourselves. + */ + regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + return (ret == -ENODEV) ? -EPROBE_DEFER : ret; + } + regulator_put(regulator); + /* The FUSB302 uses the irq at index 1 and is the only irq user */ fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); if (fusb302_irq < 0) { @@ -75,16 +151,31 @@ static int cht_int33fe_probe(struct i2c_client *client) if (!data) return -ENOMEM; - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); - board_info.properties = max17047_props; - - data->max17047 = i2c_acpi_new_device(dev, 1, &board_info); - if (!data->max17047) - return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */ + /* Work around BIOS bug, see comment on cht_int33fe_find_max17047 */ + max17047 = cht_int33fe_find_max17047(); + if (max17047) { + /* Pre-existing i2c-client for the max17047, add device-props */ + ret = device_add_properties(&max17047->dev, max17047_props); + if (ret) + return ret; + /* And re-probe to get the new device-props applied. */ + ret = device_reprobe(&max17047->dev); + if (ret) + dev_warn(dev, "Reprobing max17047 error: %d\n", ret); + } else { + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); + board_info.dev_name = "max17047"; + board_info.properties = max17047_props; + data->max17047 = i2c_acpi_new_device(dev, 1, &board_info); + if (!data->max17047) + return -EPROBE_DEFER; /* Wait for i2c-adapter to load */ + } memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "fusb302", I2C_NAME_SIZE); + strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); + board_info.dev_name = "fusb302"; + board_info.properties = fusb302_props; board_info.irq = fusb302_irq; data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); @@ -92,6 +183,7 @@ static int cht_int33fe_probe(struct i2c_client *client) goto out_unregister_max17047; memset(&board_info, 0, sizeof(board_info)); + board_info.dev_name = "pi3usb30532"; strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); @@ -106,7 +198,8 @@ out_unregister_fusb302: i2c_unregister_device(data->fusb302); out_unregister_max17047: - i2c_unregister_device(data->max17047); + if (data->max17047) + i2c_unregister_device(data->max17047); return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */ } @@ -117,7 +210,8 @@ static int cht_int33fe_remove(struct i2c_client *i2c) i2c_unregister_device(data->pi3usb30532); i2c_unregister_device(data->fusb302); - i2c_unregister_device(data->max17047); + if (data->max17047) + i2c_unregister_device(data->max17047); return 0; } diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 58dcee562d64..a0c95853fd3f 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -10,10 +10,6 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - * * The full GNU General Public License is included in this distribution in * the file called "COPYING". * @@ -259,8 +255,6 @@ static const int IPS_SAMPLE_WINDOW = 5000; /* 5s moving window of samples */ /* Per-SKU limits */ struct ips_mcp_limits { - int cpu_family; - int cpu_model; /* includes extended model... */ int mcp_power_limit; /* mW units */ int core_power_limit; int mch_power_limit; @@ -295,11 +289,14 @@ static struct ips_mcp_limits ips_ulv_limits = { }; struct ips_driver { - struct pci_dev *dev; - void *regmap; + struct device *dev; + void __iomem *regmap; + int irq; + struct task_struct *monitor; struct task_struct *adjust; struct dentry *debug_root; + struct timer_list timer; /* Average CPU core temps (all averages in .01 degrees C for precision) */ u16 ctv1_avg_temp; @@ -594,7 +591,7 @@ static void ips_disable_gpu_turbo(struct ips_driver *ips) return; if (!ips->gpu_turbo_disable()) - dev_err(&ips->dev->dev, "failed to disable graphics turbo\n"); + dev_err(ips->dev, "failed to disable graphics turbo\n"); else ips->__gpu_turbo_on = false; } @@ -649,8 +646,7 @@ static bool cpu_exceeded(struct ips_driver *ips, int cpu) spin_unlock_irqrestore(&ips->turbo_status_lock, flags); if (ret) - dev_info(&ips->dev->dev, - "CPU power or thermal limit exceeded\n"); + dev_info(ips->dev, "CPU power or thermal limit exceeded\n"); return ret; } @@ -769,7 +765,7 @@ static int ips_adjust(void *data) struct ips_driver *ips = data; unsigned long flags; - dev_dbg(&ips->dev->dev, "starting ips-adjust thread\n"); + dev_dbg(ips->dev, "starting ips-adjust thread\n"); /* * Adjust CPU and GPU clamps every 5s if needed. Doing it more @@ -816,7 +812,7 @@ sleep: schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD)); } while (!kthread_should_stop()); - dev_dbg(&ips->dev->dev, "ips-adjust thread stopped\n"); + dev_dbg(ips->dev, "ips-adjust thread stopped\n"); return 0; } @@ -942,9 +938,10 @@ static u32 calc_avg_power(struct ips_driver *ips, u32 *array) return avg; } -static void monitor_timeout(unsigned long arg) +static void monitor_timeout(struct timer_list *t) { - wake_up_process((struct task_struct *)arg); + struct ips_driver *ips = from_timer(ips, t, timer); + wake_up_process(ips->monitor); } /** @@ -961,7 +958,6 @@ static void monitor_timeout(unsigned long arg) static int ips_monitor(void *data) { struct ips_driver *ips = data; - struct timer_list timer; unsigned long seqno_timestamp, expire, last_msecs, last_sample_period; int i; u32 *cpu_samples, *mchp_samples, old_cpu_power; @@ -976,7 +972,7 @@ static int ips_monitor(void *data) mchp_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL); if (!mcp_samples || !ctv1_samples || !ctv2_samples || !mch_samples || !cpu_samples || !mchp_samples) { - dev_err(&ips->dev->dev, + dev_err(ips->dev, "failed to allocate sample array, ips disabled\n"); kfree(mcp_samples); kfree(ctv1_samples); @@ -1049,8 +1045,7 @@ static int ips_monitor(void *data) schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); last_sample_period = IPS_SAMPLE_PERIOD; - setup_deferrable_timer_on_stack(&timer, monitor_timeout, - (unsigned long)current); + timer_setup(&ips->timer, monitor_timeout, TIMER_DEFERRABLE); do { u32 cpu_val, mch_val; u16 val; @@ -1097,7 +1092,8 @@ static int ips_monitor(void *data) ITV_ME_SEQNO_SHIFT; if (cur_seqno == last_seqno && time_after(jiffies, seqno_timestamp + HZ)) { - dev_warn(&ips->dev->dev, "ME failed to update for more than 1s, likely hung\n"); + dev_warn(ips->dev, + "ME failed to update for more than 1s, likely hung\n"); } else { seqno_timestamp = get_jiffies_64(); last_seqno = cur_seqno; @@ -1107,7 +1103,7 @@ static int ips_monitor(void *data) expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); __set_current_state(TASK_INTERRUPTIBLE); - mod_timer(&timer, expire); + mod_timer(&ips->timer, expire); schedule(); /* Calculate actual sample period for power averaging */ @@ -1116,10 +1112,9 @@ static int ips_monitor(void *data) last_sample_period = 1; } while (!kthread_should_stop()); - del_timer_sync(&timer); - destroy_timer_on_stack(&timer); + del_timer_sync(&ips->timer); - dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n"); + dev_dbg(ips->dev, "ips-monitor thread stopped\n"); return 0; } @@ -1128,17 +1123,17 @@ static int ips_monitor(void *data) #define THM_DUMPW(reg) \ { \ u16 val = thm_readw(reg); \ - dev_dbg(&ips->dev->dev, #reg ": 0x%04x\n", val); \ + dev_dbg(ips->dev, #reg ": 0x%04x\n", val); \ } #define THM_DUMPL(reg) \ { \ u32 val = thm_readl(reg); \ - dev_dbg(&ips->dev->dev, #reg ": 0x%08x\n", val); \ + dev_dbg(ips->dev, #reg ": 0x%08x\n", val); \ } #define THM_DUMPQ(reg) \ { \ u64 val = thm_readq(reg); \ - dev_dbg(&ips->dev->dev, #reg ": 0x%016x\n", val); \ + dev_dbg(ips->dev, #reg ": 0x%016x\n", val); \ } static void dump_thermal_info(struct ips_driver *ips) @@ -1146,7 +1141,7 @@ static void dump_thermal_info(struct ips_driver *ips) u16 ptl; ptl = thm_readw(THM_PTL); - dev_dbg(&ips->dev->dev, "Processor temp limit: %d\n", ptl); + dev_dbg(ips->dev, "Processor temp limit: %d\n", ptl); THM_DUMPW(THM_CTA); THM_DUMPW(THM_TRC); @@ -1175,8 +1170,8 @@ static irqreturn_t ips_irq_handler(int irq, void *arg) if (!tses && !tes) return IRQ_NONE; - dev_info(&ips->dev->dev, "TSES: 0x%02x\n", tses); - dev_info(&ips->dev->dev, "TES: 0x%02x\n", tes); + dev_info(ips->dev, "TSES: 0x%02x\n", tses); + dev_info(ips->dev, "TES: 0x%02x\n", tes); /* STS update from EC? */ if (tes & 1) { @@ -1214,8 +1209,8 @@ static irqreturn_t ips_irq_handler(int irq, void *arg) /* Thermal trip */ if (tses) { - dev_warn(&ips->dev->dev, - "thermal trip occurred, tses: 0x%04x\n", tses); + dev_warn(ips->dev, "thermal trip occurred, tses: 0x%04x\n", + tses); thm_writeb(THM_TSES, tses); } @@ -1330,8 +1325,7 @@ static void ips_debugfs_init(struct ips_driver *ips) ips->debug_root = debugfs_create_dir("ips", NULL); if (!ips->debug_root) { - dev_err(&ips->dev->dev, - "failed to create debugfs entries: %ld\n", + dev_err(ips->dev, "failed to create debugfs entries: %ld\n", PTR_ERR(ips->debug_root)); return; } @@ -1345,8 +1339,7 @@ static void ips_debugfs_init(struct ips_driver *ips) ips->debug_root, node, &ips_debugfs_ops); if (!ent) { - dev_err(&ips->dev->dev, - "failed to create debug file: %ld\n", + dev_err(ips->dev, "failed to create debug file: %ld\n", PTR_ERR(ent)); goto err_cleanup; } @@ -1373,8 +1366,8 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) u16 tdp; if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) { - dev_info(&ips->dev->dev, "Non-IPS CPU detected.\n"); - goto out; + dev_info(ips->dev, "Non-IPS CPU detected.\n"); + return NULL; } rdmsrl(IA32_MISC_ENABLE, misc_en); @@ -1395,8 +1388,8 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) else if (strstr(boot_cpu_data.x86_model_id, "CPU U")) limits = &ips_ulv_limits; else { - dev_info(&ips->dev->dev, "No CPUID match found.\n"); - goto out; + dev_info(ips->dev, "No CPUID match found.\n"); + return NULL; } rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power); @@ -1404,12 +1397,12 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) /* Sanity check TDP against CPU */ if (limits->core_power_limit != (tdp / 8) * 1000) { - dev_info(&ips->dev->dev, "CPU TDP doesn't match expected value (found %d, expected %d)\n", + dev_info(ips->dev, + "CPU TDP doesn't match expected value (found %d, expected %d)\n", tdp / 8, limits->core_power_limit / 1000); limits->core_power_limit = (tdp / 8) * 1000; } -out: return limits; } @@ -1459,7 +1452,7 @@ ips_gpu_turbo_enabled(struct ips_driver *ips) { if (!ips->gpu_busy && late_i915_load) { if (ips_get_i915_syms(ips)) { - dev_info(&ips->dev->dev, + dev_info(ips->dev, "i915 driver attached, reenabling gpu turbo\n"); ips->gpu_turbo_enabled = !(thm_readl(THM_HTS) & HTS_GTD_DIS); } @@ -1480,8 +1473,7 @@ ips_link_to_i915_driver(void) EXPORT_SYMBOL_GPL(ips_link_to_i915_driver); static const struct pci_device_id ips_id_table[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, - PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), }, { 0, } }; @@ -1517,62 +1509,45 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) if (dmi_check_system(ips_blacklist)) return -ENODEV; - ips = kzalloc(sizeof(struct ips_driver), GFP_KERNEL); + ips = devm_kzalloc(&dev->dev, sizeof(*ips), GFP_KERNEL); if (!ips) return -ENOMEM; - pci_set_drvdata(dev, ips); - ips->dev = dev; + spin_lock_init(&ips->turbo_status_lock); + ips->dev = &dev->dev; ips->limits = ips_detect_cpu(ips); if (!ips->limits) { dev_info(&dev->dev, "IPS not supported on this CPU\n"); - ret = -ENXIO; - goto error_free; + return -ENXIO; } - spin_lock_init(&ips->turbo_status_lock); - - ret = pci_enable_device(dev); + ret = pcim_enable_device(dev); if (ret) { dev_err(&dev->dev, "can't enable PCI device, aborting\n"); - goto error_free; + return ret; } - if (!pci_resource_start(dev, 0)) { - dev_err(&dev->dev, "TBAR not assigned, aborting\n"); - ret = -ENXIO; - goto error_free; - } - - ret = pci_request_regions(dev, "ips thermal sensor"); + ret = pcim_iomap_regions(dev, 1 << 0, pci_name(dev)); if (ret) { - dev_err(&dev->dev, "thermal resource busy, aborting\n"); - goto error_free; - } - - - ips->regmap = ioremap(pci_resource_start(dev, 0), - pci_resource_len(dev, 0)); - if (!ips->regmap) { dev_err(&dev->dev, "failed to map thermal regs, aborting\n"); - ret = -EBUSY; - goto error_release; + return ret; } + ips->regmap = pcim_iomap_table(dev)[0]; + + pci_set_drvdata(dev, ips); tse = thm_readb(THM_TSE); if (tse != TSE_EN) { dev_err(&dev->dev, "thermal device not enabled (0x%02x), aborting\n", tse); - ret = -ENXIO; - goto error_unmap; + return -ENXIO; } trc = thm_readw(THM_TRC); trc_required_mask = TRC_CORE1_EN | TRC_CORE_PWR | TRC_MCH_EN; if ((trc & trc_required_mask) != trc_required_mask) { dev_err(&dev->dev, "thermal reporting for required devices not enabled, aborting\n"); - ret = -ENXIO; - goto error_unmap; + return -ENXIO; } if (trc & TRC_CORE2_EN) @@ -1602,20 +1577,23 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) rdmsrl(PLATFORM_INFO, platform_info); if (!(platform_info & PLATFORM_TDP)) { dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n"); - ret = -ENODEV; - goto error_unmap; + return -ENODEV; } /* * IRQ handler for ME interaction * Note: don't use MSI here as the PCH has bugs. */ - pci_disable_msi(dev); - ret = request_irq(dev->irq, ips_irq_handler, IRQF_SHARED, "ips", - ips); + ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_LEGACY); + if (ret < 0) + return ret; + + ips->irq = pci_irq_vector(dev, 0); + + ret = request_irq(ips->irq, ips_irq_handler, IRQF_SHARED, "ips", ips); if (ret) { dev_err(&dev->dev, "request irq failed, aborting\n"); - goto error_unmap; + return ret; } /* Enable aux, hot & critical interrupts */ @@ -1672,13 +1650,8 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) error_thread_cleanup: kthread_stop(ips->adjust); error_free_irq: - free_irq(ips->dev->irq, ips); -error_unmap: - iounmap(ips->regmap); -error_release: - pci_release_regions(dev); -error_free: - kfree(ips); + free_irq(ips->irq, ips); + pci_free_irq_vectors(dev); return ret; } @@ -1709,27 +1682,20 @@ static void ips_remove(struct pci_dev *dev) wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit); - free_irq(ips->dev->irq, ips); + free_irq(ips->irq, ips); + pci_free_irq_vectors(dev); if (ips->adjust) kthread_stop(ips->adjust); if (ips->monitor) kthread_stop(ips->monitor); - iounmap(ips->regmap); - pci_release_regions(dev); - kfree(ips); dev_dbg(&dev->dev, "IPS driver removed\n"); } -static void ips_shutdown(struct pci_dev *dev) -{ -} - static struct pci_driver ips_pci_driver = { .name = "intel ips", .id_table = ips_id_table, .probe = ips_probe, .remove = ips_remove, - .shutdown = ips_shutdown, }; module_pci_driver(ips_pci_driver); diff --git a/drivers/platform/x86/intel_ips.h b/drivers/platform/x86/intel_ips.h index 73299beff5b3..60f4e3ddbe9f 100644 --- a/drivers/platform/x86/intel_ips.h +++ b/drivers/platform/x86/intel_ips.h @@ -10,10 +10,6 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - * * The full GNU General Public License is included in this distribution in * the file called "COPYING". */ diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c index a47a41fc10ad..b5b890127479 100644 --- a/drivers/platform/x86/intel_punit_ipc.c +++ b/drivers/platform/x86/intel_punit_ipc.c @@ -252,28 +252,28 @@ static int intel_punit_get_bars(struct platform_device *pdev) * - GTDRIVER_IPC BASE_IFACE */ res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - if (res) { + if (res && resource_size(res) > 1) { addr = devm_ioremap_resource(&pdev->dev, res); if (!IS_ERR(addr)) punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; } res = platform_get_resource(pdev, IORESOURCE_MEM, 3); - if (res) { + if (res && resource_size(res) > 1) { addr = devm_ioremap_resource(&pdev->dev, res); if (!IS_ERR(addr)) punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; } res = platform_get_resource(pdev, IORESOURCE_MEM, 4); - if (res) { + if (res && resource_size(res) > 1) { addr = devm_ioremap_resource(&pdev->dev, res); if (!IS_ERR(addr)) punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; } res = platform_get_resource(pdev, IORESOURCE_MEM, 5); - if (res) { + if (res && resource_size(res) > 1) { addr = devm_ioremap_resource(&pdev->dev, res); if (!IS_ERR(addr)) punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; diff --git a/drivers/platform/x86/intel_telemetry_core.c b/drivers/platform/x86/intel_telemetry_core.c index 0d4c3808a6d8..f378621b5fe9 100644 --- a/drivers/platform/x86/intel_telemetry_core.c +++ b/drivers/platform/x86/intel_telemetry_core.c @@ -15,9 +15,8 @@ * Telemetry Framework provides platform related PM and performance statistics. * This file provides the core telemetry API implementation. */ -#include <linux/module.h> -#include <linux/init.h> #include <linux/device.h> +#include <linux/module.h> #include <asm/intel_telemetry.h> diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index d4fc42b4cbeb..4249e8267bbc 100644 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -21,14 +21,12 @@ * /sys/kernel/debug/telemetry/ioss_race_verbosity: Write and Change Tracing * Verbosity via firmware */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/device.h> #include <linux/debugfs.h> -#include <linux/seq_file.h> +#include <linux/device.h> #include <linux/io.h> -#include <linux/uaccess.h> +#include <linux/module.h> #include <linux/pci.h> +#include <linux/seq_file.h> #include <linux/suspend.h> #include <asm/cpu_device_id.h> @@ -76,8 +74,6 @@ #define TELEM_IOSS_DX_D0IX_EVTS 25 #define TELEM_IOSS_PG_EVTS 30 -#define TELEM_EVT_LEN(x) (sizeof(x)/sizeof((x)[0])) - #define TELEM_DEBUGFS_CPU(model, data) \ { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, (unsigned long)&data} @@ -304,13 +300,13 @@ static struct telemetry_debugfs_conf telem_apl_debugfs_conf = { .ioss_d0ix_data = telem_apl_ioss_d0ix_data, .ioss_pg_data = telem_apl_ioss_pg_data, - .pss_idle_evts = TELEM_EVT_LEN(telem_apl_pss_idle_data), - .pcs_idle_blkd_evts = TELEM_EVT_LEN(telem_apl_pcs_idle_blkd_data), - .pcs_s0ix_blkd_evts = TELEM_EVT_LEN(telem_apl_pcs_s0ix_blkd_data), - .pss_ltr_evts = TELEM_EVT_LEN(telem_apl_pss_ltr_data), - .pss_wakeup_evts = TELEM_EVT_LEN(telem_apl_pss_wakeup), - .ioss_d0ix_evts = TELEM_EVT_LEN(telem_apl_ioss_d0ix_data), - .ioss_pg_evts = TELEM_EVT_LEN(telem_apl_ioss_pg_data), + .pss_idle_evts = ARRAY_SIZE(telem_apl_pss_idle_data), + .pcs_idle_blkd_evts = ARRAY_SIZE(telem_apl_pcs_idle_blkd_data), + .pcs_s0ix_blkd_evts = ARRAY_SIZE(telem_apl_pcs_s0ix_blkd_data), + .pss_ltr_evts = ARRAY_SIZE(telem_apl_pss_ltr_data), + .pss_wakeup_evts = ARRAY_SIZE(telem_apl_pss_wakeup), + .ioss_d0ix_evts = ARRAY_SIZE(telem_apl_ioss_d0ix_data), + .ioss_pg_evts = ARRAY_SIZE(telem_apl_ioss_pg_data), .pstates_id = TELEM_APL_PSS_PSTATES_ID, .pss_idle_id = TELEM_APL_PSS_IDLE_ID, diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index e0424d5a795a..2f889d6c270e 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -16,15 +16,9 @@ * It used the PUNIT and PMC IPC interfaces for configuring the counters. * The accumulated results are fetched from SRAM. */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/device.h> -#include <linux/debugfs.h> -#include <linux/seq_file.h> + #include <linux/io.h> -#include <linux/uaccess.h> -#include <linux/pci.h> -#include <linux/suspend.h> +#include <linux/module.h> #include <linux/platform_device.h> #include <asm/cpu_device_id.h> @@ -256,7 +250,7 @@ static int telemetry_check_evtid(enum telemetry_unit telem_unit, break; default: - pr_err("Unknown Telemetry action Specified %d\n", action); + pr_err("Unknown Telemetry action specified %d\n", action); return -EINVAL; } @@ -659,7 +653,7 @@ static int telemetry_setup(struct platform_device *pdev) ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, TELEM_RESET); if (ret) { - dev_err(&pdev->dev, "TELEMTRY Setup Failed\n"); + dev_err(&pdev->dev, "TELEMETRY Setup Failed\n"); return ret; } return 0; @@ -685,7 +679,7 @@ static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig, ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, TELEM_UPDATE); if (ret) - pr_err("TELEMTRY Config Failed\n"); + pr_err("TELEMETRY Config Failed\n"); return ret; } @@ -822,7 +816,7 @@ static int telemetry_plt_reset_events(void) ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, TELEM_RESET); if (ret) - pr_err("TELEMTRY Reset Failed\n"); + pr_err("TELEMETRY Reset Failed\n"); return ret; } @@ -885,7 +879,7 @@ static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts, ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, TELEM_ADD); if (ret) - pr_err("TELEMTRY ADD Failed\n"); + pr_err("TELEMETRY ADD Failed\n"); return ret; } @@ -1195,7 +1189,7 @@ static int telemetry_pltdrv_probe(struct platform_device *pdev) ret = telemetry_set_pltdata(&telm_pltops, telm_conf); if (ret) { - dev_err(&pdev->dev, "TELEMTRY Set Pltops Failed.\n"); + dev_err(&pdev->dev, "TELEMETRY Set Pltops Failed.\n"); goto out; } @@ -1210,7 +1204,7 @@ out: iounmap(telm_conf->pss_config.regmap); if (telm_conf->ioss_config.regmap) iounmap(telm_conf->ioss_config.regmap); - dev_err(&pdev->dev, "TELEMTRY Setup Failed.\n"); + dev_err(&pdev->dev, "TELEMETRY Setup Failed.\n"); return ret; } @@ -1234,7 +1228,6 @@ static struct platform_driver telemetry_soc_driver = { static int __init telemetry_module_init(void) { - pr_info(DRIVER_NAME ": version %s loaded\n", DRIVER_VERSION); return platform_driver_register(&telemetry_soc_driver); } diff --git a/drivers/platform/x86/intel_turbo_max_3.c b/drivers/platform/x86/intel_turbo_max_3.c index 4f60d8e32a0a..d4ea01805879 100644 --- a/drivers/platform/x86/intel_turbo_max_3.c +++ b/drivers/platform/x86/intel_turbo_max_3.c @@ -125,6 +125,7 @@ static int itmt_legacy_cpu_online(unsigned int cpu) static const struct x86_cpu_id itmt_legacy_cpu_ids[] = { ICPU(INTEL_FAM6_BROADWELL_X), + ICPU(INTEL_FAM6_SKYLAKE_X), {} }; diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 4f3de2a8c4df..504256c3660d 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -216,8 +216,8 @@ static struct resource mlxplat_mlxcpld_resources[] = { [0] = DEFINE_RES_IRQ_NAMED(17, "mlxcpld-hotplug"), }; -struct platform_device *mlxplat_dev; -struct mlxcpld_hotplug_platform_data *mlxplat_hotplug; +static struct platform_device *mlxplat_dev; +static struct mlxcpld_hotplug_platform_data *mlxplat_hotplug; static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) { diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index bc98ef95514a..9b9e1f39bbfb 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -8,6 +8,7 @@ */ #include <linux/acpi.h> +#include <linux/dmi.h> #include <linux/input-polldev.h> #include <linux/kernel.h> #include <linux/module.h> @@ -64,8 +65,23 @@ static void peaq_wmi_poll(struct input_polled_dev *dev) } } +/* Some other devices (Shuttle XS35) use the same WMI GUID for other purposes */ +static const struct dmi_system_id peaq_dmi_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), + DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), + }, + }, + {} +}; + static int __init peaq_wmi_init(void) { + /* WMI GUID is not unique, also check for a DMI match */ + if (!dmi_check_system(peaq_dmi_table)) + return -ENODEV; + if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) return -ENODEV; @@ -86,9 +102,6 @@ static int __init peaq_wmi_init(void) static void __exit peaq_wmi_exit(void) { - if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) - return; - input_unregister_polled_device(peaq_poll_dev); } diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index 1157a7b646d6..266535c2a72f 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -58,6 +58,7 @@ static const struct property_entry dexp_ursus_7w_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-y", 630), PROPERTY_ENTRY_STRING("firmware-name", "gsl1686-dexp-ursus-7w.fw"), PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), { } }; @@ -72,6 +73,7 @@ static const struct property_entry surftab_wintron70_st70416_6_props[] = { PROPERTY_ENTRY_STRING("firmware-name", "gsl1686-surftab-wintron70-st70416-6.fw"), PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), { } }; @@ -83,6 +85,8 @@ static const struct silead_ts_dmi_data surftab_wintron70_st70416_6_data = { static const struct property_entry gp_electronic_t701_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 960), PROPERTY_ENTRY_U32("touchscreen-size-y", 640), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-gp-electronic-t701.fw"), { } @@ -114,6 +118,7 @@ static const struct property_entry pov_mobii_wintab_p800w_props[] = { PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-pov-mobii-wintab-p800w.fw"), + PROPERTY_ENTRY_BOOL("silead,home-button"), { } }; @@ -136,6 +141,36 @@ static const struct silead_ts_dmi_data itworks_tw891_data = { .properties = itworks_tw891_props, }; +static const struct property_entry chuwi_hi8_pro_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1728), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1148), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3680-chuwi-hi8-pro.fw"), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data chuwi_hi8_pro_data = { + .acpi_name = "MSSL1680:00", + .properties = chuwi_hi8_pro_props, +}; + +static const struct property_entry digma_citi_e200_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1980), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1500), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl1686-digma_citi_e200.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data digma_citi_e200_data = { + .acpi_name = "MSSL1680:00", + .properties = digma_citi_e200_props, +}; + static const struct dmi_system_id silead_ts_dmi_table[] = { { /* CUBE iwork8 Air */ @@ -219,6 +254,23 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "TW891"), }, }, + { + /* Chuwi Hi8 Pro */ + .driver_data = (void *)&chuwi_hi8_pro_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "X1D3_C806N"), + }, + }, + { + /* Digma Citi E200 */ + .driver_data = (void *)&digma_citi_e200_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Digma"), + DMI_MATCH(DMI_PRODUCT_NAME, "CITI E200"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + }, + }, { }, }; diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index a16cea2be9c3..62aa2c37b8d2 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -1627,7 +1627,7 @@ static const struct rfkill_ops sony_rfkill_ops = { static int sony_nc_setup_rfkill(struct acpi_device *device, enum sony_nc_rfkill nc_type) { - int err = 0; + int err; struct rfkill *rfk; enum rfkill_type type; const char *name; @@ -1660,17 +1660,19 @@ static int sony_nc_setup_rfkill(struct acpi_device *device, if (!rfk) return -ENOMEM; - if (sony_call_snc_handle(sony_rfkill_handle, 0x200, &result) < 0) { + err = sony_call_snc_handle(sony_rfkill_handle, 0x200, &result); + if (err < 0) { rfkill_destroy(rfk); - return -1; + return err; } hwblock = !(result & 0x1); - if (sony_call_snc_handle(sony_rfkill_handle, - sony_rfkill_address[nc_type], - &result) < 0) { + err = sony_call_snc_handle(sony_rfkill_handle, + sony_rfkill_address[nc_type], + &result); + if (err < 0) { rfkill_destroy(rfk); - return -1; + return err; } swblock = !(result & 0x2); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3887dfeafc96..117be48ff4de 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -310,8 +310,7 @@ static struct { enum { TP_HOTKEY_TABLET_NONE = 0, TP_HOTKEY_TABLET_USES_MHKG, - /* X1 Yoga 2016, seen on BIOS N1FET44W */ - TP_HOTKEY_TABLET_USES_CMMD, + TP_HOTKEY_TABLET_USES_GMMS, } hotkey_tablet; u32 kbdlight:1; u32 light:1; @@ -2044,8 +2043,28 @@ static void hotkey_poll_setup(const bool may_warn); /* HKEY.MHKG() return bits */ #define TP_HOTKEY_TABLET_MASK (1 << 3) -/* ThinkPad X1 Yoga (2016) */ -#define TP_EC_CMMD_TABLET_MODE 0x6 +enum { + TP_ACPI_MULTI_MODE_INVALID = 0, + TP_ACPI_MULTI_MODE_UNKNOWN = 1 << 0, + TP_ACPI_MULTI_MODE_LAPTOP = 1 << 1, + TP_ACPI_MULTI_MODE_TABLET = 1 << 2, + TP_ACPI_MULTI_MODE_FLAT = 1 << 3, + TP_ACPI_MULTI_MODE_STAND = 1 << 4, + TP_ACPI_MULTI_MODE_TENT = 1 << 5, + TP_ACPI_MULTI_MODE_STAND_TENT = 1 << 6, +}; + +enum { + /* The following modes are considered tablet mode for the purpose of + * reporting the status to userspace. i.e. in all these modes it makes + * sense to disable the laptop input devices such as touchpad and + * keyboard. + */ + TP_ACPI_MULTI_MODE_TABLET_LIKE = TP_ACPI_MULTI_MODE_TABLET | + TP_ACPI_MULTI_MODE_STAND | + TP_ACPI_MULTI_MODE_TENT | + TP_ACPI_MULTI_MODE_STAND_TENT, +}; static int hotkey_get_wlsw(void) { @@ -2066,6 +2085,90 @@ static int hotkey_get_wlsw(void) return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF; } +static int hotkey_gmms_get_tablet_mode(int s, int *has_tablet_mode) +{ + int type = (s >> 16) & 0xffff; + int value = s & 0xffff; + int mode = TP_ACPI_MULTI_MODE_INVALID; + int valid_modes = 0; + + if (has_tablet_mode) + *has_tablet_mode = 0; + + switch (type) { + case 1: + valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | + TP_ACPI_MULTI_MODE_TABLET | + TP_ACPI_MULTI_MODE_STAND_TENT; + break; + case 2: + valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | + TP_ACPI_MULTI_MODE_FLAT | + TP_ACPI_MULTI_MODE_TABLET | + TP_ACPI_MULTI_MODE_STAND | + TP_ACPI_MULTI_MODE_TENT; + break; + case 3: + valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | + TP_ACPI_MULTI_MODE_FLAT; + break; + case 4: + valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | + TP_ACPI_MULTI_MODE_TABLET | + TP_ACPI_MULTI_MODE_STAND | + TP_ACPI_MULTI_MODE_TENT; + break; + case 5: + valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | + TP_ACPI_MULTI_MODE_FLAT | + TP_ACPI_MULTI_MODE_TABLET | + TP_ACPI_MULTI_MODE_STAND | + TP_ACPI_MULTI_MODE_TENT; + break; + default: + pr_err("Unknown multi mode status type %d with value 0x%04X, please report this to %s\n", + type, value, TPACPI_MAIL); + return 0; + } + + if (has_tablet_mode && (valid_modes & TP_ACPI_MULTI_MODE_TABLET_LIKE)) + *has_tablet_mode = 1; + + switch (value) { + case 1: + mode = TP_ACPI_MULTI_MODE_LAPTOP; + break; + case 2: + mode = TP_ACPI_MULTI_MODE_FLAT; + break; + case 3: + mode = TP_ACPI_MULTI_MODE_TABLET; + break; + case 4: + if (type == 1) + mode = TP_ACPI_MULTI_MODE_STAND_TENT; + else + mode = TP_ACPI_MULTI_MODE_STAND; + break; + case 5: + mode = TP_ACPI_MULTI_MODE_TENT; + break; + default: + if (type == 5 && value == 0xffff) { + pr_warn("Multi mode status is undetected, assuming laptop\n"); + return 0; + } + } + + if (!(mode & valid_modes)) { + pr_err("Unknown/reserved multi mode value 0x%04X for type %d, please report this to %s\n", + value, type, TPACPI_MAIL); + return 0; + } + + return !!(mode & TP_ACPI_MULTI_MODE_TABLET_LIKE); +} + static int hotkey_get_tablet_mode(int *status) { int s; @@ -2077,11 +2180,11 @@ static int hotkey_get_tablet_mode(int *status) *status = ((s & TP_HOTKEY_TABLET_MASK) != 0); break; - case TP_HOTKEY_TABLET_USES_CMMD: - if (!acpi_evalf(ec_handle, &s, "CMMD", "d")) + case TP_HOTKEY_TABLET_USES_GMMS: + if (!acpi_evalf(hkey_handle, &s, "GMMS", "dd", 0)) return -EIO; - *status = (s == TP_EC_CMMD_TABLET_MODE); + *status = hotkey_gmms_get_tablet_mode(s, NULL); break; default: break; @@ -3113,16 +3216,19 @@ static int hotkey_init_tablet_mode(void) int in_tablet_mode = 0, res; char *type = NULL; - if (acpi_evalf(hkey_handle, &res, "MHKG", "qd")) { + if (acpi_evalf(hkey_handle, &res, "GMMS", "qdd", 0)) { + int has_tablet_mode; + + in_tablet_mode = hotkey_gmms_get_tablet_mode(res, + &has_tablet_mode); + if (has_tablet_mode) + tp_features.hotkey_tablet = TP_HOTKEY_TABLET_USES_GMMS; + type = "GMMS"; + } else if (acpi_evalf(hkey_handle, &res, "MHKG", "qd")) { /* For X41t, X60t, X61t Tablets... */ tp_features.hotkey_tablet = TP_HOTKEY_TABLET_USES_MHKG; in_tablet_mode = !!(res & TP_HOTKEY_TABLET_MASK); type = "MHKG"; - } else if (acpi_evalf(ec_handle, &res, "CMMD", "qd")) { - /* For X1 Yoga (2016) */ - tp_features.hotkey_tablet = TP_HOTKEY_TABLET_USES_CMMD; - in_tablet_mode = res == TP_EC_CMMD_TABLET_MODE; - type = "CMMD"; } if (!tp_features.hotkey_tablet) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 0765b1797d4c..791449a2370f 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -33,17 +33,20 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/types.h> +#include <linux/acpi.h> #include <linux/device.h> +#include <linux/init.h> +#include <linux/kernel.h> #include <linux/list.h> -#include <linux/acpi.h> -#include <linux/slab.h> +#include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/wmi.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> #include <linux/uuid.h> +#include <linux/wmi.h> +#include <uapi/linux/wmi.h> ACPI_MODULE_NAME("wmi"); MODULE_AUTHOR("Carlos Corbacho"); @@ -69,9 +72,12 @@ struct wmi_block { struct wmi_device dev; struct list_head list; struct guid_block gblock; + struct miscdevice char_dev; + struct mutex char_mutex; struct acpi_device *acpi_device; wmi_notify_handler handler; void *handler_data; + u64 req_buf_size; bool read_takes_no_args; }; @@ -188,6 +194,25 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) /* * Exported WMI functions */ + +/** + * set_required_buffer_size - Sets the buffer size needed for performing IOCTL + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * + * Allocates memory needed for buffer, stores the buffer size in that memory + */ +int set_required_buffer_size(struct wmi_device *wdev, u64 length) +{ + struct wmi_block *wblock; + + wblock = container_of(wdev, struct wmi_block, dev); + wblock->req_buf_size = length; + + return 0; +} +EXPORT_SYMBOL_GPL(set_required_buffer_size); + /** * wmi_evaluate_method - Evaluate a WMI method * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba @@ -201,6 +226,28 @@ static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) { + struct wmi_block *wblock = NULL; + + if (!find_guid(guid_string, &wblock)) + return AE_ERROR; + return wmidev_evaluate_method(&wblock->dev, instance, method_id, + in, out); +} +EXPORT_SYMBOL_GPL(wmi_evaluate_method); + +/** + * wmidev_evaluate_method - Evaluate a WMI method + * @wdev: A wmi bus device from a driver + * @instance: Instance index + * @method_id: Method ID to call + * &in: Buffer containing input for the method call + * &out: Empty buffer to return the method results + * + * Call an ACPI-WMI method + */ +acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, + u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) +{ struct guid_block *block = NULL; struct wmi_block *wblock = NULL; acpi_handle handle; @@ -209,9 +256,7 @@ u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) union acpi_object params[3]; char method[5] = "WM"; - if (!find_guid(guid_string, &wblock)) - return AE_ERROR; - + wblock = container_of(wdev, struct wmi_block, dev); block = &wblock->gblock; handle = wblock->acpi_device->handle; @@ -246,7 +291,7 @@ u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) return status; } -EXPORT_SYMBOL_GPL(wmi_evaluate_method); +EXPORT_SYMBOL_GPL(wmidev_evaluate_method); static acpi_status __query_block(struct wmi_block *wblock, u8 instance, struct acpi_buffer *out) @@ -348,23 +393,6 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance) } EXPORT_SYMBOL_GPL(wmidev_block_query); -struct wmi_device *wmidev_get_other_guid(struct wmi_device *wdev, - const char *guid_string) -{ - struct wmi_block *this_wb = container_of(wdev, struct wmi_block, dev); - struct wmi_block *other_wb; - - if (!find_guid(guid_string, &other_wb)) - return NULL; - - if (other_wb->acpi_device != this_wb->acpi_device) - return NULL; - - get_device(&other_wb->dev.dev); - return &other_wb->dev; -} -EXPORT_SYMBOL_GPL(wmidev_get_other_guid); - /** * wmi_set_block - Write to a WMI block * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba @@ -761,6 +789,113 @@ static int wmi_dev_match(struct device *dev, struct device_driver *driver) return 0; } +static int wmi_char_open(struct inode *inode, struct file *filp) +{ + const char *driver_name = filp->f_path.dentry->d_iname; + struct wmi_block *wblock = NULL; + struct wmi_block *next = NULL; + + list_for_each_entry_safe(wblock, next, &wmi_block_list, list) { + if (!wblock->dev.dev.driver) + continue; + if (strcmp(driver_name, wblock->dev.dev.driver->name) == 0) { + filp->private_data = wblock; + break; + } + } + + if (!filp->private_data) + return -ENODEV; + + return nonseekable_open(inode, filp); +} + +static ssize_t wmi_char_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + struct wmi_block *wblock = filp->private_data; + + return simple_read_from_buffer(buffer, length, offset, + &wblock->req_buf_size, + sizeof(wblock->req_buf_size)); +} + +static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct wmi_ioctl_buffer __user *input = + (struct wmi_ioctl_buffer __user *) arg; + struct wmi_block *wblock = filp->private_data; + struct wmi_ioctl_buffer *buf = NULL; + struct wmi_driver *wdriver = NULL; + int ret; + + if (_IOC_TYPE(cmd) != WMI_IOC) + return -ENOTTY; + + /* make sure we're not calling a higher instance than exists*/ + if (_IOC_NR(cmd) >= wblock->gblock.instance_count) + return -EINVAL; + + mutex_lock(&wblock->char_mutex); + buf = wblock->handler_data; + if (get_user(buf->length, &input->length)) { + dev_dbg(&wblock->dev.dev, "Read length from user failed\n"); + ret = -EFAULT; + goto out_ioctl; + } + /* if it's too small, abort */ + if (buf->length < wblock->req_buf_size) { + dev_err(&wblock->dev.dev, + "Buffer %lld too small, need at least %lld\n", + buf->length, wblock->req_buf_size); + ret = -EINVAL; + goto out_ioctl; + } + /* if it's too big, warn, driver will only use what is needed */ + if (buf->length > wblock->req_buf_size) + dev_warn(&wblock->dev.dev, + "Buffer %lld is bigger than required %lld\n", + buf->length, wblock->req_buf_size); + + /* copy the structure from userspace */ + if (copy_from_user(buf, input, wblock->req_buf_size)) { + dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n", + wblock->req_buf_size); + ret = -EFAULT; + goto out_ioctl; + } + + /* let the driver do any filtering and do the call */ + wdriver = container_of(wblock->dev.dev.driver, + struct wmi_driver, driver); + if (!try_module_get(wdriver->driver.owner)) { + ret = -EBUSY; + goto out_ioctl; + } + ret = wdriver->filter_callback(&wblock->dev, cmd, buf); + module_put(wdriver->driver.owner); + if (ret) + goto out_ioctl; + + /* return the result (only up to our internal buffer size) */ + if (copy_to_user(input, buf, wblock->req_buf_size)) { + dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n", + wblock->req_buf_size); + ret = -EFAULT; + } + +out_ioctl: + mutex_unlock(&wblock->char_mutex); + return ret; +} + +static const struct file_operations wmi_fops = { + .owner = THIS_MODULE, + .read = wmi_char_read, + .open = wmi_char_open, + .unlocked_ioctl = wmi_ioctl, + .compat_ioctl = wmi_ioctl, +}; static int wmi_dev_probe(struct device *dev) { @@ -768,16 +903,63 @@ static int wmi_dev_probe(struct device *dev) struct wmi_driver *wdriver = container_of(dev->driver, struct wmi_driver, driver); int ret = 0; + int count; + char *buf; if (ACPI_FAILURE(wmi_method_enable(wblock, 1))) dev_warn(dev, "failed to enable device -- probing anyway\n"); if (wdriver->probe) { ret = wdriver->probe(dev_to_wdev(dev)); - if (ret != 0 && ACPI_FAILURE(wmi_method_enable(wblock, 0))) - dev_warn(dev, "failed to disable device\n"); + if (ret != 0) + goto probe_failure; + } + + /* driver wants a character device made */ + if (wdriver->filter_callback) { + /* check that required buffer size declared by driver or MOF */ + if (!wblock->req_buf_size) { + dev_err(&wblock->dev.dev, + "Required buffer size not set\n"); + ret = -EINVAL; + goto probe_failure; + } + + count = get_order(wblock->req_buf_size); + wblock->handler_data = (void *)__get_free_pages(GFP_KERNEL, + count); + if (!wblock->handler_data) { + ret = -ENOMEM; + goto probe_failure; + } + + buf = kmalloc(strlen(wdriver->driver.name) + 4, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto probe_string_failure; + } + sprintf(buf, "wmi/%s", wdriver->driver.name); + wblock->char_dev.minor = MISC_DYNAMIC_MINOR; + wblock->char_dev.name = buf; + wblock->char_dev.fops = &wmi_fops; + wblock->char_dev.mode = 0444; + ret = misc_register(&wblock->char_dev); + if (ret) { + dev_warn(dev, "failed to register char dev: %d", ret); + ret = -ENOMEM; + goto probe_misc_failure; + } } + return 0; + +probe_misc_failure: + kfree(buf); +probe_string_failure: + kfree(wblock->handler_data); +probe_failure: + if (ACPI_FAILURE(wmi_method_enable(wblock, 0))) + dev_warn(dev, "failed to disable device\n"); return ret; } @@ -788,6 +970,13 @@ static int wmi_dev_remove(struct device *dev) container_of(dev->driver, struct wmi_driver, driver); int ret = 0; + if (wdriver->filter_callback) { + misc_deregister(&wblock->char_dev); + kfree(wblock->char_dev.name); + free_pages((unsigned long)wblock->handler_data, + get_order(wblock->req_buf_size)); + } + if (wdriver->remove) ret = wdriver->remove(dev_to_wdev(dev)); @@ -844,6 +1033,7 @@ static int wmi_create_device(struct device *wmi_bus_dev, if (gblock->flags & ACPI_WMI_METHOD) { wblock->dev.dev.type = &wmi_type_method; + mutex_init(&wblock->char_mutex); goto out_init; } @@ -1145,7 +1335,7 @@ static int acpi_wmi_remove(struct platform_device *device) acpi_remove_address_space_handler(acpi_device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); wmi_free_devices(acpi_device); - device_unregister((struct device *)dev_get_drvdata(&device->dev)); + device_destroy(&wmi_bus_class, MKDEV(0, 0)); return 0; } @@ -1199,7 +1389,7 @@ static int acpi_wmi_probe(struct platform_device *device) return 0; err_remove_busdev: - device_unregister(wmi_bus_dev); + device_destroy(&wmi_bus_class, MKDEV(0, 0)); err_remove_notify_handler: acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, @@ -1264,8 +1454,8 @@ err_unreg_class: static void __exit acpi_wmi_exit(void) { platform_driver_unregister(&acpi_wmi_driver); - class_unregister(&wmi_bus_class); bus_unregister(&wmi_bus_type); + class_unregister(&wmi_bus_class); } subsys_initcall(acpi_wmi_init); diff --git a/include/linux/wmi.h b/include/linux/wmi.h index cd0d7734dc49..4757cb5077e5 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -18,6 +18,7 @@ #include <linux/device.h> #include <linux/acpi.h> +#include <uapi/linux/wmi.h> struct wmi_device { struct device dev; @@ -26,13 +27,17 @@ struct wmi_device { bool setable; }; +/* evaluate the ACPI method associated with this device */ +extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev, + u8 instance, u32 method_id, + const struct acpi_buffer *in, + struct acpi_buffer *out); + /* Caller must kfree the result. */ extern union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance); -/* Gets another device on the same bus. Caller must put_device the result. */ -extern struct wmi_device *wmidev_get_other_guid(struct wmi_device *wdev, - const char *guid_string); +extern int set_required_buffer_size(struct wmi_device *wdev, u64 length); struct wmi_device_id { const char *guid_string; @@ -45,6 +50,8 @@ struct wmi_driver { int (*probe)(struct wmi_device *wdev); int (*remove)(struct wmi_device *wdev); void (*notify)(struct wmi_device *device, union acpi_object *data); + long (*filter_callback)(struct wmi_device *wdev, unsigned int cmd, + struct wmi_ioctl_buffer *arg); }; extern int __must_check __wmi_driver_register(struct wmi_driver *driver, diff --git a/include/uapi/linux/wmi.h b/include/uapi/linux/wmi.h new file mode 100644 index 000000000000..7a92e9e3d1c0 --- /dev/null +++ b/include/uapi/linux/wmi.h @@ -0,0 +1,73 @@ +/* + * User API methods for ACPI-WMI mapping driver + * + * Copyright (C) 2017 Dell, Inc. + * + * 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. + */ +#ifndef _UAPI_LINUX_WMI_H +#define _UAPI_LINUX_WMI_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +/* WMI bus will filter all WMI vendor driver requests through this IOC */ +#define WMI_IOC 'W' + +/* All ioctl requests through WMI should declare their size followed by + * relevant data objects + */ +struct wmi_ioctl_buffer { + __u64 length; + __u8 data[]; +}; + +/* This structure may be modified by the firmware when we enter + * system management mode through SMM, hence the volatiles + */ +struct calling_interface_buffer { + __u16 cmd_class; + __u16 cmd_select; + volatile __u32 input[4]; + volatile __u32 output[4]; +} __packed; + +struct dell_wmi_extensions { + __u32 argattrib; + __u32 blength; + __u8 data[]; +} __packed; + +struct dell_wmi_smbios_buffer { + __u64 length; + struct calling_interface_buffer std; + struct dell_wmi_extensions ext; +} __packed; + +/* Whitelisted smbios class/select commands */ +#define CLASS_TOKEN_READ 0 +#define CLASS_TOKEN_WRITE 1 +#define SELECT_TOKEN_STD 0 +#define SELECT_TOKEN_BAT 1 +#define SELECT_TOKEN_AC 2 +#define CLASS_FLASH_INTERFACE 7 +#define SELECT_FLASH_INTERFACE 3 +#define CLASS_ADMIN_PROP 10 +#define SELECT_ADMIN_PROP 3 +#define CLASS_INFO 17 +#define SELECT_RFKILL 11 +#define SELECT_APP_REGISTRATION 3 +#define SELECT_DOCK 22 + +/* whitelisted tokens */ +#define CAPSULE_EN_TOKEN 0x0461 +#define CAPSULE_DIS_TOKEN 0x0462 +#define WSMT_EN_TOKEN 0x04EC +#define WSMT_DIS_TOKEN 0x04ED + +/* Dell SMBIOS calling IOCTL command used by dell-smbios-wmi */ +#define DELL_WMI_SMBIOS_CMD _IOWR(WMI_IOC, 0, struct dell_wmi_smbios_buffer) + +#endif diff --git a/tools/Makefile b/tools/Makefile index c03b4f69d5b7..be02c8b904db 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -30,6 +30,7 @@ help: @echo ' usb - USB testing tools' @echo ' virtio - vhost test module' @echo ' vm - misc vm tools' + @echo ' wmi - WMI interface examples' @echo ' x86_energy_perf_policy - Intel energy policy tool' @echo '' @echo 'You can do:' @@ -58,7 +59,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds: FORCE +cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds wmi: FORCE $(call descend,$@) liblockdep: FORCE @@ -93,7 +94,7 @@ kvm_stat: FORCE all: acpi cgroup cpupower gpio hv firewire liblockdep \ perf selftests spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ - tmon freefall iio objtool kvm_stat + tmon freefall iio objtool kvm_stat wmi acpi_install: $(call descend,power/$(@:_install=),install) @@ -101,7 +102,7 @@ acpi_install: cpupower_install: $(call descend,power/$(@:_install=),install) -cgroup_install firewire_install gpio_install hv_install iio_install perf_install spi_install usb_install virtio_install vm_install bpf_install objtool_install: +cgroup_install firewire_install gpio_install hv_install iio_install perf_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install: $(call descend,$(@:_install=),install) liblockdep_install: @@ -126,7 +127,8 @@ install: acpi_install cgroup_install cpupower_install gpio_install \ hv_install firewire_install iio_install liblockdep_install \ perf_install selftests_install turbostat_install usb_install \ virtio_install vm_install bpf_install x86_energy_perf_policy_install \ - tmon_install freefall_install objtool_install kvm_stat_install + tmon_install freefall_install objtool_install kvm_stat_install \ + wmi_install acpi_clean: $(call descend,power/acpi,clean) @@ -134,7 +136,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean: +cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean: $(call descend,$(@:_clean=),clean) liblockdep_clean: @@ -172,6 +174,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \ perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ - gpio_clean objtool_clean leds_clean + gpio_clean objtool_clean leds_clean wmi_clean .PHONY: FORCE diff --git a/tools/wmi/Makefile b/tools/wmi/Makefile new file mode 100644 index 000000000000..e664f1167388 --- /dev/null +++ b/tools/wmi/Makefile @@ -0,0 +1,18 @@ +PREFIX ?= /usr +SBINDIR ?= sbin +INSTALL ?= install +CFLAGS += -D__EXPORTED_HEADERS__ -I../../include/uapi -I../../include +CC = $(CROSS_COMPILE)gcc + +TARGET = dell-smbios-example + +all: $(TARGET) + +%: %.c + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< + +clean: + $(RM) $(TARGET) + +install: dell-smbios-example + $(INSTALL) -D -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/$(SBINDIR)/$(TARGET) diff --git a/tools/wmi/dell-smbios-example.c b/tools/wmi/dell-smbios-example.c new file mode 100644 index 000000000000..9d3bde081249 --- /dev/null +++ b/tools/wmi/dell-smbios-example.c @@ -0,0 +1,210 @@ +/* + * Sample application for SMBIOS communication over WMI interface + * Performs the following: + * - Simple cmd_class/cmd_select lookup for TPM information + * - Simple query of known tokens and their values + * - Simple activation of a token + * + * Copyright (C) 2017 Dell, Inc. + * + * 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. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <unistd.h> + +/* if uapi header isn't installed, this might not yet exist */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif +#include <linux/wmi.h> + +/* It would be better to discover these using udev, but for a simple + * application they're hardcoded + */ +static const char *ioctl_devfs = "/dev/wmi/dell-smbios"; +static const char *token_sysfs = + "/sys/bus/platform/devices/dell-smbios.0/tokens"; + +static void show_buffer(struct dell_wmi_smbios_buffer *buffer) +{ + printf("Call: %x/%x [%x,%x,%x,%x]\nResults: [%8x,%8x,%8x,%8x]\n", + buffer->std.cmd_class, buffer->std.cmd_select, + buffer->std.input[0], buffer->std.input[1], + buffer->std.input[2], buffer->std.input[3], + buffer->std.output[0], buffer->std.output[1], + buffer->std.output[2], buffer->std.output[3]); +} + +static int run_wmi_smbios_cmd(struct dell_wmi_smbios_buffer *buffer) +{ + int fd; + int ret; + + fd = open(ioctl_devfs, O_NONBLOCK); + ret = ioctl(fd, DELL_WMI_SMBIOS_CMD, buffer); + close(fd); + return ret; +} + +static int find_token(__u16 token, __u16 *location, __u16 *value) +{ + char location_sysfs[60]; + char value_sysfs[57]; + char buf[4096]; + FILE *f; + int ret; + + ret = sprintf(value_sysfs, "%s/%04x_value", token_sysfs, token); + if (ret < 0) { + printf("sprintf value failed\n"); + return 2; + } + f = fopen(value_sysfs, "rb"); + if (!f) { + printf("failed to open %s\n", value_sysfs); + return 2; + } + fread(buf, 1, 4096, f); + fclose(f); + *value = (__u16) strtol(buf, NULL, 16); + + ret = sprintf(location_sysfs, "%s/%04x_location", token_sysfs, token); + if (ret < 0) { + printf("sprintf location failed\n"); + return 1; + } + f = fopen(location_sysfs, "rb"); + if (!f) { + printf("failed to open %s\n", location_sysfs); + return 2; + } + fread(buf, 1, 4096, f); + fclose(f); + *location = (__u16) strtol(buf, NULL, 16); + + if (*location) + return 0; + return 2; +} + +static int token_is_active(__u16 *location, __u16 *cmpvalue, + struct dell_wmi_smbios_buffer *buffer) +{ + int ret; + + buffer->std.cmd_class = CLASS_TOKEN_READ; + buffer->std.cmd_select = SELECT_TOKEN_STD; + buffer->std.input[0] = *location; + ret = run_wmi_smbios_cmd(buffer); + if (ret != 0 || buffer->std.output[0] != 0) + return ret; + ret = (buffer->std.output[1] == *cmpvalue); + return ret; +} + +static int query_token(__u16 token, struct dell_wmi_smbios_buffer *buffer) +{ + __u16 location; + __u16 value; + int ret; + + ret = find_token(token, &location, &value); + if (ret != 0) { + printf("unable to find token %04x\n", token); + return 1; + } + return token_is_active(&location, &value, buffer); +} + +static int activate_token(struct dell_wmi_smbios_buffer *buffer, + __u16 token) +{ + __u16 location; + __u16 value; + int ret; + + ret = find_token(token, &location, &value); + if (ret != 0) { + printf("unable to find token %04x\n", token); + return 1; + } + buffer->std.cmd_class = CLASS_TOKEN_WRITE; + buffer->std.cmd_select = SELECT_TOKEN_STD; + buffer->std.input[0] = location; + buffer->std.input[1] = 1; + ret = run_wmi_smbios_cmd(buffer); + return ret; +} + +static int query_buffer_size(__u64 *buffer_size) +{ + FILE *f; + + f = fopen(ioctl_devfs, "rb"); + if (!f) + return -EINVAL; + fread(buffer_size, sizeof(__u64), 1, f); + fclose(f); + return EXIT_SUCCESS; +} + +int main(void) +{ + struct dell_wmi_smbios_buffer *buffer; + int ret; + __u64 value = 0; + + ret = query_buffer_size(&value); + if (ret == EXIT_FAILURE || !value) { + printf("Unable to read buffer size\n"); + goto out; + } + printf("Detected required buffer size %lld\n", value); + + buffer = malloc(value); + if (buffer == NULL) { + printf("failed to alloc memory for ioctl\n"); + ret = -ENOMEM; + goto out; + } + buffer->length = value; + + /* simple SMBIOS call for looking up TPM info */ + buffer->std.cmd_class = CLASS_FLASH_INTERFACE; + buffer->std.cmd_select = SELECT_FLASH_INTERFACE; + buffer->std.input[0] = 2; + ret = run_wmi_smbios_cmd(buffer); + if (ret) { + printf("smbios ioctl failed: %d\n", ret); + ret = EXIT_FAILURE; + goto out; + } + show_buffer(buffer); + + /* query some tokens */ + ret = query_token(CAPSULE_EN_TOKEN, buffer); + printf("UEFI Capsule enabled token is: %d\n", ret); + ret = query_token(CAPSULE_DIS_TOKEN, buffer); + printf("UEFI Capsule disabled token is: %d\n", ret); + + /* activate UEFI capsule token if disabled */ + if (ret) { + printf("Enabling UEFI capsule token"); + if (activate_token(buffer, CAPSULE_EN_TOKEN)) { + printf("activate failed\n"); + ret = -1; + goto out; + } + } + ret = EXIT_SUCCESS; +out: + free(buffer); + return ret; +} |