From ce6a90027c10f970f872de5db0294f9e3e969f1c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 8 Sep 2017 10:23:11 -0500 Subject: platform/x86: Add driver to force WMI Thunderbolt controller power status Current implementations of Intel Thunderbolt controllers will go into a low power mode when not in use. Many machines containing these controllers also have a GPIO wired up that can force the controller awake. This is offered via a ACPI-WMI interface intended to be manipulated by a userspace utility. This mechanism is provided by Intel to OEMs to include in BIOS. It uses an industry wide GUID that is populated in a separate _WDG entry with no binary MOF. This interface allows software such as fwupd to wake up thunderbolt controllers to query the firmware version or flash new firmware. Signed-off-by: Mario Limonciello Reviewed-by: Mika Westerberg Reviewed-by: Yehezkel Bernat Signed-off-by: Darren Hart (VMware) [andy fixed merge conflicts and bump kernel version for ABI] Signed-off-by: Andy Shevchenko --- .../testing/sysfs-platform-intel-wmi-thunderbolt | 11 +++ Documentation/admin-guide/thunderbolt.rst | 15 +++ MAINTAINERS | 5 + drivers/platform/x86/Kconfig | 13 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/intel-wmi-thunderbolt.c | 101 +++++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt create mode 100644 drivers/platform/x86/intel-wmi-thunderbolt.c 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//force_power +Date: September 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" +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 6a4cd1f159ca..dadcd66ee12f 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -197,3 +197,18 @@ information is missing. To recover from this mode, one needs to flash a valid NVM image to the host host controller in the same way it is done in the previous chapter. + +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 2281af4b41b6..a6a5746ced9d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7092,6 +7092,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 +S: Maintained +F: drivers/platform/x86/intel-wmi-thunderbolt.c + INTEL(R) TRACE HUB M: Alexander Shishkin S: Supported diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 80b87954f6dd..f401ae463e9b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -658,6 +658,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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 91cec1751461..2b315d0df3b7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -39,6 +39,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/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c new file mode 100644 index 000000000000..32fb6cc33d72 --- /dev/null +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -0,0 +1,101 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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)) { + pr_err("intel-wmi-thunderbolt: failed setting %s\n", + buf); + return -ENODEV; + } + } else { + pr_err("intel-wmi-thunderbolt: unsupported mode: %d", mode); + } + 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 "); +MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5c003458db40cf3c89aeddd22c6e934c28b5a565 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 2 Sep 2017 18:20:40 +0200 Subject: platform/x86: intel_cht_int33fe: Work around BIOS bug on some devices 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. This commit checks for the max17047 already being enumerated through a separate MAX17047 ACPI device and if so it uses the i2c-client instantiated for this and attaches the device-props for the max17047 to that i2c-client. Signed-off-by: Hans de Goede Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_cht_int33fe.c | 70 ++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c index da706e2c4232..94716b2fbc00 100644 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -34,6 +34,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. + */ +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; +} + +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[] = { @@ -46,9 +82,11 @@ 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; 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)) { @@ -75,13 +113,25 @@ 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.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); @@ -106,7 +156,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 +168,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; } -- cgit v1.2.3 From ae11c5b2c2dfe8768b51f73f491869ba2cd9d4f9 Mon Sep 17 00:00:00 2001 From: Allen Pais Date: Wed, 13 Sep 2017 16:29:31 +0530 Subject: platform/x86: intel_telemetry_debugfs: Use standard ARRAY_SIZE() macro Use the standard ARRAY_SIZE() macro instead of the custom TELEM_EVT_LEN(). Signed-off-by: Allen Pais Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/intel_telemetry_debugfs.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index d4fc42b4cbeb..74e518dd237c 100644 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -76,8 +76,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 +302,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, -- cgit v1.2.3 From 12933ea2b8c1e1f87a5dfece947325ee9e6d65b0 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 14 Sep 2017 01:41:55 -0500 Subject: platform/x86: intel-wmi-thunderbolt: Silence error cases These were raised by Lukas Wunner as potential DOS attacks against the system log by passing bad data to sysfs. Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/intel-wmi-thunderbolt.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c index 32fb6cc33d72..c2257bd06f18 100644 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -41,13 +41,10 @@ static ssize_t force_power_store(struct device *dev, if (mode == 0 || mode == 1) { status = wmi_evaluate_method(INTEL_WMI_THUNDERBOLT_GUID, 0, 1, &input, NULL); - if (ACPI_FAILURE(status)) { - pr_err("intel-wmi-thunderbolt: failed setting %s\n", - buf); + if (ACPI_FAILURE(status)) return -ENODEV; - } } else { - pr_err("intel-wmi-thunderbolt: unsupported mode: %d", mode); + return -EINVAL; } return count; } -- cgit v1.2.3 From 821b85366284542e00dd834062144c637e818ee0 Mon Sep 17 00:00:00 2001 From: Jérôme de Bretagne Date: Mon, 18 Sep 2017 00:57:12 +0200 Subject: platform/x86: intel-hid: Power button suspend on Dell Latitude 7275 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Dell Latitude 7275 the 5-button array is not exposed in the ACPI tables, but notififies are still sent to the Intel HID device object (device ID INT33D5) in response to power button actions. They were ignored as the intel-hid driver was not prepared to take care of them until recently. Power button wakeup from suspend-to-idle was added in: commit 635173a17b03 ("intel-hid: Wake up Dell Latitude 7275 from suspend-to-idle") However power button suspend doesn't work yet on this platform so it would be good to add it also. On the affected platform (for which priv->array is NULL), add a new upfront check against the power button press notification (0xCE) to notify_handler(), outside the wakeup mode this time, which allows reporting the power button press event and triggers the suspend. Also catch and ignore the corresponding power button release notification (0xCF) to stop it from being reported as an "unknown event" in the logs. Link: https://bugzilla.kernel.org/show_bug.cgi?id=196115 Tested-by: Jérôme de Bretagne Signed-off-by: Jérôme de Bretagne Acked-By: Mario Limonciello [dvhart: minor coding style and commit message format fix] Signed-off-by: Darren Hart (VMware) Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/intel-hid.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 || -- cgit v1.2.3 From 4c05984459ec55056857bee32ecb01653c6b8f84 Mon Sep 17 00:00:00 2001 From: Maxime Bellengé Date: Sat, 23 Sep 2017 18:23:37 +0200 Subject: platform/x86: asus-wmi: Add lightbar led support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Asus laptops (ROG series for example) are provided with a lightbar behind the monitor. This patch make possible to switch it on and off. This lightbar works exactly like any other led. Signed-off-by: Maxime Bellengé [dvhart: fix commit message format and variable declaration order] Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/asus-wmi.c | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) 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: -- cgit v1.2.3 From 72e83204b7ec3d698189117d980dc7c15d07af8a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:49:59 -0500 Subject: platform/x86: dell-wmi: Label driver as handling notifications This driver serves the purpose of responding to WMI based notifications from the DELL_EVENT_GUID (9DBB5994-A997-11DA-B012-B622A1EF5492). Other GUIDs will be handled by separate drivers. Update the language used by this driver to avoid future confusion. Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index a6a5746ced9d..08b96f77f618 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3996,7 +3996,7 @@ S: Maintained F: Documentation/dcdbas.txt F: drivers/firmware/dcdbas.* -DELL WMI EXTRAS DRIVER +DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett M: Pali Rohár S: Maintained diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index f401ae463e9b..1f7959ff055c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -116,7 +116,7 @@ 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 -- cgit v1.2.3 From 9808f3628ce43fafbb6f8eb8d347f38ba9b275ba Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:50:01 -0500 Subject: platform/x86: dell-wmi: Do not match on descriptor GUID modalias The descriptor GUID is not used to indicate that WMI notifications in the dell-wmi driver work properly. As such a modalias should not be present that causes this driver to load on systems with this GUID. Signed-off-by: Mario Limonciello Reviewed-by: Pali Pohar Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-wmi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 28d9f8696081..1fbef560ca67 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -51,7 +51,6 @@ MODULE_LICENSE("GPL"); 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; -- cgit v1.2.3 From 7b4dd21630afa835a907cd030fd4e2af6849c6a9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:50:02 -0500 Subject: platform/x86: dell-smbios: Add pr_fmt definition to driver pr_fmt provides formatting to be used by the driver when displaying errors and messages. Signed-off-by: Mario Limonciello Reviewed-by: Pali Pohar Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 0a5723468bff..e9b1ca07c872 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -12,6 +12,7 @@ * 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 #include -- cgit v1.2.3 From b60ee4e0918db95a1e458ae160488368c45a0687 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:50:03 -0500 Subject: platform/x86: wmi: Sort include list The include list is randomly assembled right now. Sort in alphabetical order. Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 0765b1797d4c..e19b074df01d 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -33,17 +33,17 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include -#include -#include +#include #include +#include +#include #include -#include -#include #include #include -#include +#include +#include #include +#include ACPI_MODULE_NAME("wmi"); MODULE_AUTHOR("Carlos Corbacho"); -- cgit v1.2.3 From 303d1fcc0bf07129bc05a61632fad9f7064b671f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:50:04 -0500 Subject: platform/x86: wmi: Cleanup exit routine in reverse order of init The initialize routine is: * class -> bus -> platform The exit routine is: * platform -> class -> bus Fix the exit routine to be: * platform -> bus -> class Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index e19b074df01d..acbc2b02db3d 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1264,8 +1264,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); -- cgit v1.2.3 From 7b11e8989618581bc0226ad313264cdc05d48d86 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 26 Sep 2017 13:50:05 -0500 Subject: platform/x86: wmi: Destroy on cleanup rather than unregister device_create documentation says to cleanup using device_destroy Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index acbc2b02db3d..7a05843aff19 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1145,7 +1145,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 +1199,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, -- cgit v1.2.3 From cd6bf7741cf992306748db11e1888c741d16dd0f Mon Sep 17 00:00:00 2001 From: Ville Syrjälä Date: Mon, 18 Sep 2017 23:00:59 +0300 Subject: platform/x86: fujitsu-laptop: Don't oops when FUJ02E3 is not presnt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My Fujitsu-Siemens Lifebook S6120 doesn't have the FUJ02E3 device, but it does have FUJ02B1. That means we do register the backlight device (and it even seems to work), but the code will oops as soon as we try to set the backlight brightness because it's trying to call call_fext_func() with a NULL device. Let's just skip those function calls when the FUJ02E3 device is not present. Cc: Jonathan Woithe Cc: Andy Shevchenko Signed-off-by: Ville Syrjälä Cc: # 4.13.x Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/fujitsu-laptop.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 85de30f93a9c..56a8195096a2 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -254,10 +254,12 @@ static int bl_update_status(struct backlight_device *b) { struct acpi_device *device = bl_get_data(b); - if (b->props.power == FB_BLANK_POWERDOWN) - call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x3); - else - call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x0); + if (fext) { + if (b->props.power == FB_BLANK_POWERDOWN) + call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x3); + else + call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x0); + } return set_lcd_level(device, b->props.brightness); } -- cgit v1.2.3 From dda3ec0aa631d15b12a42438d23336354037e108 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 15 Sep 2017 15:20:49 +0200 Subject: platform/x86: thinkpad_acpi: Implement tablet mode using GMMS method Many thinkpad laptops and convertibles provide the GMMS method to resolve how far the laptop has been opened and whether it has been converted into tablet mode. This allows reporting a more precise tablet mode state to userspace. The current implementation only reports a summarized tablet mode state which is triggered as soon as the input devices become unusable as they are folded away from the display. This will work on all models where the CMMD method was used previously and it may also work in other cases. Thanks to Peter Zhang of Lenovo for providing information on how to use the GMMS method to query the tablet mode. Signed-off-by: Benjamin Berg Cc: Peter FP1 Zhang Cc: Lyude Paul Acked-by: Henrique de Moraes Holschuh Reviewed-by: Lyude Paul Signed-off-by: Andy Shevchenko --- drivers/platform/x86/thinkpad_acpi.c | 132 +++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 2242d6035d9e..91fab1a13a6d 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) -- cgit v1.2.3 From d7ca5ebf2493ddd610d07cc1c6c48de8fabb0496 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Mon, 4 Sep 2017 22:37:21 -0700 Subject: platform/x86: intel_pmc_ipc: Use devm_* calls in driver probe function This patch cleans up unnecessary free/alloc calls in ipc_plat_probe(), ipc_pci_probe() and ipc_plat_get_res() functions by using devm_* calls. This patch also adds proper error handling for failure cases in ipc_pci_probe() function. Signed-off-by: Kuppuswamy Sathyanarayanan [andy: fixed style issues, missed devm_free_irq(), removed unnecessary log message] Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_ipc.c | 94 +++++++++++------------------------- 1 file changed, 28 insertions(+), 66 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index bb792a52248b..751b1212d01c 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -480,52 +480,39 @@ static irqreturn_t ioc(int irq, void *dev_id) static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - resource_size_t pci_resource; + struct intel_pmc_ipc_dev *pmc = &ipcdev; int ret; - int len; - ipcdev.dev = &pci_dev_get(pdev)->dev; - ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ; + /* Only one PMC is supported */ + if (pmc->dev) + return -EBUSY; - ret = pci_enable_device(pdev); + pmc->irq_mode = IPC_TRIGGER_MODE_IRQ; + + ret = pcim_enable_device(pdev); if (ret) return ret; - ret = pci_request_regions(pdev, "intel_pmc_ipc"); + ret = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev)); if (ret) return ret; - pci_resource = pci_resource_start(pdev, 0); - len = pci_resource_len(pdev, 0); - if (!pci_resource || !len) { - dev_err(&pdev->dev, "Failed to get resource\n"); - return -ENOMEM; - } + init_completion(&pmc->cmd_complete); - init_completion(&ipcdev.cmd_complete); + pmc->ipc_base = pcim_iomap_table(pdev)[0]; - if (request_irq(pdev->irq, ioc, 0, "intel_pmc_ipc", &ipcdev)) { + ret = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_pmc_ipc", + pmc); + if (ret) { dev_err(&pdev->dev, "Failed to request irq\n"); - return -EBUSY; + return ret; } - ipcdev.ipc_base = ioremap_nocache(pci_resource, len); - if (!ipcdev.ipc_base) { - dev_err(&pdev->dev, "Failed to ioremap ipc base\n"); - free_irq(pdev->irq, &ipcdev); - ret = -ENOMEM; - } + pmc->dev = &pdev->dev; - return ret; -} + pci_set_drvdata(pdev, pmc); -static void ipc_pci_remove(struct pci_dev *pdev) -{ - free_irq(pdev->irq, &ipcdev); - pci_release_regions(pdev); - pci_dev_put(pdev); - iounmap(ipcdev.ipc_base); - ipcdev.dev = NULL; + return 0; } static const struct pci_device_id ipc_pci_ids[] = { @@ -540,7 +527,6 @@ static struct pci_driver ipc_pci_driver = { .name = "intel_pmc_ipc", .id_table = ipc_pci_ids, .probe = ipc_pci_probe, - .remove = ipc_pci_remove, }; static ssize_t intel_pmc_ipc_simple_cmd_store(struct device *dev, @@ -850,17 +836,12 @@ static int ipc_plat_get_res(struct platform_device *pdev) return -ENXIO; } size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE; + res->end = res->start + size - 1; + + addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(addr)) + return PTR_ERR(addr); - if (!request_mem_region(res->start, size, pdev->name)) { - dev_err(&pdev->dev, "Failed to request ipc resource\n"); - return -EBUSY; - } - addr = ioremap_nocache(res->start, size); - if (!addr) { - dev_err(&pdev->dev, "I/O memory remapping failed\n"); - release_mem_region(res->start, size); - return -ENOMEM; - } ipcdev.ipc_base = addr; ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET; @@ -917,7 +898,6 @@ MODULE_DEVICE_TABLE(acpi, ipc_acpi_ids); static int ipc_plat_probe(struct platform_device *pdev) { - struct resource *res; int ret; ipcdev.dev = &pdev->dev; @@ -939,11 +919,11 @@ static int ipc_plat_probe(struct platform_device *pdev) ret = ipc_create_pmc_devices(); if (ret) { dev_err(&pdev->dev, "Failed to create pmc devices\n"); - goto err_device; + return ret; } - if (request_irq(ipcdev.irq, ioc, IRQF_NO_SUSPEND, - "intel_pmc_ipc", &ipcdev)) { + if (devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND, + "intel_pmc_ipc", &ipcdev)) { dev_err(&pdev->dev, "Failed to request irq\n"); ret = -EBUSY; goto err_irq; @@ -960,40 +940,22 @@ static int ipc_plat_probe(struct platform_device *pdev) return 0; err_sys: - free_irq(ipcdev.irq, &ipcdev); + devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); err_irq: platform_device_unregister(ipcdev.tco_dev); platform_device_unregister(ipcdev.punit_dev); platform_device_unregister(ipcdev.telemetry_dev); -err_device: - iounmap(ipcdev.ipc_base); - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_IPC_INDEX); - if (res) { - release_mem_region(res->start, - PLAT_RESOURCE_IPC_SIZE + - PLAT_RESOURCE_GCR_SIZE); - } + return ret; } static int ipc_plat_remove(struct platform_device *pdev) { - struct resource *res; - sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group); - free_irq(ipcdev.irq, &ipcdev); + devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); platform_device_unregister(ipcdev.tco_dev); platform_device_unregister(ipcdev.punit_dev); platform_device_unregister(ipcdev.telemetry_dev); - iounmap(ipcdev.ipc_base); - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_IPC_INDEX); - if (res) { - release_mem_region(res->start, - PLAT_RESOURCE_IPC_SIZE + - PLAT_RESOURCE_GCR_SIZE); - } ipcdev.dev = NULL; return 0; } -- cgit v1.2.3 From ff74972e966bfc586cea116f976866858d1b0fec Mon Sep 17 00:00:00 2001 From: Kai Heng Feng Date: Mon, 2 Oct 2017 11:56:00 +0800 Subject: platform/x86: peaq-wmi: Blacklist Lenovo ideapad 700-15ISK peaq-wmi on Lenovo ideapad 700-15ISK keeps sending KEY_SOUND, which makes user's repeated keys gets interrupted. The system does not have Dolby button, let's blacklist it. BugLink: https://bugs.launchpad.net/bugs/1720219 Signed-off-by: Kai-Heng Feng Signed-off-by: Andy Shevchenko --- drivers/platform/x86/peaq-wmi.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index bc98ef95514a..e09f37cdeb3c 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -64,9 +65,22 @@ static void peaq_wmi_poll(struct input_polled_dev *dev) } } +static const struct dmi_system_id peaq_blacklist[] __initconst = { + { + /* Lenovo ideapad 700-15ISK does not have Dolby button */ + .ident = "Lenovo ideapad 700-15ISK", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "80RU"), + }, + }, + {} +}; + static int __init peaq_wmi_init(void) { - if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) + if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID) || + dmi_check_system(peaq_blacklist)) return -ENODEV; peaq_poll_dev = input_allocate_polled_device(); -- cgit v1.2.3 From dfea7e18276c6a702860c927298e15d4a4c2569d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 8 Oct 2017 21:04:43 +0300 Subject: platform/x86: peaq-wmi: Revert Blacklist Lenovo ideapad 700-15ISK In favour of new approach this reverts commit ff74972e966bfc586cea116f976866858d1b0fec. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/peaq-wmi.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index e09f37cdeb3c..bc98ef95514a 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -65,22 +64,9 @@ static void peaq_wmi_poll(struct input_polled_dev *dev) } } -static const struct dmi_system_id peaq_blacklist[] __initconst = { - { - /* Lenovo ideapad 700-15ISK does not have Dolby button */ - .ident = "Lenovo ideapad 700-15ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "80RU"), - }, - }, - {} -}; - static int __init peaq_wmi_init(void) { - if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID) || - dmi_check_system(peaq_blacklist)) + if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) return -ENODEV; peaq_poll_dev = input_allocate_polled_device(); -- cgit v1.2.3 From 3b95206110a2c13076c3a7fa8ddeae36c2dbcf42 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 5 Oct 2017 20:04:04 +0200 Subject: platform/x86: peaq-wmi: Add DMI check before binding to the WMI interface It seems that the WMI GUID used by the PEAQ 2-in-1 WMI hotkeys is not as unique as a GUID should be and is used on some other devices too. This is causing spurious key-press reports on these other devices. This commits adds a DMI check to the PEAQ 2-in-1 WMI hotkeys driver to ensure that it is actually running on a PEAQ 2-in-1, fixing the spurious key-presses on these other devices. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1497861 BugLink: https://bugzilla.suse.com/attachment.cgi?id=743182 Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/peaq-wmi.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index bc98ef95514a..67fa3fa32011 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -64,8 +65,22 @@ 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[] = { + { + .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,6 +101,9 @@ static int __init peaq_wmi_init(void) static void __exit peaq_wmi_exit(void) { + if (!dmi_check_system(peaq_dmi_table)) + return; + if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) return; -- cgit v1.2.3 From 687d25a2bae98b32534d2119419ed1eea6e53bbb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 5 Oct 2017 16:31:47 +0200 Subject: MAINTAINERS: Add entry for the PEAQ WMI hotkeys driver Add an entry to make myself the maintainer of the PEAQ WMI hotkeys driver. Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 08b96f77f618..037a8d2a7f4e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10521,6 +10521,12 @@ S: Maintained F: crypto/pcrypt.c F: include/crypto/pcrypt.h +PEAQ WMI HOTKEYS DRIVER +M: Hans de Goede +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/peaq-wmi.c + PER-CPU MEMORY ALLOCATOR M: Tejun Heo M: Christoph Lameter -- cgit v1.2.3 From f5b33d94c1eca23802e3ac76b0788ba59e44a481 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 26 Jan 2017 17:16:26 +0200 Subject: platform/x86: intel_ips: Simplify error handling via devres API Use devm_ and pcim_ functions to make error handling simpler and code smaller and tidier. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 62 ++++++++++------------------------------ 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 58dcee562d64..063d9a1624b4 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -296,7 +296,7 @@ static struct ips_mcp_limits ips_ulv_limits = { struct ips_driver { struct pci_dev *dev; - void *regmap; + void __iomem *regmap; struct task_struct *monitor; struct task_struct *adjust; struct dentry *debug_root; @@ -1517,62 +1517,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); + spin_lock_init(&ips->turbo_status_lock); ips->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,8 +1585,7 @@ 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; } /* @@ -1615,7 +1597,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) ips); if (ret) { dev_err(&dev->dev, "request irq failed, aborting\n"); - goto error_unmap; + return ret; } /* Enable aux, hot & critical interrupts */ @@ -1673,12 +1655,6 @@ 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); return ret; } @@ -1714,22 +1690,14 @@ static void ips_remove(struct pci_dev *dev) 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); -- cgit v1.2.3 From 8b8bd6d255c960e1a935837c26fbc598e91eba24 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Oct 2017 11:28:01 +0300 Subject: platform/x86: intel_ips: Switch to new PCI IRQ allocation API This makes code cleaner. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 063d9a1624b4..6f6900065bc2 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -297,6 +297,8 @@ static struct ips_mcp_limits ips_ulv_limits = { struct ips_driver { struct pci_dev *dev; void __iomem *regmap; + int irq; + struct task_struct *monitor; struct task_struct *adjust; struct dentry *debug_root; @@ -1592,9 +1594,13 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) * 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"); return ret; @@ -1654,7 +1660,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); + free_irq(ips->irq, ips); + pci_free_irq_vectors(dev); return ret; } @@ -1685,7 +1692,8 @@ 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) -- cgit v1.2.3 From 512f4665d839e1b4d5ebc8e446a4b948cbac681d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Oct 2017 10:46:40 +0300 Subject: platform/x86: intel_ips: Use PCI_VDEVICE() macro Intel vendor ID is defined globally, thus we may use PCI_VDEVICE(). Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 6f6900065bc2..489ff6fff9f9 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -1482,8 +1482,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, } }; -- cgit v1.2.3 From d2fa170a2543bfd0c3d91ae0a6f808a5becda5c9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Oct 2017 10:55:40 +0300 Subject: platform/x86: intel_ips: Keep pointer to struct device ...instead of keeping pointer to struct pci_dev. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 51 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 489ff6fff9f9..27e70637e40b 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -295,7 +295,7 @@ static struct ips_mcp_limits ips_ulv_limits = { }; struct ips_driver { - struct pci_dev *dev; + struct device *dev; void __iomem *regmap; int irq; @@ -596,7 +596,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; } @@ -651,8 +651,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; } @@ -771,7 +770,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 @@ -818,7 +817,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; } @@ -978,7 +977,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); @@ -1099,7 +1098,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; @@ -1121,7 +1121,7 @@ static int ips_monitor(void *data) del_timer_sync(&timer); destroy_timer_on_stack(&timer); - dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n"); + dev_dbg(ips->dev, "ips-monitor thread stopped\n"); return 0; } @@ -1130,17 +1130,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) @@ -1148,7 +1148,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); @@ -1177,8 +1177,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) { @@ -1216,8 +1216,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); } @@ -1332,8 +1332,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; } @@ -1347,8 +1346,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; } @@ -1375,7 +1373,7 @@ 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"); + dev_info(ips->dev, "Non-IPS CPU detected.\n"); goto out; } @@ -1397,7 +1395,7 @@ 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"); + dev_info(ips->dev, "No CPUID match found.\n"); goto out; } @@ -1406,7 +1404,8 @@ 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; } @@ -1461,7 +1460,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); } @@ -1523,7 +1522,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) return -ENOMEM; spin_lock_init(&ips->turbo_status_lock); - ips->dev = dev; + ips->dev = &dev->dev; ips->limits = ips_detect_cpu(ips); if (!ips->limits) { -- cgit v1.2.3 From b8cc799ddcbde8bf970804ce5f5546af2abc81bc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Oct 2017 14:10:03 +0300 Subject: platform/x86: intel_ips: Remove unneeded fields and label There are fields in the struct ips_mcp_limits which are not used anywhere and a label which we may get rid of. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 27e70637e40b..9f5afdd123bb 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -259,8 +259,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; @@ -1374,7 +1372,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) { dev_info(ips->dev, "Non-IPS CPU detected.\n"); - goto out; + return NULL; } rdmsrl(IA32_MISC_ENABLE, misc_en); @@ -1396,7 +1394,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) limits = &ips_ulv_limits; else { dev_info(ips->dev, "No CPUID match found.\n"); - goto out; + return NULL; } rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power); @@ -1410,7 +1408,6 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) limits->core_power_limit = (tdp / 8) * 1000; } -out: return limits; } -- cgit v1.2.3 From 8f21b74e7cf87af187f628ceb29419a1303ef008 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 17 Jul 2017 16:05:41 -0400 Subject: platform/x86: intel_ips: Remove FSF address from GPL notice This patch removes the FSF address from the GPL notice to fix a checkpatch.pl CHECK message. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 4 ---- drivers/platform/x86/intel_ips.h | 4 ---- 2 files changed, 8 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 9f5afdd123bb..680ab4fd7087 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". * 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". */ -- cgit v1.2.3 From 485f2a5811f9d87d5b634d068b5c7574fd1bb09e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 4 Oct 2017 15:30:12 +0200 Subject: platform/x86: silead_dmi: Add entry for the Chuwi Hi8 Pro tablet Add touchscreen platform data for the Chuwi Hi8 Pro tablet. Signed-off-by: Hans de Goede Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/silead_dmi.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index 1157a7b646d6..a88823086406 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -136,6 +136,19 @@ 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"), + { } +}; + +static const struct silead_ts_dmi_data chuwi_hi8_pro_data = { + .acpi_name = "MSSL1680:00", + .properties = chuwi_hi8_pro_props, +}; + static const struct dmi_system_id silead_ts_dmi_table[] = { { /* CUBE iwork8 Air */ @@ -219,6 +232,14 @@ 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"), + }, + }, { }, }; -- cgit v1.2.3 From 36c282b31f648b31373be0dc8c2475c22fcab02a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 5 Oct 2017 11:42:11 +0100 Subject: platform/x86: mlx-platform: make a couple of structures static The structures mlxplat_dev and mlxplat_hotplug are local to the source and do not need to be in global scope, so make them static. Cleans up sparse warnings: symbol 'mlxplat_dev' was not declared. Should it be static? symbol 'mlxplat_hotplug' was not declared. Should it be static? Signed-off-by: Colin Ian King Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/mlx-platform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { -- cgit v1.2.3 From f0d962392abc5b41d66ac4f7b723c82ba4f80582 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 11 Oct 2017 10:30:10 +0100 Subject: platform/x86: intel_cht_int33fe: make a couple of local functions static The functions cht_int33fe_check_for_max17047 and cht_int33fe_find_max17047 are local to the source and do not need to be in global scope, so make them static. Cleans up sparse warnings: symbol 'cht_int33fe_check_for_max17047' was not declared. Should it be static? symbol 'cht_int33fe_find_max17047' was not declared. Should it be static? Signed-off-by: Colin Ian King Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_cht_int33fe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c index 94716b2fbc00..286c6207765d 100644 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -42,7 +42,7 @@ struct cht_int33fe_data { * These helpers are used to work around this by checking if an i2c-client * for the max17047 has already been registered. */ -int cht_int33fe_check_for_max17047(struct device *dev, void *data) +static int cht_int33fe_check_for_max17047(struct device *dev, void *data) { struct i2c_client **max17047 = data; struct acpi_device *adev; @@ -62,7 +62,7 @@ int cht_int33fe_check_for_max17047(struct device *dev, void *data) return 1; } -struct i2c_client *cht_int33fe_find_max17047(void) +static struct i2c_client *cht_int33fe_find_max17047(void) { struct i2c_client *max17047 = NULL; -- cgit v1.2.3 From 2cb81ac0787774e63fa48ca1a10c667f0d091a8f Mon Sep 17 00:00:00 2001 From: Rajneesh Bhardwaj Date: Tue, 10 Oct 2017 15:35:17 +0530 Subject: platform/x86: intel_telemetry: Fix load failure info Intel Telemetry driver depends on IPC1 interface. If IPC1 interface is disabled on a given platform by the system firmware, the driver does not load but prints misleading information in the dmesg logs. Signed-off-by: Rajneesh Bhardwaj Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_telemetry_pltdrv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index e0424d5a795a..136507a0d391 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -1234,7 +1234,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); } -- cgit v1.2.3 From 999c8397df590aad2ed8e67250ccf4816484153b Mon Sep 17 00:00:00 2001 From: Rajneesh Bhardwaj Date: Tue, 10 Oct 2017 15:35:18 +0530 Subject: platform/x86: intel_telemetry: Fix typos Telemetry word is misspelled several times in this file as Telemtry. This fixes the spelling mistake and folds in another minor typo. Signed-off-by: Rajneesh Bhardwaj Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_telemetry_pltdrv.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index 136507a0d391..3daf92e1282f 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -256,7 +256,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 +659,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 +685,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 +822,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 +885,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 +1195,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 +1210,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; } -- cgit v1.2.3 From a5e50220edbdd1ec8912c191a0f5272d629743bf Mon Sep 17 00:00:00 2001 From: Rajneesh Bhardwaj Date: Tue, 10 Oct 2017 15:35:19 +0530 Subject: platform/x86: intel_telemetry: cleanup redundant headers Removes unnecessary header files included in the driver and sorts the remaining ones in the alphabetical order. Signed-off-by: Rajneesh Bhardwaj Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_telemetry_core.c | 3 +-- drivers/platform/x86/intel_telemetry_debugfs.c | 8 +++----- drivers/platform/x86/intel_telemetry_pltdrv.c | 8 -------- 3 files changed, 4 insertions(+), 15 deletions(-) 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 -#include #include +#include #include diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index 74e518dd237c..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 -#include -#include #include -#include +#include #include -#include +#include #include +#include #include #include diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index 3daf92e1282f..678abf2e9e92 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -17,14 +17,6 @@ * The accumulated results are fetched from SRAM. */ #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -- cgit v1.2.3 From e3075fd6f80c9a6ce678fa2f5bbdb2824c506c6c Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Sat, 7 Oct 2017 15:19:51 -0700 Subject: platform/x86: intel_pmc_ipc: Use spin_lock to protect GCR updates Currently, update_no_reboot_bit() function implemented in this driver uses mutex_lock() to protect its register updates. But this function is called with in atomic context in iTCO_wdt_start() and iTCO_wdt_stop() functions in iTCO_wdt.c driver, which in turn causes "sleeping into atomic context" issue. This patch fixes this issue by replacing the mutex_lock() with spin_lock() to protect the GCR read/write/update APIs. Fixes: 9d855d4 ("platform/x86: intel_pmc_ipc: Fix iTCO_wdt GCS memory mapping failure") Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_ipc.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index 751b1212d01c..e03fa31446ca 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -131,6 +132,7 @@ static struct intel_pmc_ipc_dev { /* gcr */ void __iomem *gcr_mem_base; bool has_gcr_regs; + spinlock_t gcr_lock; /* punit */ struct platform_device *punit_dev; @@ -225,17 +227,17 @@ int intel_pmc_gcr_read(u32 offset, u32 *data) { int ret; - mutex_lock(&ipclock); + spin_lock(&ipcdev.gcr_lock); ret = is_gcr_valid(offset); if (ret < 0) { - mutex_unlock(&ipclock); + spin_unlock(&ipcdev.gcr_lock); return ret; } *data = readl(ipcdev.gcr_mem_base + offset); - mutex_unlock(&ipclock); + spin_unlock(&ipcdev.gcr_lock); return 0; } @@ -255,17 +257,17 @@ int intel_pmc_gcr_write(u32 offset, u32 data) { int ret; - mutex_lock(&ipclock); + spin_lock(&ipcdev.gcr_lock); ret = is_gcr_valid(offset); if (ret < 0) { - mutex_unlock(&ipclock); + spin_unlock(&ipcdev.gcr_lock); return ret; } writel(data, ipcdev.gcr_mem_base + offset); - mutex_unlock(&ipclock); + spin_unlock(&ipcdev.gcr_lock); return 0; } @@ -287,7 +289,7 @@ int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val) u32 new_val; int ret = 0; - mutex_lock(&ipclock); + spin_lock(&ipcdev.gcr_lock); ret = is_gcr_valid(offset); if (ret < 0) @@ -309,7 +311,7 @@ int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val) } gcr_ipc_unlock: - mutex_unlock(&ipclock); + spin_unlock(&ipcdev.gcr_lock); return ret; } EXPORT_SYMBOL_GPL(intel_pmc_gcr_update); @@ -489,6 +491,8 @@ static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pmc->irq_mode = IPC_TRIGGER_MODE_IRQ; + spin_lock_init(&ipcdev.gcr_lock); + ret = pcim_enable_device(pdev); if (ret) return ret; @@ -903,6 +907,7 @@ static int ipc_plat_probe(struct platform_device *pdev) ipcdev.dev = &pdev->dev; ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ; init_completion(&ipcdev.cmd_complete); + spin_lock_init(&ipcdev.gcr_lock); ipcdev.irq = platform_get_irq(pdev, 0); if (ipcdev.irq < 0) { -- cgit v1.2.3 From cfab22c01258389578358eb93cb8e973307df6f2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 27 Oct 2017 17:25:50 +0300 Subject: platform/x86: intel_telemetry: Add needed inclusion linux/io.h defines readq() and ioremap_nocache() / iounmap() functions. If not included, the build fails: intel_telemetry_pltdrv.c:1165:31: warning: assignment makes pointer from integer without a cast [-Wint-conversion] intel_telemetry_pltdrv.c:1165:33: error: implicit declaration of function 'ioremap_nocache' [-Werror=implicit-function-declaration] intel_telemetry_pltdrv.c:1202:3: error: implicit declaration of function 'iounmap' [-Werror=implicit-function-declaration] intel_telemetry_pltdrv.c:900:20: error: implicit declaration of function 'readq' [-Werror=implicit-function-declaration] Fix this by including linux/io.h. Reported-by: kbuild test robot Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_telemetry_pltdrv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/platform/x86/intel_telemetry_pltdrv.c b/drivers/platform/x86/intel_telemetry_pltdrv.c index 678abf2e9e92..2f889d6c270e 100644 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ b/drivers/platform/x86/intel_telemetry_pltdrv.c @@ -16,6 +16,8 @@ * It used the PUNIT and PMC IPC interfaces for configuring the counters. * The accumulated results are fetched from SRAM. */ + +#include #include #include -- cgit v1.2.3 From 026930bc06e95b93c3200036ab3b44b783dc6f9d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 27 Oct 2017 17:30:16 +0300 Subject: platform/x86: intel_telemetry: Remove useless default in Kconfig 'default n' is a default behaviour of Kconfig options. So, remove explicit line from Kconfig. Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 1f7959ff055c..0fdf68865c5e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1101,7 +1101,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 -- cgit v1.2.3 From 5520437bebf7c65507a12bb111eba79f35efba1d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sat, 21 Oct 2017 09:52:00 -0700 Subject: platform/x86: intel_turbo_max_3: Add Skylake platform Ev Kontsevoy reported that he can't see the presence of "/proc/sys/kernel/sched_itmt_enabled" on i9-7900x with Asrock x299 Taichi system even if he enabled "Turbo 3.0" in the BIOS. The problem is that even if one core max is 200MHz more than others, the current implementation couldn't enumerate that with the way the system is configured. The system by default configured for legacy mode (no HWP or speed shift technology), in this mode only way we can enumerate via the mail box interface as implemented in this driver. We were planing to only use this driver for Broadwell, but we need to extend this because some Skylake system has same issue as Braodwell systems. On this system BIOS allows to change to HWP mode, where we expect that we can enumerate favored core with ACPI-CPPC. But on this system the core priority is 0xff for all cores in CPPC object. So this is not an option. Hence this change allows Skylake systems to be enumerate favored core similar to Broadwell in legacy mode. Reported-and-tested-by: Ev Kontsevoy Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_turbo_max_3.c | 1 + 1 file changed, 1 insertion(+) 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), {} }; -- cgit v1.2.3 From 163ca80013aafb6dc9cb295de3db7aeab9ab43f8 Mon Sep 17 00:00:00 2001 From: Osama Khan Date: Sat, 21 Oct 2017 10:42:21 +0000 Subject: platform/x86: hp_accel: Add quirk for HP ProBook 440 G4 Added support for HP ProBook 440 G4 laptops by including the accelerometer orientation quirk for that device. Testing was performed based on the axis orientation guidelines here: https://www.kernel.org/doc/Documentation/misc-devices/lis3lv02d which states "If the left side is elevated, X increases (becomes positive)". When tested, on lifting the left edge, x values became increasingly negative thus indicating an inverted x-axis on the installed lis3lv02d chip. This was compensated by adding an entry for this device in hp_accel.c specifying the quirk as x_inverted. The patch was tested on a ProBook 440 G4 device and x-axis as well as y and z-axis values are now generated as per spec. Signed-off-by: Osama Khan Signed-off-by: Andy Shevchenko --- drivers/platform/x86/hp_accel.c | 1 + 1 file changed, 1 insertion(+) 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), -- cgit v1.2.3 From c7b3e98e4d0ada6aeaa8cc37a7e9ab16e7b3a4fe Mon Sep 17 00:00:00 2001 From: Michał Kępień Date: Wed, 25 Oct 2017 06:29:46 +0200 Subject: platform/x86: fujitsu-laptop: Fix radio LED detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Radio LED detection method implemented in commit 4f62568c1fcf ("fujitsu-laptop: Support radio LED") turned out to be incorrect as it causes a radio LED to be erroneously detected on a Fujitsu Lifebook E751 which has a slide switch (and thus no radio LED). Use bit 17 of flags_supported (the value returned by method S000 of ACPI device FUJ02E3) to determine whether a radio LED is present as it seems to be a more reliable indicator, based on comparing DSDT tables of four Fujitsu Lifebook models (E744, E751, S7110, S8420). Fixes: 4f62568c1fcf ("fujitsu-laptop: Support radio LED") Reported-by: Heinrich Siebmanns Signed-off-by: Michał Kępień Tested-by: Heinrich Siebmanns Signed-off-by: Andy Shevchenko --- drivers/platform/x86/fujitsu-laptop.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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; -- cgit v1.2.3 From b231669ca350d98cd126841627d3f083046a81ef Mon Sep 17 00:00:00 2001 From: Philipp Hug Date: Tue, 24 Oct 2017 16:32:22 +0200 Subject: platform/x86: ideapad-laptop: Add Lenovo Yoga 920-13IKB to no_hw_rfkill dmi list The Lenovo Yoga 920-13IKB does not have a hw rfkill switch, and trying to read the hw rfkill switch through the ideapad module causes it to always report as blocked. This commit adds the Lenovo Yoga 920-13IKB to the no_hw_rfkill dmi list, fixing the WiFI breakage. Signed-off-by: Philipp Hug Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/ideapad-laptop.c | 7 +++++++ 1 file changed, 7 insertions(+) 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"), + }, + }, {} }; -- cgit v1.2.3 From d6fa71f1c003fb2bc824276bb424a4171f9a717f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 30 Oct 2017 14:07:37 +0100 Subject: platform/x86: peaq_wmi: Fix missing terminating entry for peaq_dmi_table Add missing terminating entry to peaq_dmi_table. Fixes: 3b95206110a2 ("platform/x86: peaq-wmi: Add DMI check before ...") Cc: stable@vger.kernel.org Reported-by: Fengguang Wu Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/peaq-wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index 67fa3fa32011..2da48ecc90c1 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -73,6 +73,7 @@ static const struct dmi_system_id peaq_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), }, }, + {} }; static int __init peaq_wmi_init(void) -- cgit v1.2.3 From f6c7b8031d7e177c837c23d1065204b697fb54b7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 30 Oct 2017 14:07:38 +0100 Subject: platform/x86: peaq-wmi: Remove unnecessary checks from peaq_wmi_exit peaq_wmi_exit will only ever get called if peaq_wmi_init succeeds, so there is no need to repeat the checks from peaq_wmi_init. Suggested-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/peaq-wmi.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index 2da48ecc90c1..9b9e1f39bbfb 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -66,7 +66,7 @@ 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[] = { +static const struct dmi_system_id peaq_dmi_table[] __initconst = { { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), @@ -102,12 +102,6 @@ static int __init peaq_wmi_init(void) static void __exit peaq_wmi_exit(void) { - if (!dmi_check_system(peaq_dmi_table)) - return; - - if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) - return; - input_unregister_polled_device(peaq_poll_dev); } -- cgit v1.2.3 From 5c24c05efa4800bbe42de0ad3e752c5fd75fa351 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 19 Oct 2017 09:44:14 +0200 Subject: platform/x86: silead_dmi: Fix GP-electronic T701 entry The GP-electronic T701 has its LCD panel mounted upside-down, initially my plan was to fix this by transparently rotating the image in the i915 driver (my "drm/i915: Deal with upside-down mounted LCD" patch), but that approach has been rejected instead the kernel will now export a "panel orientation" property on the drm-connector for the panel and let userspace deal with it. But userspace expects the touchscreen coordinates to match the panel coordinates *before* applying any rotation, so now that we no longer hide the upside-down-ness of the LCD panel from userspace the coordinates being generated are wrong and we need to apply a rotation of 180 degrees to the coordinates to fix this. Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/silead_dmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index a88823086406..fadfce9b3f5f 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -83,6 +83,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"), { } -- cgit v1.2.3 From 3fcf2b2a2529e268c537d8ad9dac5987ebc8a2f7 Mon Sep 17 00:00:00 2001 From: Sergey Tshovrebov Date: Fri, 27 Oct 2017 12:27:07 +0200 Subject: platform/x86: silead_dmi: Add entry for the Digma e200 tablet Add touchscreen platform data for the Digma e200 tablet. Signed-off-by: Sergey Tshovrebov Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/silead_dmi.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index fadfce9b3f5f..5af3379b0573 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -151,6 +151,22 @@ static const struct silead_ts_dmi_data chuwi_hi8_pro_data = { .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 */ @@ -242,6 +258,15 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { 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"), + }, + }, { }, }; -- cgit v1.2.3 From 455e027f33a03ddffe82c36f430a4d173e840b75 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 11 Oct 2017 11:41:21 +0200 Subject: platform/x86: intel_cht_int33fe: Update fusb302 type string, add properties The fusb302 driver as merged in staging uses "typec_fusb302" as i2c-id rather then just "fusb302" and needs us to set a number of device- properties, adjust the intel_cht_int33fe driver accordingly. One of the properties set is max-snk-mv which makes the fusb302 driver negotiate up to 12V charging voltage, which is a bad idea on boards which are not setup to handle this, so this commit also adds 2 extra sanity checks to make sure that the expected Whiskey Cove PMIC + TI bq24292i charger combo, which can handle 12V, is present. Signed-off-by: Hans de Goede Acked-by: Andy Shevchenko Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 6 ++++- drivers/platform/x86/intel_cht_int33fe.c | 44 +++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0fdf68865c5e..87c8c36efce4 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -806,7 +806,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. @@ -817,6 +817,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 diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c index 286c6207765d..380ef7ec094f 100644 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #define EXPECTED_PTYPE 4 @@ -77,12 +78,21 @@ 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; @@ -101,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) { @@ -127,6 +165,7 @@ static int cht_int33fe_probe(struct i2c_client *client) } 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) @@ -134,7 +173,9 @@ static int cht_int33fe_probe(struct i2c_client *client) } 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); @@ -142,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); -- cgit v1.2.3 From f35a8efe2c34496eaf45f8a9bd5bb7625d34bb5c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:22 -0500 Subject: platform/x86: dell-smbios: Prefix class/select with cmd_ Later on these structures will be brought up to userspace. the word "class" is a reserved word in c++ and this will prevent uapi headers from being included directly in c++ programs. To make life easier on these applications, prepare the change now. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios.c | 4 ++-- drivers/platform/x86/dell-smbios.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index e9b1ca07c872..ffc174638aa4 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -86,8 +86,8 @@ void dell_smbios_send_request(int class, int select) command.ebx = virt_to_phys(buffer); command.ecx = 0x42534931; - buffer->class = class; - buffer->select = select; + buffer->cmd_class = class; + buffer->cmd_select = select; dcdbas_smi_request(&command); } diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index 45cbc2292cd3..742dd8bd66b9 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -22,8 +22,8 @@ struct notifier_block; * system management mode, hence the volatiles */ struct calling_interface_buffer { - u16 class; - u16 select; + u16 cmd_class; + u16 cmd_select; volatile u32 input[4]; volatile u32 output[4]; } __packed; -- cgit v1.2.3 From 722c856d46c6ca74a246b54a72f14751fec01aae Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:23 -0500 Subject: platform/x86: wmi: Add new method wmidev_evaluate_method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drivers properly using the wmibus can pass their wmi_device pointer rather than the GUID back to the WMI bus to evaluate the proper methods. Any "new" drivers added that use the WMI bus should use this rather than the old wmi_evaluate_method that would take the GUID. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 28 ++++++++++++++++++++++++---- include/linux/wmi.h | 6 ++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 7a05843aff19..4d73a87c2ddf 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -200,6 +200,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; @@ -209,9 +231,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 +266,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) diff --git a/include/linux/wmi.h b/include/linux/wmi.h index cd0d7734dc49..2cd10c3b89e9 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -26,6 +26,12 @@ 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); -- cgit v1.2.3 From d0bf42d83e9492ffc80108b0913f596dbbd220c0 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:24 -0500 Subject: platform/x86: dell-wmi: increase severity of some failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a lot of error checking in place for the format of the WMI descriptor buffer, but some of the potentially raised issues should be considered critical failures. If the buffer size or header don't match, this is a good indication that the buffer format changed in a way that the rest of the data should not be relied upon. For the remaining data set vectors, continue to notate a warning in undefined results, but as those are fields that the descriptor intended to refer to other applications, don't fail if they're new values. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-wmi.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 1fbef560ca67..2cfaaa8faf0a 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -657,17 +657,18 @@ static int dell_wmi_check_descriptor_buffer(struct wmi_device *wdev) dev_err(&wdev->dev, "Dell descriptor buffer has invalid length (%d)\n", obj->buffer.length); - if (obj->buffer.length < 16) { - ret = -EINVAL; - goto out; - } + 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", + if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%*ph)\n", 8, buffer); + ret = -EINVAL; + goto out; + } if (buffer[2] != 0 && buffer[2] != 1) dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%d)\n", -- cgit v1.2.3 From 7e6dcbebcbc5d8cd7d9e564d9cfbf523c2fbcb68 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:25 -0500 Subject: platform/x86: dell-wmi: clean up wmi descriptor check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some cases the wrong type was used for errors and checks can be done more cleanly. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Suggested-by: Andy Shevchenko Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-wmi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 2cfaaa8faf0a..b2bd396acac5 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -663,16 +663,16 @@ static int dell_wmi_check_descriptor_buffer(struct wmi_device *wdev) buffer = (u32 *)obj->buffer.pointer; - if (buffer[0] != 0x4C4C4544 && buffer[1] != 0x494D5720) { - dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%*ph)\n", - 8, buffer); + if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", + buffer); ret = -EINVAL; goto out; } if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%d)\n", - buffer[2]); + dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", + (unsigned long) buffer[2]); if (buffer[3] != 4096) dev_warn(&wdev->dev, "Dell descriptor buffer has invalid buffer length (%d)\n", -- cgit v1.2.3 From fa9f924c7ff95f9299e11657c4441288d7f01dbd Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:26 -0500 Subject: platform/x86: dell-wmi: don't check length returned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is intended to be variable and provided by the platform. Some platforms this year will be adopting a 32k WMI buffer, so don't complain when encountering those platforms or any other future changes. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-wmi.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index b2bd396acac5..da4f629d0831 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -624,7 +624,7 @@ static void dell_wmi_input_destroy(struct wmi_device *wdev) * Vendor Signature 0 4 "DELL" * Object Signature 4 4 " WMI" * WMI Interface Version 8 4 - * WMI buffer length 12 4 4096 + * WMI buffer length 12 4 */ static int dell_wmi_check_descriptor_buffer(struct wmi_device *wdev) { @@ -674,10 +674,6 @@ static int dell_wmi_check_descriptor_buffer(struct wmi_device *wdev) dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", (unsigned long) 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; -- cgit v1.2.3 From 92b8c540bce7b1662212dff35f503f5b1266725b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:27 -0500 Subject: platform/x86: dell-wmi-descriptor: split WMI descriptor into it's own driver All communication on individual GUIDs should occur in separate drivers. Allowing a driver to communicate with the bus to another GUID is just a hack that discourages drivers to adopt the bus model. The information found from the WMI descriptor driver is now exported for use by other drivers. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 5 + drivers/platform/x86/Kconfig | 5 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/dell-wmi-descriptor.c | 170 +++++++++++++++++++++++++++++ drivers/platform/x86/dell-wmi-descriptor.h | 21 ++++ drivers/platform/x86/dell-wmi.c | 80 +------------- 6 files changed, 208 insertions(+), 74 deletions(-) create mode 100644 drivers/platform/x86/dell-wmi-descriptor.c create mode 100644 drivers/platform/x86/dell-wmi-descriptor.h diff --git a/MAINTAINERS b/MAINTAINERS index b77e493cff9c..69a81400cc65 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4013,6 +4013,11 @@ M: Pali Rohár S: Maintained F: drivers/platform/x86/dell-wmi.c +DELL WMI DESCRIPTOR DRIVER +M: Mario Limonciello +S: Maintained +F: drivers/platform/x86/dell-wmi-descriptor.c + DELTA ST MEDIA DRIVER M: Hugues Fruchet L: linux-media@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 87c8c36efce4..a0babdc5136f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -121,6 +121,7 @@ config DELL_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 +130,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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 2b315d0df3b7..8636f5d3424f 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.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 diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c new file mode 100644 index 000000000000..3204c408e261 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -0,0 +1,170 @@ +/* + * 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 +#include +#include +#include +#include "dell-wmi-descriptor.h" + +struct descriptor_priv { + struct list_head list; + u32 interface_version; + u32 size; +}; +static LIST_HEAD(wmi_list); +static DEFINE_MUTEX(list_mutex); + +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 + * WMI buffer length 12 4 + */ +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; + 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; + 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; + goto out; + } + + 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); + + 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 "); +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..5f7b69c2c83a --- /dev/null +++ b/drivers/platform/x86/dell-wmi-descriptor.h @@ -0,0 +1,21 @@ +/* + * 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 + +#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" + +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 da4f629d0831..6d657eb97672 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -39,6 +39,7 @@ #include #include #include "dell-smbios.h" +#include "dell-wmi-descriptor.h" MODULE_AUTHOR("Matthew Garrett "); MODULE_AUTHOR("Pali Rohár "); @@ -46,7 +47,6 @@ 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; @@ -617,75 +617,6 @@ static void dell_wmi_input_destroy(struct wmi_device *wdev) input_unregister_device(priv->input_dev); } -/* - * 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 - * WMI buffer length 12 4 - */ -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); - ret = -EINVAL; - 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; - goto out; - } - - if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", - (unsigned long) buffer[2]); - - 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: * @@ -721,7 +652,9 @@ 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; + + if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) + return -ENODEV; priv = devm_kzalloc( &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); @@ -729,9 +662,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); } -- cgit v1.2.3 From f97e058cfe8032504e310bd5c20e35d640ef2858 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:28 -0500 Subject: platform/x86: wmi: Don't allow drivers to get each other's GUIDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only driver using this was dell-wmi, and it really was a hack. The driver was getting a data attribute from another driver and this type of action should not be encouraged. Rather drivers that need to interact with one another should pass data back and forth via exported functions. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 17 ----------------- include/linux/wmi.h | 4 ---- 2 files changed, 21 deletions(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 4d73a87c2ddf..bcb41c1c7f52 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -368,23 +368,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 diff --git a/include/linux/wmi.h b/include/linux/wmi.h index 2cd10c3b89e9..ddee427e0721 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -36,10 +36,6 @@ extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev, 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); - struct wmi_device_id { const char *guid_string; }; -- cgit v1.2.3 From 980f481d63f57bb62ac171a66294de3e14d52b77 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:29 -0500 Subject: platform/x86: dell-smbios: only run if proper oem string is detected The proper way to indicate that a system is a 'supported' Dell System is by the presence of this string in OEM strings. Allowing the driver to load on non-Dell systems will have undefined results. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index ffc174638aa4..12a3eb153911 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -172,8 +172,15 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) 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) { -- cgit v1.2.3 From 33b9ca1e53b45f7cacdba9d4fba5cb1387b26827 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:30 -0500 Subject: platform/x86: dell-smbios: Add a sysfs interface for SMBIOS tokens Currently userspace tools can access system tokens via the dcdbas kernel module and a SMI call that will cause the platform to execute SMM code. With a goal in mind of deprecating the dcdbas kernel module a different method for accessing these tokens from userspace needs to be created. This is intentionally marked to only be readable as a process with CAP_SYS_ADMIN as it can contain sensitive information about the platform's configuration. While adding this interface I found that some tokens are duplicated. These need to be ignored from sysfs to avoid duplicate files. MAINTAINERS was missing for this driver. Add myself and Pali to maintainers list for it. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- .../ABI/testing/sysfs-platform-dell-smbios | 21 ++ MAINTAINERS | 7 + drivers/platform/x86/dell-smbios.c | 213 ++++++++++++++++++++- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-dell-smbios 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//tokens/* +Date: November 2017 +KernelVersion: 4.15 +Contact: "Mario Limonciello" +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/MAINTAINERS b/MAINTAINERS index 69a81400cc65..cb4bc87874c0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3978,6 +3978,13 @@ M: "Maciej W. Rozycki" S: Maintained F: drivers/net/fddi/defxx.* +DELL SMBIOS DRIVER +M: Pali Rohár +M: Mario Limonciello +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios.* + DELL LAPTOP DRIVER M: Matthew Garrett M: Pali Rohár diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 12a3eb153911..ed4995fdcd46 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -16,10 +16,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include "../../firmware/dcdbas.h" @@ -39,7 +41,11 @@ static DEFINE_MUTEX(buffer_mutex); static int da_command_address; static int da_command_code; 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; int dell_smbios_error(int value) { @@ -157,6 +163,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) { @@ -170,6 +197,154 @@ 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 buffer_location[13]; + char buffer_value[10]; + 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 */ + sprintf(buffer_location, "%04x_location", + da_tokens[i].tokenID); + location_name = kstrdup(buffer_location, GFP_KERNEL); + 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 */ + sprintf(buffer_value, "%04x_value", + da_tokens[i].tokenID); + value_name = kstrdup(buffer_value, GFP_KERNEL); + 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; @@ -197,9 +372,40 @@ static int __init dell_smbios_init(void) ret = -ENOMEM; goto fail_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_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_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: + free_page((unsigned long)buffer); + fail_buffer: kfree(da_tokens); return ret; @@ -207,8 +413,13 @@ fail_buffer: static void __exit dell_smbios_exit(void) { - kfree(da_tokens); + if (platform_device) { + free_group(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } free_page((unsigned long)buffer); + kfree(da_tokens); } subsys_initcall(dell_smbios_init); -- cgit v1.2.3 From 549b4930f057658dc50d8010e66219233119a4d8 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:31 -0500 Subject: platform/x86: dell-smbios: Introduce dispatcher for SMM calls This splits up the dell-smbios driver into two drivers: * dell-smbios * dell-smbios-smm dell-smbios can operate with multiple different dispatcher drivers to perform SMBIOS operations. Also modify the interface that dell-laptop and dell-wmi use align to this model more closely. Rather than a single global buffer being allocated for all drivers, each driver will allocate and be responsible for it's own buffer. The pointer will be passed to the calling function and each dispatcher driver will then internally copy it to the proper location to perform it's call. Add defines for calls used by these methods in the dell-smbios.h header for tracking purposes. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 6 + drivers/platform/x86/Kconfig | 15 +- drivers/platform/x86/Makefile | 1 + drivers/platform/x86/dell-laptop.c | 283 ++++++++++++--------------------- drivers/platform/x86/dell-smbios-smm.c | 163 +++++++++++++++++++ drivers/platform/x86/dell-smbios.c | 121 +++++++------- drivers/platform/x86/dell-smbios.h | 46 +++++- drivers/platform/x86/dell-wmi.c | 11 +- 8 files changed, 398 insertions(+), 248 deletions(-) create mode 100644 drivers/platform/x86/dell-smbios-smm.c diff --git a/MAINTAINERS b/MAINTAINERS index cb4bc87874c0..c74f40ae8622 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3985,6 +3985,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/dell-smbios.* +DELL SMBIOS SMM DRIVER +M: Mario Limonciello +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios-smm.c + DELL LAPTOP DRIVER M: Matthew Garrett M: Pali Rohár diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a0babdc5136f..ebe7870a7d9f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -93,12 +93,19 @@ config ASUS_LAPTOP config DELL_SMBIOS tristate - select DCDBAS + +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" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 8636f5d3424f..e743615241f8 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -12,6 +12,7 @@ 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_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 diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index f42159fd2031..c4903c5ce7cf 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; @@ -2158,6 +2080,10 @@ static int __init dell_init(void) goto fail_rfkill; } + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + if (!buffer) + goto fail_buffer; + if (quirks && quirks->touchpad_led) touchpad_led_init(&platform_device->dev); @@ -2175,12 +2101,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 +2138,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 +2159,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..53eabb14fb48 --- /dev/null +++ b/drivers/platform/x86/dell-smbios-smm.c @@ -0,0 +1,163 @@ +/* + * SMI methods for use with dell-smbios + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * 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 +#include +#include +#include +#include +#include +#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; +} + +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); + + 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_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 "); +MODULE_AUTHOR("Gabriele Mazzotta "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Dell SMBIOS communications over SMI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index ed4995fdcd46..2229d44cb92c 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -19,33 +19,26 @@ #include #include #include -#include #include #include #include -#include -#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 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 *); +}; + +static LIST_HEAD(smbios_device_list); int dell_smbios_error(int value) { @@ -62,42 +55,71 @@ 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; -} -EXPORT_SYMBOL_GPL(dell_smbios_get_buffer); + struct smbios_device *priv; -void dell_smbios_clear_buffer(void) -{ - memset(buffer, 0, sizeof(struct calling_interface_buffer)); + 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_clear_buffer); +EXPORT_SYMBOL_GPL(dell_smbios_register_device); -void dell_smbios_release_buffer(void) +void dell_smbios_unregister_device(struct device *d) { - mutex_unlock(&buffer_mutex); + 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_release_buffer); +EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); -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; + + 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; + } + } - 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; + if (!selected_dev) { + ret = -ENODEV; + pr_err("No dell-smbios drivers are loaded\n"); + goto out_smbios_call; + } - buffer->cmd_class = class; - buffer->cmd_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) { @@ -146,9 +168,6 @@ 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; - new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * sizeof(struct calling_interface_token), GFP_KERNEL); @@ -344,7 +363,6 @@ static void free_group(struct platform_device *pdev) kfree(token_location_attrs); } - static int __init dell_smbios_init(void) { const struct dmi_device *valid; @@ -363,15 +381,6 @@ 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 = -ENOMEM; - goto fail_buffer; - } ret = platform_driver_register(&platform_driver); if (ret) goto fail_platform_driver; @@ -404,22 +413,20 @@ fail_platform_device_alloc: platform_driver_unregister(&platform_driver); fail_platform_driver: - free_page((unsigned long)buffer); - -fail_buffer: 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); } - free_page((unsigned long)buffer); kfree(da_tokens); + 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 742dd8bd66b9..b3179f5b326b 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -16,6 +16,35 @@ #ifndef _DELL_SMBIOS_H_ #define _DELL_SMBIOS_H_ +#include + +/* Classes and selects used in kernel drivers */ +#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_KBD_BACKLIGHT 4 +#define SELECT_KBD_BACKLIGHT 11 +#define CLASS_INFO 17 +#define SELECT_RFKILL 11 +#define SELECT_APP_REGISTRATION 3 + +/* 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; /* This structure will be modified by the firmware when we enter @@ -37,12 +66,19 @@ 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(struct calling_interface_buffer *buffer); struct calling_interface_token *dell_smbios_find_token(int tokenid); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 6d657eb97672..54321080a30d 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -638,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); } -- cgit v1.2.3 From 1a258e670434f404a4500b65ba1afea2c2b29bba Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:32 -0500 Subject: platform/x86: dell-smbios-wmi: Add new WMI dispatcher driver The dell-smbios stack only currently uses an SMI interface which grants direct access to physical memory to the firmware SMM methods via a pointer. This dispatcher driver adds a WMI-ACPI interface that is detected by WMI probe and preferred over the SMI interface in dell-smbios. Changing this to operate over WMI-ACPI will use an ACPI OperationRegion for a buffer of data storage when SMM calls are performed. This is a safer approach to use in kernel drivers as the SMM will only have access to that OperationRegion. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 6 + drivers/platform/x86/Kconfig | 14 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/dell-smbios-wmi.c | 236 +++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 drivers/platform/x86/dell-smbios-wmi.c diff --git a/MAINTAINERS b/MAINTAINERS index c74f40ae8622..aede236d10f1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3991,6 +3991,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/dell-smbios-smm.c +DELL SMBIOS WMI DRIVER +M: Mario Limonciello +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell-smbios-wmi.c + DELL LAPTOP DRIVER M: Matthew Garrett M: Pali Rohár diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ebe7870a7d9f..1157efcc28c7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -94,6 +94,20 @@ config ASUS_LAPTOP config DELL_SMBIOS tristate +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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e743615241f8..1c4234861de0 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -12,6 +12,7 @@ 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 diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c new file mode 100644 index 000000000000..b31f457e58c3 --- /dev/null +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -0,0 +1,236 @@ +/* + * 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 +#include +#include +#include +#include +#include +#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 dell_wmi_extensions { + __u32 argattrib; + __u32 blength; + __u8 data[]; +} __packed; + +struct dell_wmi_smbios_buffer { + struct calling_interface_buffer std; + struct dell_wmi_extensions ext; +} __packed; + +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) + return -ENODEV; + + 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); + mutex_unlock(&call_mutex); + + return ret; +} + +static int dell_smbios_wmi_probe(struct wmi_device *wdev) +{ + struct wmi_smbios_priv *priv; + int count; + int ret; + + if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) + return -ENODEV; + + 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; + + 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, +}; + +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 "); +MODULE_DESCRIPTION("Dell SMBIOS communications over WMI"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From da1f607ed6e6a904463396bb6a28bf96584c61cc Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:33 -0500 Subject: platform/x86: dell-smbios-smm: test for WSMT WSMT is as an attestation to the OS that the platform won't modify memory outside of pre-defined areas. If a platform has WSMT enabled in BIOS setup, SMM calls through dcdbas will fail. The only way to access platform data in these instances is through the WMI SMBIOS calling interface. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios-smm.c | 33 +++++++++++++++++++++++++++++++++ drivers/platform/x86/dell-smbios.h | 2 ++ 2 files changed, 35 insertions(+) diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c index 53eabb14fb48..89f65c4651a0 100644 --- a/drivers/platform/x86/dell-smbios-smm.c +++ b/drivers/platform/x86/dell-smbios-smm.c @@ -102,6 +102,32 @@ int dell_smbios_smm_call(struct calling_interface_buffer *input) 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; @@ -115,6 +141,12 @@ static int __init dell_smbios_smm_init(void) 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; @@ -138,6 +170,7 @@ fail_register: fail_platform_device_add: platform_device_put(platform_device); +fail_wsmt: fail_platform_device_alloc: free_page((unsigned long)buffer); return ret; diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index b3179f5b326b..07effdc7ae8b 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -44,6 +44,8 @@ #define KBD_LED_AUTO_100_TOKEN 0x02F6 #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 +#define WSMT_EN_TOKEN 0x04EC +#define WSMT_DIS_TOKEN 0x04ED struct notifier_block; -- cgit v1.2.3 From 1f8543a5d602b816b9b64a62cafd6caae2af4ca6 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:34 -0500 Subject: platform/x86: dell-smbios: Add filtering support When a userspace interface is introduced to dell-smbios filtering support will be used to make sure that userspace doesn't make calls deemed unsafe or that can cause the kernel drivers to get out of sync. A blacklist is provided for the following: - Items that are in use by other kernel drivers - Items that are deemed unsafe (diagnostics, write-once, etc) - Any items in the blacklist will be rejected. Following that a whitelist is provided as follows: - Each item has an associated capability. If a userspace interface accesses this item, that capability will be tested to filter the request. - If the process provides CAP_SYS_RAWIO the whitelist will be overridden. When an item is not in the blacklist, or whitelist and the process is run with insufficient capabilities the call will be rejected. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios.c | 192 +++++++++++++++++++++++++++++++++++++ drivers/platform/x86/dell-smbios.h | 11 +++ 2 files changed, 203 insertions(+) diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 2229d44cb92c..d99edd803c19 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -24,6 +24,7 @@ #include #include "dell-smbios.h" +static u32 da_supported_commands; static int da_num_tokens; static struct platform_device *platform_device; static struct calling_interface_token *da_tokens; @@ -38,6 +39,91 @@ struct smbios_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) @@ -90,6 +176,110 @@ void dell_smbios_unregister_device(struct device *d) } EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer) +{ + 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_call_filter); + int dell_smbios_call(struct calling_interface_buffer *buffer) { int (*call_fn)(struct calling_interface_buffer *) = NULL; @@ -168,6 +358,8 @@ static void __init parse_da_table(const struct dmi_header *dm) if (dm->length < 17) return; + da_supported_commands = table->supportedCmds; + new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * sizeof(struct calling_interface_token), GFP_KERNEL); diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index 07effdc7ae8b..91e8004d48ba 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -26,9 +26,14 @@ #define SELECT_TOKEN_AC 2 #define CLASS_KBD_BACKLIGHT 4 #define SELECT_KBD_BACKLIGHT 11 +#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 /* Tokens used in kernel drivers, any of these * should be filtered from userspace access @@ -44,6 +49,10 @@ #define KBD_LED_AUTO_100_TOKEN 0x02F6 #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 + +/* tokens whitelisted to userspace use */ +#define CAPSULE_EN_TOKEN 0x0461 +#define CAPSULE_DIS_TOKEN 0x0462 #define WSMT_EN_TOKEN 0x04EC #define WSMT_DIS_TOKEN 0x04ED @@ -80,6 +89,8 @@ 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); -- cgit v1.2.3 From 44b6b7661132b1b0e5fd3147ded66f1e4a817ca9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:35 -0500 Subject: platform/x86: wmi: create userspace interface for drivers For WMI operations that are only Set or Query readable and writable sysfs attributes created by WMI vendor drivers or the bus driver makes sense. For other WMI operations that are run on Method, there needs to be a way to guarantee to userspace that the results from the method call belong to the data request to the method call. Sysfs attributes don't work well in this scenario because two userspace processes may be competing at reading/writing an attribute and step on each other's data. When a WMI vendor driver declares a callback method in the wmi_driver the WMI bus driver will create a character device that maps to that function. This callback method will be responsible for filtering invalid requests and performing the actual call. That character device will correspond to this path: /dev/wmi/$driver Performing read() on this character device will provide the size of the buffer that the character device needs to perform calls. This buffer size can be set by vendor drivers through a new symbol or when MOF parsing is available by the MOF. Performing ioctl() on this character device will be interpretd by the WMI bus driver. It will perform sanity tests for size of data, test them for a valid instance, copy the data from userspace and pass iton to the vendor driver to further process and run. This creates an implicit policy that each driver will only be allowed a single character device. If a module matches multiple GUID's, the wmi_devices will need to be all handled by the same wmi_driver. The WMI vendor drivers will be responsible for managing inappropriate access to this character device and proper locking on data used by it. When a WMI vendor driver is unloaded the WMI bus driver will clean up the character device and any memory allocated for the call. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 1 + drivers/platform/x86/wmi.c | 189 ++++++++++++++++++++++++++++++++++++++++++++- include/linux/wmi.h | 5 ++ include/uapi/linux/wmi.h | 26 +++++++ 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 include/uapi/linux/wmi.h diff --git a/MAINTAINERS b/MAINTAINERS index aede236d10f1..3af07502220a 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 diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index bcb41c1c7f52..8c31ed4f0e1b 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -38,12 +38,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include 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 @@ -764,6 +789,111 @@ 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)) + return -EBUSY; + 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) { @@ -771,16 +901,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; } @@ -791,6 +968,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)); @@ -847,6 +1031,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; } diff --git a/include/linux/wmi.h b/include/linux/wmi.h index ddee427e0721..4757cb5077e5 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -18,6 +18,7 @@ #include #include +#include struct wmi_device { struct device dev; @@ -36,6 +37,8 @@ extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev, extern union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance); +extern int set_required_buffer_size(struct wmi_device *wdev, u64 length); + struct wmi_device_id { const char *guid_string; }; @@ -47,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..7e52350ac9b3 --- /dev/null +++ b/include/uapi/linux/wmi.h @@ -0,0 +1,26 @@ +/* + * 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 + +/* 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[]; +}; + +#endif -- cgit v1.2.3 From f2645fa317b8905b8934f06a0601d5b7fa66aba0 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:36 -0500 Subject: platform/x86: dell-smbios-wmi: introduce userspace interface It's important for the driver to provide a R/W ioctl to ensure that two competing userspace processes don't race to provide or read each others data. This userspace character device will be used to perform SMBIOS calls from any applications. It provides an ioctl that will allow passing the WMI calling interface buffer between userspace and kernel space. This character device is intended to deprecate the dcdbas kernel module and the interface that it provides to userspace. To perform an SMBIOS IOCTL call using the character device userspace will perform a read() on the the character device. The WMI bus will provide a u64 variable containing the necessary size of the IOCTL buffer. The API for interacting with this interface is defined in documentation as well as the WMI uapi header provides the format of the structures. Not all userspace requests will be accepted. The dell-smbios filtering functionality will be used to prevent access to certain tokens and calls. All whitelisted commands and tokens are now shared out to userspace so applications don't need to define them in their own headers. Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- Documentation/ABI/testing/dell-smbios-wmi | 41 +++++++++++++++++++++++ drivers/platform/x86/dell-smbios-wmi.c | 54 ++++++++++++++++++++++++------- drivers/platform/x86/dell-smbios.h | 32 ++---------------- include/uapi/linux/wmi.h | 47 +++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 Documentation/ABI/testing/dell-smbios-wmi 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" +Description: + Perform SMBIOS calls on supported Dell machines. + through the Dell ACPI-WMI interface. + + IOCTL's and buffer formats are defined in: + + + 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/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index b31f457e58c3..35c13815b24c 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -30,17 +30,6 @@ struct misc_bios_flags_structure { #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" -struct dell_wmi_extensions { - __u32 argattrib; - __u32 blength; - __u8 data[]; -} __packed; - -struct dell_wmi_smbios_buffer { - struct calling_interface_buffer std; - struct dell_wmi_extensions ext; -} __packed; - struct wmi_smbios_priv { struct dell_wmi_smbios_buffer *buf; struct list_head list; @@ -117,6 +106,42 @@ int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) 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; @@ -135,6 +160,12 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev) 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) @@ -210,6 +241,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { .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) diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index 91e8004d48ba..138d478d9adc 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -17,23 +17,11 @@ #define _DELL_SMBIOS_H_ #include +#include -/* Classes and selects used in kernel drivers */ -#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 +/* Classes and selects used only in kernel drivers */ #define CLASS_KBD_BACKLIGHT 4 #define SELECT_KBD_BACKLIGHT 11 -#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 /* Tokens used in kernel drivers, any of these * should be filtered from userspace access @@ -50,24 +38,8 @@ #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 -/* tokens whitelisted to userspace use */ -#define CAPSULE_EN_TOKEN 0x0461 -#define CAPSULE_DIS_TOKEN 0x0462 -#define WSMT_EN_TOKEN 0x04EC -#define WSMT_DIS_TOKEN 0x04ED - struct notifier_block; -/* This structure will be modified by the firmware when we enter - * system management mode, hence the volatiles */ - -struct calling_interface_buffer { - u16 cmd_class; - u16 cmd_select; - volatile u32 input[4]; - volatile u32 output[4]; -} __packed; - struct calling_interface_token { u16 tokenID; u16 location; diff --git a/include/uapi/linux/wmi.h b/include/uapi/linux/wmi.h index 7e52350ac9b3..7a92e9e3d1c0 100644 --- a/include/uapi/linux/wmi.h +++ b/include/uapi/linux/wmi.h @@ -10,6 +10,7 @@ #ifndef _UAPI_LINUX_WMI_H #define _UAPI_LINUX_WMI_H +#include #include /* WMI bus will filter all WMI vendor driver requests through this IOC */ @@ -23,4 +24,50 @@ struct wmi_ioctl_buffer { __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 -- cgit v1.2.3 From 9d64fc08f6fe59a7d71e84f650dd2c0f080254dd Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Nov 2017 14:25:37 -0500 Subject: tools/wmi: add a sample for dell smbios communication over WMI This application uses the character device /dev/wmi/dell-smbios to perform SMBIOS communications from userspace. It offers demonstrations of a few simple tasks: - Running a class/select command - Querying a token value - Activating a token Signed-off-by: Mario Limonciello Reviewed-by: Edward O'Callaghan Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 1 + tools/Makefile | 14 +-- tools/wmi/Makefile | 18 ++++ tools/wmi/dell-smbios-example.c | 210 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 tools/wmi/Makefile create mode 100644 tools/wmi/dell-smbios-example.c diff --git a/MAINTAINERS b/MAINTAINERS index 3af07502220a..da1851fc88a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3997,6 +3997,7 @@ M: Mario Limonciello 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 diff --git a/tools/Makefile b/tools/Makefile index 9dfede37c8ff..9d2fd2606810 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -29,6 +29,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:' @@ -57,7 +58,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -cgroup firewire hv guest spi usb virtio vm net iio gpio objtool leds: FORCE +cgroup firewire hv guest spi usb virtio vm net iio gpio objtool leds wmi: FORCE $(call descend,$@) liblockdep: FORCE @@ -92,7 +93,7 @@ kvm_stat: FORCE all: acpi cgroup cpupower gpio hv firewire liblockdep \ perf selftests spi turbostat usb \ virtio vm net x86_energy_perf_policy \ - tmon freefall iio objtool kvm_stat + tmon freefall iio objtool kvm_stat wmi acpi_install: $(call descend,power/$(@:_install=),install) @@ -100,7 +101,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 net_install objtool_install: +cgroup_install firewire_install gpio_install hv_install iio_install perf_install spi_install usb_install virtio_install vm_install net_install objtool_install wmi_install: $(call descend,$(@:_install=),install) liblockdep_install: @@ -125,7 +126,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 net_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) @@ -133,7 +135,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean net_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 net_clean iio_clean gpio_clean objtool_clean leds_clean: $(call descend,$(@:_clean=),clean) liblockdep_clean: @@ -171,6 +173,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 net_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 +#include +#include +#include +#include +#include + +/* if uapi header isn't installed, this might not yet exist */ +#ifndef __packed +#define __packed __attribute__((packed)) +#endif +#include + +/* 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; +} -- cgit v1.2.3 From f6c8a317ab208aee223776327c06f23342492d54 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 1 Nov 2017 18:42:45 +0100 Subject: platform/x86: sony-laptop: Fix error handling in sony_nc_setup_rfkill() Source code review for a specific software refactoring showed the need for another correction because the error code "-1" was returned so far if a call of the function "sony_call_snc_handle" failed here. Thus assign the return value from these two function calls also to the variable "err" and provide it in case of a failure. Fixes: d6f15ed876b83a1a0eba1d0473eef58acc95444a ("sony-laptop: use soft rfkill status stored in hw") Suggested-by: Andy Shevchenko Link: https://lkml.org/lkml/2017/10/31/463 Link: https://lkml.kernel.org/r/ Signed-off-by: Markus Elfring Signed-off-by: Andy Shevchenko --- drivers/platform/x86/sony-laptop.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index a16cea2be9c3..4332cc982ce0 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -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); -- cgit v1.2.3 From e4a18052bb99e25d2c0074981120b76638285c22 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 1 Nov 2017 19:00:59 +0100 Subject: platform/x86: sony-laptop: Drop variable assignment in sony_nc_setup_rfkill() The local variable "err" will eventually be set to an appropriate value a bit later. Thus omit the explicit initialisation at the beginning. Signed-off-by: Markus Elfring Signed-off-by: Andy Shevchenko --- drivers/platform/x86/sony-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 4332cc982ce0..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; -- cgit v1.2.3 From fbc15e30400c9d927eda37445c78a7811a68e4a7 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 4 Oct 2017 17:54:21 -0700 Subject: platform/x86: intel_ips: Convert timers to use timer_setup() In preparation for unconditionally passing the struct timer_list pointer to all timer callbacks, switch to using the new timer_setup() and from_timer() to pass the timer pointer explicitly. Moves timer structure off stack and into struct ips_driver. Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: Thomas Gleixner Signed-off-by: Kees Cook Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_ips.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 680ab4fd7087..a0c95853fd3f 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -296,6 +296,7 @@ struct ips_driver { 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; @@ -937,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); } /** @@ -956,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; @@ -1044,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; @@ -1103,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 */ @@ -1112,8 +1112,7 @@ 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, "ips-monitor thread stopped\n"); -- cgit v1.2.3 From 9968e12a291e639dd51d1218b694d440b22a917f Mon Sep 17 00:00:00 2001 From: Stefan Brüns Date: Fri, 3 Nov 2017 03:01:53 +0100 Subject: platform/x86: hp-wmi: Fix tablet mode detection for convertibles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit f9cf3b2880cc ("platform/x86: hp-wmi: Refactor dock and tablet state fetchers") consolidated the methods for docking and laptop mode detection, but omitted to apply the correct mask for the laptop mode (it always uses the constant for docking). Fixes: f9cf3b2880cc ("platform/x86: hp-wmi: Refactor dock and tablet state fetchers") Signed-off-by: Stefan Brüns Signed-off-by: Andy Shevchenko --- drivers/platform/x86/hp-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) -- cgit v1.2.3 From ce7ff1cffdaf82354aca5f4c8691e5c85474fbde Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 31 Oct 2017 11:03:18 +0000 Subject: platform/x86: dell-smo8800: remove redundant assignments to byte_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Variable byte_data is being initialized and re-assigned with values that are never read. Remove these as these redundant assignments. Cleans up clang warning: drivers/platform/x86/dell-smo8800.c:106:2: warning: Value stored to 'byte_data' is never read Signed-off-by: Colin Ian King Acked-by: Pali Rohár Signed-off-by: Andy Shevchenko --- drivers/platform/x86/dell-smo8800.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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) -- cgit v1.2.3 From 6cc8cbbc8868033f279b63e98b26b75eaa0006ab Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Sun, 29 Oct 2017 02:49:54 -0700 Subject: platform/x86: intel_punit_ipc: Fix resource ioremap warning For PUNIT device, ISPDRIVER_IPC and GTDDRIVER_IPC resources are not mandatory. So when PMC IPC driver creates a PUNIT device, if these resources are not available then it creates dummy resource entries for these missing resources. But during PUNIT device probe, doing ioremap on these dummy resources generates following warning messages. intel_punit_ipc: can't request region for resource [mem 0x00000000] intel_punit_ipc: can't request region for resource [mem 0x00000000] intel_punit_ipc: can't request region for resource [mem 0x00000000] intel_punit_ipc: can't request region for resource [mem 0x00000000] This patch fixes this issue by adding extra check for resource size before performing ioremap operation. Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_punit_ipc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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; -- cgit v1.2.3 From 43aaf4f03f063b12bcba2f8b800fdec85e2acc75 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Sun, 29 Oct 2017 02:49:55 -0700 Subject: platform/x86: intel_pmc_ipc: Use MFD framework to create dependent devices Currently, we have lot of repetitive code in dependent device resource allocation and device creation handling code. This logic can be improved if we use MFD framework for dependent device creation. This patch adds this support. Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_ipc.c | 398 ++++++++++++----------------------- 1 file changed, 139 insertions(+), 259 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index e03fa31446ca..e36144c337cd 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ #define PLAT_RESOURCE_ISP_IFACE_INDEX 5 #define PLAT_RESOURCE_GTD_DATA_INDEX 6 #define PLAT_RESOURCE_GTD_IFACE_INDEX 7 +#define PLAT_RESOURCE_MEM_MAX_INDEX 8 #define PLAT_RESOURCE_ACPI_IO_INDEX 0 /* @@ -106,8 +108,6 @@ #define TELEM_SSRAM_SIZE 240 #define TELEM_PMC_SSRAM_OFFSET 0x1B00 #define TELEM_PUNIT_SSRAM_OFFSET 0x1A00 -#define TCO_PMC_OFFSET 0x8 -#define TCO_PMC_SIZE 0x4 /* PMC register bit definitions */ @@ -124,26 +124,10 @@ static struct intel_pmc_ipc_dev { int cmd; struct completion cmd_complete; - /* The following PMC BARs share the same ACPI device with the IPC */ - resource_size_t acpi_io_base; - int acpi_io_size; - struct platform_device *tco_dev; - /* gcr */ void __iomem *gcr_mem_base; bool has_gcr_regs; spinlock_t gcr_lock; - - /* punit */ - struct platform_device *punit_dev; - - /* Telemetry */ - resource_size_t telem_pmc_ssram_base; - resource_size_t telem_punit_ssram_base; - int telem_pmc_ssram_size; - int telem_punit_ssram_size; - u8 telem_res_inval; - struct platform_device *telemetry_dev; } ipcdev; static char *ipc_err_sources[] = { @@ -508,7 +492,7 @@ static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ret = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_pmc_ipc", pmc); if (ret) { - dev_err(&pdev->dev, "Failed to request irq\n"); + dev_err(&pdev->dev, "Failed to request IRQ\n"); return ret; } @@ -593,44 +577,6 @@ static const struct attribute_group intel_ipc_group = { .attrs = intel_ipc_attrs, }; -static struct resource punit_res_array[] = { - /* Punit BIOS */ - { - .flags = IORESOURCE_MEM, - }, - { - .flags = IORESOURCE_MEM, - }, - /* Punit ISP */ - { - .flags = IORESOURCE_MEM, - }, - { - .flags = IORESOURCE_MEM, - }, - /* Punit GTD */ - { - .flags = IORESOURCE_MEM, - }, - { - .flags = IORESOURCE_MEM, - }, -}; - -#define TCO_RESOURCE_ACPI_IO 0 -#define TCO_RESOURCE_SMI_EN_IO 1 -#define TCO_RESOURCE_GCR_MEM 2 -static struct resource tco_res[] = { - /* ACPI - TCO */ - { - .flags = IORESOURCE_IO, - }, - /* ACPI - SMI */ - { - .flags = IORESOURCE_IO, - }, -}; - static struct itco_wdt_platform_data tco_info = { .name = "Apollo Lake SoC", .version = 5, @@ -638,234 +584,177 @@ static struct itco_wdt_platform_data tco_info = { .update_no_reboot_bit = update_no_reboot_bit, }; -#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0 -#define TELEMETRY_RESOURCE_PMC_SSRAM 1 -static struct resource telemetry_res[] = { - /*Telemetry*/ - { - .flags = IORESOURCE_MEM, - }, - { - .flags = IORESOURCE_MEM, - }, -}; - -static int ipc_create_punit_device(void) +static int ipc_create_punit_device(struct platform_device *pdev) { - struct platform_device *pdev; - const struct platform_device_info pdevinfo = { - .parent = ipcdev.dev, - .name = PUNIT_DEVICE_NAME, - .id = -1, - .res = punit_res_array, - .num_res = ARRAY_SIZE(punit_res_array), + struct resource punit_res[PLAT_RESOURCE_MEM_MAX_INDEX]; + struct mfd_cell punit_cell; + struct resource *res; + int mindex, pindex = 0; + + for (mindex = 0; mindex <= PLAT_RESOURCE_MEM_MAX_INDEX; mindex++) { + + res = platform_get_resource(pdev, IORESOURCE_MEM, mindex); + + switch (mindex) { + /* Get PUNIT resources */ + case PLAT_RESOURCE_BIOS_DATA_INDEX: + case PLAT_RESOURCE_BIOS_IFACE_INDEX: + /* BIOS resources are required, so return error if not + * available + */ + if (!res) { + dev_err(&pdev->dev, + "Failed to get PUNIT MEM resource %d\n", + pindex); + return -ENXIO; + } + case PLAT_RESOURCE_ISP_DATA_INDEX: + case PLAT_RESOURCE_ISP_IFACE_INDEX: + case PLAT_RESOURCE_GTD_DATA_INDEX: + case PLAT_RESOURCE_GTD_IFACE_INDEX: + /* if valid resource found, copy the resource to PUNIT + * resource + */ + if (res) + memcpy(&punit_res[pindex], res, sizeof(*res)); + punit_res[pindex].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "PUNIT memory res: %pR\n", + &punit_res[pindex]); + pindex++; + break; }; + } - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - ipcdev.punit_dev = pdev; + /* Create PUNIT IPC MFD cell */ + punit_cell.name = PUNIT_DEVICE_NAME; + punit_cell.num_resources = ARRAY_SIZE(punit_res); + punit_cell.resources = punit_res; + punit_cell.ignore_resource_conflicts = 1; - return 0; + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &punit_cell, 1, NULL, 0, NULL); } -static int ipc_create_tco_device(void) +static int ipc_create_wdt_device(struct platform_device *pdev) { - struct platform_device *pdev; + static struct resource wdt_ipc_res[2]; struct resource *res; - const struct platform_device_info pdevinfo = { - .parent = ipcdev.dev, - .name = TCO_DEVICE_NAME, - .id = -1, - .res = tco_res, - .num_res = ARRAY_SIZE(tco_res), - .data = &tco_info, - .size_data = sizeof(tco_info), - }; + static struct mfd_cell wdt_cell; - res = tco_res + TCO_RESOURCE_ACPI_IO; - res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET; - res->end = res->start + TCO_REGS_SIZE - 1; + /* If we have ACPI based watchdog use that instead, othewise create + * a MFD cell for iTCO watchdog + */ + if (acpi_has_watchdog()) + return 0; - res = tco_res + TCO_RESOURCE_SMI_EN_IO; - res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET; - res->end = res->start + SMI_EN_SIZE - 1; + /* Get iTCO watchdog resources */ + res = platform_get_resource(pdev, IORESOURCE_IO, + PLAT_RESOURCE_ACPI_IO_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get WDT resource\n"); + return -ENXIO; + } - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); + wdt_ipc_res[0].start = res->start + TCO_BASE_OFFSET; + wdt_ipc_res[0].end = res->start + + TCO_BASE_OFFSET + TCO_REGS_SIZE - 1; + wdt_ipc_res[0].flags = IORESOURCE_IO; + wdt_ipc_res[1].start = res->start + SMI_EN_OFFSET; + wdt_ipc_res[1].end = res->start + + SMI_EN_OFFSET + SMI_EN_SIZE - 1; + wdt_ipc_res[1].flags = IORESOURCE_IO; - ipcdev.tco_dev = pdev; + dev_dbg(&pdev->dev, "watchdog res 0: %pR\n", &wdt_ipc_res[0]); + dev_dbg(&pdev->dev, "watchdog res 1: %pR\n", &wdt_ipc_res[1]); - return 0; + wdt_cell.name = TCO_DEVICE_NAME; + wdt_cell.platform_data = &tco_info; + wdt_cell.pdata_size = sizeof(tco_info); + wdt_cell.num_resources = ARRAY_SIZE(wdt_ipc_res); + wdt_cell.resources = wdt_ipc_res; + wdt_cell.ignore_resource_conflicts = 1; + + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &wdt_cell, 1, NULL, 0, NULL); } -static int ipc_create_telemetry_device(void) +static int ipc_create_telemetry_device(struct platform_device *pdev) { - struct platform_device *pdev; + struct resource telemetry_ipc_res[2]; + struct mfd_cell telemetry_cell; struct resource *res; - const struct platform_device_info pdevinfo = { - .parent = ipcdev.dev, - .name = TELEMETRY_DEVICE_NAME, - .id = -1, - .res = telemetry_res, - .num_res = ARRAY_SIZE(telemetry_res), - }; - res = telemetry_res + TELEMETRY_RESOURCE_PUNIT_SSRAM; - res->start = ipcdev.telem_punit_ssram_base; - res->end = res->start + ipcdev.telem_punit_ssram_size - 1; + /* Get telemetry resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_TELEM_SSRAM_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get telemetry resource\n"); + return -ENXIO; + } - res = telemetry_res + TELEMETRY_RESOURCE_PMC_SSRAM; - res->start = ipcdev.telem_pmc_ssram_base; - res->end = res->start + ipcdev.telem_pmc_ssram_size - 1; + telemetry_ipc_res[0].start = res->start + TELEM_PUNIT_SSRAM_OFFSET; + telemetry_ipc_res[0].end = res->start + + TELEM_PUNIT_SSRAM_OFFSET + TELEM_SSRAM_SIZE - 1; + telemetry_ipc_res[0].flags = IORESOURCE_MEM; + telemetry_ipc_res[1].start = res->start + TELEM_PMC_SSRAM_OFFSET; + telemetry_ipc_res[1].end = res->start + + TELEM_PMC_SSRAM_OFFSET + TELEM_SSRAM_SIZE - 1; + telemetry_ipc_res[1].flags = IORESOURCE_MEM; - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); + dev_dbg(&pdev->dev, "Telemetry res 0: %pR\n", &telemetry_ipc_res[0]); + dev_dbg(&pdev->dev, "Telemetry res 1: %pR\n", &telemetry_ipc_res[1]); - ipcdev.telemetry_dev = pdev; + telemetry_cell.name = TELEMETRY_DEVICE_NAME; + telemetry_cell.num_resources = ARRAY_SIZE(telemetry_ipc_res); + telemetry_cell.resources = telemetry_ipc_res; + telemetry_cell.ignore_resource_conflicts = 1; - return 0; + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &telemetry_cell, 1, NULL, 0, NULL); } -static int ipc_create_pmc_devices(void) +static int ipc_create_pmc_devices(struct platform_device *pdev) { int ret; - /* If we have ACPI based watchdog use that instead */ - if (!acpi_has_watchdog()) { - ret = ipc_create_tco_device(); - if (ret) { - dev_err(ipcdev.dev, "Failed to add tco platform device\n"); - return ret; - } - } + ret = ipc_create_punit_device(pdev); + if (ret < 0) + return ret; - ret = ipc_create_punit_device(); - if (ret) { - dev_err(ipcdev.dev, "Failed to add punit platform device\n"); - platform_device_unregister(ipcdev.tco_dev); - } + ret = ipc_create_wdt_device(pdev); + if (ret < 0) + return ret; - if (!ipcdev.telem_res_inval) { - ret = ipc_create_telemetry_device(); - if (ret) - dev_warn(ipcdev.dev, - "Failed to add telemetry platform device\n"); - } + ret = ipc_create_telemetry_device(pdev); + if (ret < 0) + return ret; - return ret; + return 0; } static int ipc_plat_get_res(struct platform_device *pdev) { - struct resource *res, *punit_res; + struct resource *res; void __iomem *addr; - int size; - - res = platform_get_resource(pdev, IORESOURCE_IO, - PLAT_RESOURCE_ACPI_IO_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get io resource\n"); - return -ENXIO; - } - size = resource_size(res); - ipcdev.acpi_io_base = res->start; - ipcdev.acpi_io_size = size; - dev_info(&pdev->dev, "io res: %pR\n", res); - - punit_res = punit_res_array; - /* This is index 0 to cover BIOS data register */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_BIOS_DATA_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n"); - return -ENXIO; - } - *punit_res = *res; - dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res); - - /* This is index 1 to cover BIOS interface register */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_BIOS_IFACE_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n"); - return -ENXIO; - } - *++punit_res = *res; - dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res); - - /* This is index 2 to cover ISP data register, optional */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_ISP_DATA_INDEX); - ++punit_res; - if (res) { - *punit_res = *res; - dev_info(&pdev->dev, "punit ISP data res: %pR\n", res); - } - - /* This is index 3 to cover ISP interface register, optional */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_ISP_IFACE_INDEX); - ++punit_res; - if (res) { - *punit_res = *res; - dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res); - } - - /* This is index 4 to cover GTD data register, optional */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_GTD_DATA_INDEX); - ++punit_res; - if (res) { - *punit_res = *res; - dev_info(&pdev->dev, "punit GTD data res: %pR\n", res); - } - - /* This is index 5 to cover GTD interface register, optional */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_GTD_IFACE_INDEX); - ++punit_res; - if (res) { - *punit_res = *res; - dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res); - } + /* Get IPC resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_IPC_INDEX); if (!res) { - dev_err(&pdev->dev, "Failed to get ipc resource\n"); + dev_err(&pdev->dev, "Failed to get IPC resources\n"); return -ENXIO; } - size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE; - res->end = res->start + size - 1; + + res->end = res->start + + PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE - 1; addr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(addr)) return PTR_ERR(addr); ipcdev.ipc_base = addr; - ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET; - dev_info(&pdev->dev, "ipc res: %pR\n", res); - - ipcdev.telem_res_inval = 0; - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_TELEM_SSRAM_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get telemetry ssram resource\n"); - ipcdev.telem_res_inval = 1; - } else { - ipcdev.telem_punit_ssram_base = res->start + - TELEM_PUNIT_SSRAM_OFFSET; - ipcdev.telem_punit_ssram_size = TELEM_SSRAM_SIZE; - ipcdev.telem_pmc_ssram_base = res->start + - TELEM_PMC_SSRAM_OFFSET; - ipcdev.telem_pmc_ssram_size = TELEM_SSRAM_SIZE; - dev_info(&pdev->dev, "telemetry ssram res: %pR\n", res); - } + dev_dbg(&pdev->dev, "PMC IPC resource %pR\n", res); return 0; } @@ -911,7 +800,7 @@ static int ipc_plat_probe(struct platform_device *pdev) ipcdev.irq = platform_get_irq(pdev, 0); if (ipcdev.irq < 0) { - dev_err(&pdev->dev, "Failed to get irq\n"); + dev_err(&pdev->dev, "Failed to get IRQ\n"); return -EINVAL; } @@ -921,47 +810,38 @@ static int ipc_plat_probe(struct platform_device *pdev) return ret; } - ret = ipc_create_pmc_devices(); + ret = ipc_create_pmc_devices(pdev); if (ret) { - dev_err(&pdev->dev, "Failed to create pmc devices\n"); + dev_err(&pdev->dev, "Failed to create PMC devices\n"); return ret; } - if (devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND, - "intel_pmc_ipc", &ipcdev)) { - dev_err(&pdev->dev, "Failed to request irq\n"); - ret = -EBUSY; - goto err_irq; + ret = devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND, + "intel_pmc_ipc", &ipcdev); + if (ret) { + dev_err(&pdev->dev, "Failed to request IRQ\n"); + return ret; } ret = sysfs_create_group(&pdev->dev.kobj, &intel_ipc_group); if (ret) { dev_err(&pdev->dev, "Failed to create sysfs group %d\n", ret); - goto err_sys; + devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); + return ret; } ipcdev.has_gcr_regs = true; return 0; -err_sys: - devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); -err_irq: - platform_device_unregister(ipcdev.tco_dev); - platform_device_unregister(ipcdev.punit_dev); - platform_device_unregister(ipcdev.telemetry_dev); - - return ret; } static int ipc_plat_remove(struct platform_device *pdev) { sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group); devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); - platform_device_unregister(ipcdev.tco_dev); - platform_device_unregister(ipcdev.punit_dev); - platform_device_unregister(ipcdev.telemetry_dev); ipcdev.dev = NULL; + return 0; } @@ -980,12 +860,12 @@ static int __init intel_pmc_ipc_init(void) ret = platform_driver_register(&ipc_plat_driver); if (ret) { - pr_err("Failed to register PMC ipc platform driver\n"); + pr_err("Failed to register PMC IPC platform driver\n"); return ret; } ret = pci_register_driver(&ipc_pci_driver); if (ret) { - pr_err("Failed to register PMC ipc pci driver\n"); + pr_err("Failed to register PMC IPC PCI driver\n"); platform_driver_unregister(&ipc_plat_driver); return ret; } -- cgit v1.2.3 From 54d11736ec3179954b6cdea4b4e1136d6bb39f39 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 8 Nov 2017 13:08:39 +0100 Subject: platform/x86: dell-smbios: fix string overflow The new sysfs code overwrites two fixed-length character arrays that are each one byte shorter than they need to be, to hold the trailing \0: drivers/platform/x86/dell-smbios.c: In function 'build_tokens_sysfs': drivers/platform/x86/dell-smbios.c:494:42: error: 'sprintf' writing a terminating nul past the end of the destination [-Werror=format-overflow=] sprintf(buffer_location, "%04x_location", drivers/platform/x86/dell-smbios.c:494:3: note: 'sprintf' output 14 bytes into a destination of size 13 drivers/platform/x86/dell-smbios.c:506:36: error: 'sprintf' writing a terminating nul past the end of the destination [-Werror=format-overflow=] sprintf(buffer_value, "%04x_value", drivers/platform/x86/dell-smbios.c:506:3: note: 'sprintf' output 11 bytes into a destination of size 10 This changes it to just use kasprintf(), which always gets it right. Discovered with gcc-7.1.1 with the following commit reverted: bd664f6b3e disable new gcc-7.1.1 warnings for now Fixes: 33b9ca1e53b4 ("platform/x86: dell-smbios: Add a sysfs interface for SMBIOS tokens") Signed-off-by: Arnd Bergmann Acked-by: Mario Limonciello [dvhart: add subject prefix and reproducer details for context] Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index d99edd803c19..6a60db515bda 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -463,8 +463,6 @@ static struct platform_driver platform_driver = { static int build_tokens_sysfs(struct platform_device *dev) { - char buffer_location[13]; - char buffer_value[10]; char *location_name; char *value_name; size_t size; @@ -491,9 +489,8 @@ static int build_tokens_sysfs(struct platform_device *dev) if (da_tokens[i].tokenID == 0) continue; /* add location */ - sprintf(buffer_location, "%04x_location", - da_tokens[i].tokenID); - location_name = kstrdup(buffer_location, GFP_KERNEL); + 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); @@ -503,9 +500,8 @@ static int build_tokens_sysfs(struct platform_device *dev) token_attrs[j++] = &token_location_attrs[i].attr; /* add value */ - sprintf(buffer_value, "%04x_value", - da_tokens[i].tokenID); - value_name = kstrdup(buffer_value, GFP_KERNEL); + 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); -- cgit v1.2.3 From 5e3e22971fb0b0d7d03286f1a619d0348748a243 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Sun, 5 Nov 2017 21:34:33 -0600 Subject: platform/x86: wmi: release mutex on module acquistion failure This failure mode should have also released the mutex. Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/wmi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 8c31ed4f0e1b..791449a2370f 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -868,8 +868,10 @@ static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) /* 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)) - return -EBUSY; + 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) -- cgit v1.2.3 From 307ab2a99d190d3a7949258b8551b66887ce8cf4 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Sun, 5 Nov 2017 21:34:34 -0600 Subject: platform/x86: dell-smbios-wmi: release mutex lock on WMI call failure Unbound devices may race with calling this function causing the mutex to stay locked. This failure mode should have released the mutex too. Signed-off-by: Mario Limonciello Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios-wmi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index 35c13815b24c..5cf9b13ce6e6 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -91,8 +91,10 @@ int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) mutex_lock(&call_mutex); priv = get_first_smbios_priv(); - if (!priv) - return -ENODEV; + if (!priv) { + ret = -ENODEV; + goto out_wmi_call; + } size = sizeof(struct calling_interface_buffer); difference = priv->req_buf_size - sizeof(u64) - size; @@ -101,6 +103,7 @@ int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) 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; -- cgit v1.2.3 From 73ed298b0605a81d8cbf212df887f7b2fe65a61b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 13 Nov 2017 14:50:06 +0200 Subject: platform/x86: Revert intel_pmc_ipc: Use MFD framework to create dependent devices Heikki discovered a runtime issue with this patch. Taking into consideration we have no time to test any fix right now, revert the commit 43aaf4f03f063b12bcba2f8b800fdec85e2acc75. Reported-by: Heikki Krogerus Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_ipc.c | 398 +++++++++++++++++++++++------------ 1 file changed, 259 insertions(+), 139 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index e36144c337cd..e03fa31446ca 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -89,7 +88,6 @@ #define PLAT_RESOURCE_ISP_IFACE_INDEX 5 #define PLAT_RESOURCE_GTD_DATA_INDEX 6 #define PLAT_RESOURCE_GTD_IFACE_INDEX 7 -#define PLAT_RESOURCE_MEM_MAX_INDEX 8 #define PLAT_RESOURCE_ACPI_IO_INDEX 0 /* @@ -108,6 +106,8 @@ #define TELEM_SSRAM_SIZE 240 #define TELEM_PMC_SSRAM_OFFSET 0x1B00 #define TELEM_PUNIT_SSRAM_OFFSET 0x1A00 +#define TCO_PMC_OFFSET 0x8 +#define TCO_PMC_SIZE 0x4 /* PMC register bit definitions */ @@ -124,10 +124,26 @@ static struct intel_pmc_ipc_dev { int cmd; struct completion cmd_complete; + /* The following PMC BARs share the same ACPI device with the IPC */ + resource_size_t acpi_io_base; + int acpi_io_size; + struct platform_device *tco_dev; + /* gcr */ void __iomem *gcr_mem_base; bool has_gcr_regs; spinlock_t gcr_lock; + + /* punit */ + struct platform_device *punit_dev; + + /* Telemetry */ + resource_size_t telem_pmc_ssram_base; + resource_size_t telem_punit_ssram_base; + int telem_pmc_ssram_size; + int telem_punit_ssram_size; + u8 telem_res_inval; + struct platform_device *telemetry_dev; } ipcdev; static char *ipc_err_sources[] = { @@ -492,7 +508,7 @@ static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ret = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_pmc_ipc", pmc); if (ret) { - dev_err(&pdev->dev, "Failed to request IRQ\n"); + dev_err(&pdev->dev, "Failed to request irq\n"); return ret; } @@ -577,6 +593,44 @@ static const struct attribute_group intel_ipc_group = { .attrs = intel_ipc_attrs, }; +static struct resource punit_res_array[] = { + /* Punit BIOS */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, + /* Punit ISP */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, + /* Punit GTD */ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, +}; + +#define TCO_RESOURCE_ACPI_IO 0 +#define TCO_RESOURCE_SMI_EN_IO 1 +#define TCO_RESOURCE_GCR_MEM 2 +static struct resource tco_res[] = { + /* ACPI - TCO */ + { + .flags = IORESOURCE_IO, + }, + /* ACPI - SMI */ + { + .flags = IORESOURCE_IO, + }, +}; + static struct itco_wdt_platform_data tco_info = { .name = "Apollo Lake SoC", .version = 5, @@ -584,177 +638,234 @@ static struct itco_wdt_platform_data tco_info = { .update_no_reboot_bit = update_no_reboot_bit, }; -static int ipc_create_punit_device(struct platform_device *pdev) +#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0 +#define TELEMETRY_RESOURCE_PMC_SSRAM 1 +static struct resource telemetry_res[] = { + /*Telemetry*/ + { + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, +}; + +static int ipc_create_punit_device(void) { - struct resource punit_res[PLAT_RESOURCE_MEM_MAX_INDEX]; - struct mfd_cell punit_cell; - struct resource *res; - int mindex, pindex = 0; - - for (mindex = 0; mindex <= PLAT_RESOURCE_MEM_MAX_INDEX; mindex++) { - - res = platform_get_resource(pdev, IORESOURCE_MEM, mindex); - - switch (mindex) { - /* Get PUNIT resources */ - case PLAT_RESOURCE_BIOS_DATA_INDEX: - case PLAT_RESOURCE_BIOS_IFACE_INDEX: - /* BIOS resources are required, so return error if not - * available - */ - if (!res) { - dev_err(&pdev->dev, - "Failed to get PUNIT MEM resource %d\n", - pindex); - return -ENXIO; - } - case PLAT_RESOURCE_ISP_DATA_INDEX: - case PLAT_RESOURCE_ISP_IFACE_INDEX: - case PLAT_RESOURCE_GTD_DATA_INDEX: - case PLAT_RESOURCE_GTD_IFACE_INDEX: - /* if valid resource found, copy the resource to PUNIT - * resource - */ - if (res) - memcpy(&punit_res[pindex], res, sizeof(*res)); - punit_res[pindex].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "PUNIT memory res: %pR\n", - &punit_res[pindex]); - pindex++; - break; + struct platform_device *pdev; + const struct platform_device_info pdevinfo = { + .parent = ipcdev.dev, + .name = PUNIT_DEVICE_NAME, + .id = -1, + .res = punit_res_array, + .num_res = ARRAY_SIZE(punit_res_array), }; - } - /* Create PUNIT IPC MFD cell */ - punit_cell.name = PUNIT_DEVICE_NAME; - punit_cell.num_resources = ARRAY_SIZE(punit_res); - punit_cell.resources = punit_res; - punit_cell.ignore_resource_conflicts = 1; + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); - return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, - &punit_cell, 1, NULL, 0, NULL); + ipcdev.punit_dev = pdev; + + return 0; } -static int ipc_create_wdt_device(struct platform_device *pdev) +static int ipc_create_tco_device(void) { - static struct resource wdt_ipc_res[2]; + struct platform_device *pdev; struct resource *res; - static struct mfd_cell wdt_cell; - - /* If we have ACPI based watchdog use that instead, othewise create - * a MFD cell for iTCO watchdog - */ - if (acpi_has_watchdog()) - return 0; + const struct platform_device_info pdevinfo = { + .parent = ipcdev.dev, + .name = TCO_DEVICE_NAME, + .id = -1, + .res = tco_res, + .num_res = ARRAY_SIZE(tco_res), + .data = &tco_info, + .size_data = sizeof(tco_info), + }; - /* Get iTCO watchdog resources */ - res = platform_get_resource(pdev, IORESOURCE_IO, - PLAT_RESOURCE_ACPI_IO_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get WDT resource\n"); - return -ENXIO; - } + res = tco_res + TCO_RESOURCE_ACPI_IO; + res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET; + res->end = res->start + TCO_REGS_SIZE - 1; - wdt_ipc_res[0].start = res->start + TCO_BASE_OFFSET; - wdt_ipc_res[0].end = res->start + - TCO_BASE_OFFSET + TCO_REGS_SIZE - 1; - wdt_ipc_res[0].flags = IORESOURCE_IO; - wdt_ipc_res[1].start = res->start + SMI_EN_OFFSET; - wdt_ipc_res[1].end = res->start + - SMI_EN_OFFSET + SMI_EN_SIZE - 1; - wdt_ipc_res[1].flags = IORESOURCE_IO; + res = tco_res + TCO_RESOURCE_SMI_EN_IO; + res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET; + res->end = res->start + SMI_EN_SIZE - 1; - dev_dbg(&pdev->dev, "watchdog res 0: %pR\n", &wdt_ipc_res[0]); - dev_dbg(&pdev->dev, "watchdog res 1: %pR\n", &wdt_ipc_res[1]); + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); - wdt_cell.name = TCO_DEVICE_NAME; - wdt_cell.platform_data = &tco_info; - wdt_cell.pdata_size = sizeof(tco_info); - wdt_cell.num_resources = ARRAY_SIZE(wdt_ipc_res); - wdt_cell.resources = wdt_ipc_res; - wdt_cell.ignore_resource_conflicts = 1; + ipcdev.tco_dev = pdev; - return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, - &wdt_cell, 1, NULL, 0, NULL); + return 0; } -static int ipc_create_telemetry_device(struct platform_device *pdev) +static int ipc_create_telemetry_device(void) { - struct resource telemetry_ipc_res[2]; - struct mfd_cell telemetry_cell; + struct platform_device *pdev; struct resource *res; + const struct platform_device_info pdevinfo = { + .parent = ipcdev.dev, + .name = TELEMETRY_DEVICE_NAME, + .id = -1, + .res = telemetry_res, + .num_res = ARRAY_SIZE(telemetry_res), + }; - /* Get telemetry resources */ - res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_TELEM_SSRAM_INDEX); - if (!res) { - dev_err(&pdev->dev, "Failed to get telemetry resource\n"); - return -ENXIO; - } + res = telemetry_res + TELEMETRY_RESOURCE_PUNIT_SSRAM; + res->start = ipcdev.telem_punit_ssram_base; + res->end = res->start + ipcdev.telem_punit_ssram_size - 1; - telemetry_ipc_res[0].start = res->start + TELEM_PUNIT_SSRAM_OFFSET; - telemetry_ipc_res[0].end = res->start + - TELEM_PUNIT_SSRAM_OFFSET + TELEM_SSRAM_SIZE - 1; - telemetry_ipc_res[0].flags = IORESOURCE_MEM; - telemetry_ipc_res[1].start = res->start + TELEM_PMC_SSRAM_OFFSET; - telemetry_ipc_res[1].end = res->start + - TELEM_PMC_SSRAM_OFFSET + TELEM_SSRAM_SIZE - 1; - telemetry_ipc_res[1].flags = IORESOURCE_MEM; + res = telemetry_res + TELEMETRY_RESOURCE_PMC_SSRAM; + res->start = ipcdev.telem_pmc_ssram_base; + res->end = res->start + ipcdev.telem_pmc_ssram_size - 1; - dev_dbg(&pdev->dev, "Telemetry res 0: %pR\n", &telemetry_ipc_res[0]); - dev_dbg(&pdev->dev, "Telemetry res 1: %pR\n", &telemetry_ipc_res[1]); + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); - telemetry_cell.name = TELEMETRY_DEVICE_NAME; - telemetry_cell.num_resources = ARRAY_SIZE(telemetry_ipc_res); - telemetry_cell.resources = telemetry_ipc_res; - telemetry_cell.ignore_resource_conflicts = 1; + ipcdev.telemetry_dev = pdev; - return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, - &telemetry_cell, 1, NULL, 0, NULL); + return 0; } -static int ipc_create_pmc_devices(struct platform_device *pdev) +static int ipc_create_pmc_devices(void) { int ret; - ret = ipc_create_punit_device(pdev); - if (ret < 0) - return ret; + /* If we have ACPI based watchdog use that instead */ + if (!acpi_has_watchdog()) { + ret = ipc_create_tco_device(); + if (ret) { + dev_err(ipcdev.dev, "Failed to add tco platform device\n"); + return ret; + } + } - ret = ipc_create_wdt_device(pdev); - if (ret < 0) - return ret; + ret = ipc_create_punit_device(); + if (ret) { + dev_err(ipcdev.dev, "Failed to add punit platform device\n"); + platform_device_unregister(ipcdev.tco_dev); + } - ret = ipc_create_telemetry_device(pdev); - if (ret < 0) - return ret; + if (!ipcdev.telem_res_inval) { + ret = ipc_create_telemetry_device(); + if (ret) + dev_warn(ipcdev.dev, + "Failed to add telemetry platform device\n"); + } - return 0; + return ret; } static int ipc_plat_get_res(struct platform_device *pdev) { - struct resource *res; + struct resource *res, *punit_res; void __iomem *addr; + int size; + + res = platform_get_resource(pdev, IORESOURCE_IO, + PLAT_RESOURCE_ACPI_IO_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get io resource\n"); + return -ENXIO; + } + size = resource_size(res); + ipcdev.acpi_io_base = res->start; + ipcdev.acpi_io_size = size; + dev_info(&pdev->dev, "io res: %pR\n", res); - /* Get IPC resources */ + punit_res = punit_res_array; + /* This is index 0 to cover BIOS data register */ res = platform_get_resource(pdev, IORESOURCE_MEM, - PLAT_RESOURCE_IPC_INDEX); + PLAT_RESOURCE_BIOS_DATA_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n"); + return -ENXIO; + } + *punit_res = *res; + dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res); + + /* This is index 1 to cover BIOS interface register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_BIOS_IFACE_INDEX); if (!res) { - dev_err(&pdev->dev, "Failed to get IPC resources\n"); + dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n"); return -ENXIO; } + *++punit_res = *res; + dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res); + + /* This is index 2 to cover ISP data register, optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_ISP_DATA_INDEX); + ++punit_res; + if (res) { + *punit_res = *res; + dev_info(&pdev->dev, "punit ISP data res: %pR\n", res); + } + + /* This is index 3 to cover ISP interface register, optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_ISP_IFACE_INDEX); + ++punit_res; + if (res) { + *punit_res = *res; + dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res); + } - res->end = res->start + - PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE - 1; + /* This is index 4 to cover GTD data register, optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_GTD_DATA_INDEX); + ++punit_res; + if (res) { + *punit_res = *res; + dev_info(&pdev->dev, "punit GTD data res: %pR\n", res); + } + + /* This is index 5 to cover GTD interface register, optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_GTD_IFACE_INDEX); + ++punit_res; + if (res) { + *punit_res = *res; + dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_IPC_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get ipc resource\n"); + return -ENXIO; + } + size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE; + res->end = res->start + size - 1; addr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(addr)) return PTR_ERR(addr); ipcdev.ipc_base = addr; + ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET; - dev_dbg(&pdev->dev, "PMC IPC resource %pR\n", res); + dev_info(&pdev->dev, "ipc res: %pR\n", res); + + ipcdev.telem_res_inval = 0; + res = platform_get_resource(pdev, IORESOURCE_MEM, + PLAT_RESOURCE_TELEM_SSRAM_INDEX); + if (!res) { + dev_err(&pdev->dev, "Failed to get telemetry ssram resource\n"); + ipcdev.telem_res_inval = 1; + } else { + ipcdev.telem_punit_ssram_base = res->start + + TELEM_PUNIT_SSRAM_OFFSET; + ipcdev.telem_punit_ssram_size = TELEM_SSRAM_SIZE; + ipcdev.telem_pmc_ssram_base = res->start + + TELEM_PMC_SSRAM_OFFSET; + ipcdev.telem_pmc_ssram_size = TELEM_SSRAM_SIZE; + dev_info(&pdev->dev, "telemetry ssram res: %pR\n", res); + } return 0; } @@ -800,7 +911,7 @@ static int ipc_plat_probe(struct platform_device *pdev) ipcdev.irq = platform_get_irq(pdev, 0); if (ipcdev.irq < 0) { - dev_err(&pdev->dev, "Failed to get IRQ\n"); + dev_err(&pdev->dev, "Failed to get irq\n"); return -EINVAL; } @@ -810,38 +921,47 @@ static int ipc_plat_probe(struct platform_device *pdev) return ret; } - ret = ipc_create_pmc_devices(pdev); + ret = ipc_create_pmc_devices(); if (ret) { - dev_err(&pdev->dev, "Failed to create PMC devices\n"); + dev_err(&pdev->dev, "Failed to create pmc devices\n"); return ret; } - ret = devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND, - "intel_pmc_ipc", &ipcdev); - if (ret) { - dev_err(&pdev->dev, "Failed to request IRQ\n"); - return ret; + if (devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND, + "intel_pmc_ipc", &ipcdev)) { + dev_err(&pdev->dev, "Failed to request irq\n"); + ret = -EBUSY; + goto err_irq; } ret = sysfs_create_group(&pdev->dev.kobj, &intel_ipc_group); if (ret) { dev_err(&pdev->dev, "Failed to create sysfs group %d\n", ret); - devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); - return ret; + goto err_sys; } ipcdev.has_gcr_regs = true; return 0; +err_sys: + devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); +err_irq: + platform_device_unregister(ipcdev.tco_dev); + platform_device_unregister(ipcdev.punit_dev); + platform_device_unregister(ipcdev.telemetry_dev); + + return ret; } static int ipc_plat_remove(struct platform_device *pdev) { sysfs_remove_group(&pdev->dev.kobj, &intel_ipc_group); devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev); + platform_device_unregister(ipcdev.tco_dev); + platform_device_unregister(ipcdev.punit_dev); + platform_device_unregister(ipcdev.telemetry_dev); ipcdev.dev = NULL; - return 0; } @@ -860,12 +980,12 @@ static int __init intel_pmc_ipc_init(void) ret = platform_driver_register(&ipc_plat_driver); if (ret) { - pr_err("Failed to register PMC IPC platform driver\n"); + pr_err("Failed to register PMC ipc platform driver\n"); return ret; } ret = pci_register_driver(&ipc_pci_driver); if (ret) { - pr_err("Failed to register PMC IPC PCI driver\n"); + pr_err("Failed to register PMC ipc pci driver\n"); platform_driver_unregister(&ipc_plat_driver); return ret; } -- cgit v1.2.3 From 8b9528a6d9a901b9f933231505fef5630e80ce5a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 9 Nov 2017 11:49:09 -0600 Subject: platform/x86: dell-wmi-descriptor: check if memory was allocated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devm_kzalloc will return NULL pointer if no memory was allocated. This should be checked. This problem also existed when the driver was dell-wmi.c. Signed-off-by: Mario Limonciello Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-wmi-descriptor.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c index 3204c408e261..28ef5f37cfbf 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -121,6 +121,11 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev) 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; -- cgit v1.2.3 From 868b8d33f91e431b1961a35baa6b5022639067f3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 9 Nov 2017 11:49:10 -0600 Subject: platform/x86: dell-*wmi*: Relay failed initial probe to dependent drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dell-wmi and dell-smbios-wmi are dependent upon dell-wmi-descriptor finishing probe successfully to probe themselves. Currently if dell-wmi-descriptor fails probing in a non-recoverable way (such as invalid header) dell-wmi and dell-smbios-wmi will continue to try to redo probing due to deferred probing. To solve this have the dependent drivers query the dell-wmi-descriptor driver whether the descriptor has been determined valid. The possible results are: -ENODEV: Descriptor GUID missing from WMI bus -EPROBE_DEFER: Descriptor not yet probed, dependent driver should wait and use deferred probing < 0: Descriptor probed, invalid. Dependent driver should return an error. 0: Successful descriptor probe, dependent driver can continue Successful descriptor probe still doesn't mean that the descriptor driver is necessarily bound at the time of initialization of dependent driver. Userspace can unbind the driver, so all methods used from driver should still be verified to return success values otherwise deferred probing be used. Signed-off-by: Mario Limonciello Reviewed-by: Pali Rohár Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-smbios-wmi.c | 5 +++-- drivers/platform/x86/dell-wmi-descriptor.c | 16 ++++++++++++++++ drivers/platform/x86/dell-wmi-descriptor.h | 8 +++++++- drivers/platform/x86/dell-wmi.c | 6 ++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index 5cf9b13ce6e6..0cab1f9c35af 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -151,8 +151,9 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev) int count; int ret; - if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) - return -ENODEV; + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), GFP_KERNEL); diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c index 28ef5f37cfbf..4dfef1f53481 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -21,14 +21,26 @@ #include #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; @@ -91,6 +103,7 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev) if (obj->type != ACPI_TYPE_BUFFER) { dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); ret = -EINVAL; + descriptor_valid = ret; goto out; } @@ -102,6 +115,7 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev) "Dell descriptor buffer has unexpected length (%d)\n", obj->buffer.length); ret = -EINVAL; + descriptor_valid = ret; goto out; } @@ -111,8 +125,10 @@ static int dell_wmi_descriptor_probe(struct wmi_device *wdev) 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", diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell-wmi-descriptor.h index 5f7b69c2c83a..1e8cb96ffd78 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.h +++ b/drivers/platform/x86/dell-wmi-descriptor.h @@ -13,7 +13,13 @@ #include -#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" +/* 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); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 54321080a30d..39d2f4518483 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -655,9 +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 ret; - if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) - return -ENODEV; + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; priv = devm_kzalloc( &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); -- cgit v1.2.3 From 5246741a3f2e0285394cf74f3105cb252b8f38ad Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 15 Nov 2017 14:21:13 -0600 Subject: platform/x86: dell-laptop: Allocate buffer before rfkill use On machines using rfkill interface the buffer needs to have been allocated before the initial use (memset) of it. Reported-by: Valdis Kletnieks Signed-off-by: Mario Limonciello Tested-by: Valdis Kletnieks Signed-off-by: Darren Hart (VMware) --- drivers/platform/x86/dell-laptop.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index c4903c5ce7cf..2d704361f672 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -2073,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) { @@ -2080,10 +2085,6 @@ static int __init dell_init(void) goto fail_rfkill; } - buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); - if (!buffer) - goto fail_buffer; - if (quirks && quirks->touchpad_led) touchpad_led_init(&platform_device->dev); -- cgit v1.2.3 From aaa40965d2342137d756121993c395e2a7463a8d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 19 Oct 2017 09:17:28 +0200 Subject: platform/x86: silead_dmi: Add silead, home-button property to some tablets Add "silead,home-button" property to entries for tablets which have a capacitive home button (typically a windows logo on the front). This new property is checked for by the new capacitive home button support in the silead touchscreen driver. Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/silead_dmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index 5af3379b0573..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"), { } }; @@ -116,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"), { } }; @@ -143,6 +146,7 @@ static const struct property_entry chuwi_hi8_pro_props[] = { 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"), { } }; -- cgit v1.2.3