2017-09-04 08:21:49

by Corentin Chary

[permalink] [raw]
Subject: [PATCH] drivers/x86: add thinkpad-wmi

This driver has been available on https://github.com/iksaif/thinkpad-wmi for
a few year and is already deployed on large fleets of thinkpad laptops.

The WMI interface is documented here: http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf
It mostly focused on changing BIOS/Firmware settings.

Signed-off-by: Corentin Chary <[email protected]>
---
.../ABI/testing/sysfs-platform-thinkpad-wmi | 50 +
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/thinkpad-wmi.c | 1217 ++++++++++++++++++++
4 files changed, 1278 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
create mode 100644 drivers/platform/x86/thinkpad-wmi.c

diff --git a/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
new file mode 100644
index 000000000000..c3673876c5b3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
@@ -0,0 +1,50 @@
+What: /sys/devices/platform/thinkpad-wmi/password
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ BIOS password needs to be written in this file if set
+ to be able to change BIOS settings.
+
+What: /sys/devices/platform/thinkpad-wmi/password_encoding
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Password encoding ('ascii' or 'scanmode').
+
+What: /sys/devices/platform/thinkpad-wmi/password_kbd_lang
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Keyboard language used for password. One of 'us', 'fr' and 'gr'.
+
+What: /sys/devices/platform/thinkpad-wmi/password_type
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Password type to be changed when password_change is written to, e.g. 'pap'.
+
+What: /sys/devices/platform/thinkpad-wmi/password_change
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Writing to this file will set the password specified in password_type.
+ The new password will not take effect until the next reboot.
+
+What: /sys/devices/platform/thinkpad-wmi/password_settings
+Date: Oct 2015
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Display various password settings.
+
+What: /sys/devices/platform/thinkpad-wmi/load_default_settings
+Date: Oct 2015
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Write anything to this file to load default BIOS settings.
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 80b87954f6dd..4e2e8a04228a 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -511,6 +511,16 @@ config THINKPAD_ACPI_HOTKEY_POLL
If you are not sure, say Y here. The driver enables polling only if
it is strictly necessary to do so.

+config THINKPAD_WMI
+ tristate "THINKPAD WMI Driver (EXPERIMENTAL)"
+ depends on ACPI_WMI
+ ---help---
+ This driver allow you to modify BIOS passwords, settings, and boot order
+ using Windows Management Instrumentation (WMI) through the Lenovo
+ client-management interface.
+
+ Say Y here if you have a WMI aware Thinkpad.
+
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 91cec1751461..3b03f0744794 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
+obj-$(CONFIG_THINKPAD_WMI) += thinkpad-wmi.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o
diff --git a/drivers/platform/x86/thinkpad-wmi.c b/drivers/platform/x86/thinkpad-wmi.c
new file mode 100644
index 000000000000..9f2d6d2c3e16
--- /dev/null
+++ b/drivers/platform/x86/thinkpad-wmi.c
@@ -0,0 +1,1217 @@
+/*
+ * Thinkpad WMI hotkey driver
+ *
+ * Copyright(C) 2012 Corentin Chary <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/acpi.h>
+
+#define THINKPAD_WMI_FILE "thinkpad-wmi"
+
+MODULE_AUTHOR("Corentin Chary <[email protected]>");
+MODULE_DESCRIPTION("Thinkpad WMI Driver");
+MODULE_LICENSE("GPL");
+
+/* WMI inteface */
+
+/**
+ * Name:
+ * Lenovo_BiosSetting
+ * Description:
+ * Get item name and settings for current WMI instance.
+ * Type:
+ * Query
+ * Returns:
+ * "Item,Value"
+ * Example:
+ * "WakeOnLAN,Enable"
+ */
+#define LENOVO_BIOS_SETTING_GUID \
+ "51F5230E-9677-46CD-A1CF-C0B23EE34DB7"
+
+/**
+ * Name:
+ * Lenovo_SetBiosSetting
+ * Description:
+ * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting
+ * class. To save the settings, use the Lenovo_SaveBiosSetting class.
+ * BIOS settings and values are case sensitive.
+ * After making changes to the BIOS settings, you must reboot the computer
+ * before the changes will take effect.
+ * Type:
+ * Method
+ * Arguments:
+ * "Item,Value,Password,Encoding,KbdLang;"
+ * Example:
+ * "WakeOnLAN,Disable,pswd,ascii,us;"
+ */
+#define LENOVO_SET_BIOS_SETTINGS_GUID \
+ "98479A64-33F5-4E33-A707-8E251EBBC3A1"
+
+/**
+ * Name:
+ * Lenovo_SaveBiosSettings
+ * Description:
+ * Save any pending changes in settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_SAVE_BIOS_SETTINGS_GUID \
+ "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3"
+
+
+/**
+ * Name:
+ * Lenovo_DiscardBiosSettings
+ * Description:
+ * Discard any pending changes in settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_DISCARD_BIOS_SETTINGS_GUID \
+ "74F1EBB6-927A-4C7D-95DF-698E21E80EB5"
+
+/**
+ * Name:
+ * Lenovo_LoadDefaultSettings
+ * Description:
+ * Load default BIOS settings. Use Lenovo_SaveBiosSettings to save the
+ * settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_LOAD_DEFAULT_SETTINGS_GUID \
+ "7EEF04FF-4328-447C-B5BB-D449925D538D"
+
+/**
+ * Name:
+ * Lenovo_BiosPasswordSettings
+ * Description:
+ * Return BIOS Password settings
+ * Type:
+ * Query
+ * Returns:
+ * PasswordMode, PasswordState, MinLength, MaxLength,
+ * SupportedEncoding, SupportedKeyboard
+ */
+#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID \
+ "8ADB159E-1E32-455C-BC93-308A7ED98246"
+
+/**
+ * Name:
+ * Lenovo_SetBiosPassword
+ * Description:
+ * Change a specific password.
+ * - BIOS settings cannot be changed at the same boot as power-on
+ * passwords (POP) and hard disk passwords (HDP). If you want to change
+ * BIOS settings and POP or HDP, you must reboot the system after changing
+ * one of them.
+ * - A password cannot be set using this method when one does not already
+ * exist. Passwords can only be updated or cleared.
+ * Type:
+ * Method
+ * Arguments:
+ * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
+ * Example:
+ * "pop,oldpop,newpop,ascii,us;”
+ */
+#define LENOVO_SET_BIOS_PASSWORD_GUID \
+ "2651D9FD-911C-4B69-B94E-D0DED5963BD7"
+
+/**
+ * Name:
+ * Lenovo_GetBiosSelections
+ * Description:
+ * Return a list valid settings for a given item.
+ * Type:
+ * Method
+ * Arguments:
+ * "Item"
+ * Returns:
+ * "Value1,Value2,Value3,..."
+ * Example:
+ * -> "FlashOverLAN"
+ * <- "Enabled,Disabled"
+ */
+#define LENOVO_GET_BIOS_SELECTIONS_GUID \
+ "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B"
+
+/**
+ * Name:
+ * ???
+ * Type:
+ * Method
+ * Arguments:
+ * ???
+ * Example:
+ * ???
+ * WMI-Internals:
+ * Return big chunk of data
+ */
+#define LENOVO_QUERY_GUID \
+ "05901221-D566-11D1-B2F0-00A0C9062910"
+
+/* Return values */
+
+enum {
+ /*
+ * "Success"
+ * Operation completed successfully.
+ */
+ THINKPAD_WMI_SUCCESS = 0,
+ /*
+ * "Not Supported"
+ * The feature is not supported on this system.
+ */
+ THINKPAD_WMI_NOT_SUPPORTED = -ENODEV,
+ /*
+ * "Invalid"
+ * The item or value provided is not valid parameter
+ */
+ THINKPAD_WMI_INVALID = -EINVAL,
+ /*
+ * "Access Denied"
+ * The change could not be made due to an authentication problem.
+ * If a supervisor password exists, the correct supervisor password
+ * must be provided.
+ */
+ THINKPAD_WMI_ACCESS_DENIED = -EPERM,
+ /* "System Busy"
+ * BIOS changes have already been made that need to be committed.
+ * Reboot the system and try again.
+ */
+ THINKPAD_WMI_SYSTEM_BUSY = -EBUSY
+};
+
+/* Only add an alias on this one, since it's the one used
+ * in thinkpad_wmi_probe */
+MODULE_ALIAS("wmi:"LENOVO_BIOS_SETTING_GUID);
+
+struct thinkpad_wmi_pcfg {
+ uint32_t password_mode;
+ uint32_t password_state;
+ uint32_t min_length;
+ uint32_t max_length;
+ uint32_t supported_encodings;
+ uint32_t supported_keyboard;
+};
+
+/*
+ * thinkpad_wmi/ - debugfs root directory
+ * bios_settings
+ * bios_setting
+ * list_valid_choices
+ * set_bios_settings
+ * save_bios_settings
+ * discard_bios_settings
+ * load_default
+ * set_bios_password
+ * argument
+ * instance
+ * instance_count
+ * bios_password_settings
+ */
+struct thinkpad_wmi_debug {
+ struct dentry *root;
+
+ u8 instances_count;
+ u8 instance;
+ char argument[512];
+};
+
+struct thinkpad_wmi {
+ struct platform_device *platform_device;
+
+ int settings_count;
+
+ char password[64];
+ char password_encoding[64];
+ char password_kbdlang[4]; /* 2 bytes for \n\0 */
+ char auth_string[256];
+ char password_type[64];
+
+ bool can_set_bios_settings;
+ bool can_discard_bios_settings;
+ bool can_load_default_settings;
+ bool can_get_bios_selections;
+ bool can_set_bios_password;
+ bool can_get_password_settings;
+
+ char *settings[256];
+ struct dev_ext_attribute *devattrs;
+ struct thinkpad_wmi_debug debug;
+};
+
+/* helpers */
+static int thinkpad_wmi_errstr_to_err(const char *errstr)
+{
+ if (!strcmp(errstr, "Success"))
+ return THINKPAD_WMI_SUCCESS;
+ if (!strcmp(errstr, "Not Supported"))
+ return THINKPAD_WMI_NOT_SUPPORTED;
+ if (!strcmp(errstr, "Invalid"))
+ return THINKPAD_WMI_INVALID;
+ if (!strcmp(errstr, "Access Denied"))
+ return THINKPAD_WMI_ACCESS_DENIED;
+ if (!strcmp(errstr, "System Busy"))
+ return THINKPAD_WMI_SYSTEM_BUSY;
+
+ pr_debug("Unknown error string: '%s'", errstr);
+
+ return -EINVAL;
+}
+
+static int thinkpad_wmi_extract_error(const struct acpi_buffer *output)
+{
+ const union acpi_object *obj;
+ int ret;
+
+ obj = output->pointer;
+ if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+ return -EIO;
+
+ ret = thinkpad_wmi_errstr_to_err(obj->string.pointer);
+ kfree(obj);
+ return ret;
+}
+
+static int thinkpad_wmi_simple_call(const char *guid,
+ const char *arg)
+{
+ const struct acpi_buffer input = { strlen(arg), (char *)arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_evaluate_method(guid, 0, 0, &input, &output);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_error(&output);
+}
+
+static int thinkpad_wmi_extract_output_string(const struct acpi_buffer *output,
+ char **string)
+{
+ const union acpi_object *obj;
+
+ obj = output->pointer;
+ if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+ return -EIO;
+
+ *string = kstrdup(obj->string.pointer, GFP_KERNEL);
+ kfree(obj);
+ return *string ? 0 : -ENOMEM;
+}
+
+static int thinkpad_wmi_bios_setting(int item, char **value)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_query_block(LENOVO_BIOS_SETTING_GUID, item, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_get_bios_selections(const char *item, char **value)
+{
+ const struct acpi_buffer input = { strlen(item), (char *)item };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID,
+ 0, 0, &input, &output);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_set_bios_settings(const char *settings)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID,
+ settings);
+}
+
+static int thinkpad_wmi_save_bios_settings(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_discard_bios_settings(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_DISCARD_BIOS_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_load_default(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_LOAD_DEFAULT_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_set_bios_password(const char *settings)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID,
+ settings);
+}
+
+static int thinkpad_wmi_password_settings(struct thinkpad_wmi_pcfg *pcfg)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ const union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0,
+ &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer)
+ return -EIO;
+ if (obj->buffer.length != sizeof(*pcfg)) {
+ pr_warn("Unknown pcfg buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return -EIO;
+ }
+
+ memcpy(pcfg, obj->buffer.pointer, obj->buffer.length);
+ kfree(obj);
+ return 0;
+}
+
+/* sysfs */
+
+#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr)
+
+static ssize_t show_setting(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ int item = (uintptr_t)ea->var;
+ char *name = thinkpad->settings[item];
+ char *settings = NULL, *choices = NULL, *value;
+ ssize_t count = 0;
+ int ret;
+
+ ret = thinkpad_wmi_bios_setting(item, &settings);
+ if (ret)
+ return ret;
+ if (!settings)
+ return -EIO;
+
+ if (thinkpad->can_get_bios_selections) {
+ ret = thinkpad_wmi_get_bios_selections(name, &choices);
+ if (ret)
+ goto error;
+ if (!choices || !*choices) {
+ ret = -EIO;
+ goto error;
+ }
+ }
+
+ value = strchr(settings, ',');
+ if (!value)
+ goto error;
+ value++;
+
+ count = sprintf(buf, "%s\n", value);
+ if (choices)
+ count += sprintf(buf + count, "%s\n", choices);
+
+error:
+ kfree(settings);
+ kfree(choices);
+ return ret ? ret : count;
+}
+
+static ssize_t store_setting(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ int item_idx = (uintptr_t)ea->var;
+ const char *item = thinkpad->settings[item_idx];
+ int ret;
+ size_t buffer_size;
+ char *buffer;
+
+ /* Format: 'Item,Value,Authstring;' */
+ buffer_size = (strlen(item) + 1 + count + 1 +
+ sizeof(thinkpad->auth_string) + 2);
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ strcpy(buffer, item);
+ strcat(buffer, ",");
+ strncat(buffer, buf, count);
+ if (count)
+ strim(buffer);
+ if (*thinkpad->auth_string) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->auth_string);
+ }
+ strcat(buffer, ";");
+
+ ret = thinkpad_wmi_set_bios_settings(buffer);
+ if (ret)
+ goto end;
+
+ ret = thinkpad_wmi_save_bios_settings(thinkpad->auth_string);
+ if (ret) {
+ /* Try to discard the settings if we failed to apply them. */
+ thinkpad_wmi_discard_bios_settings(thinkpad->auth_string);
+ goto end;
+ }
+ ret = count;
+
+end:
+ kfree(buffer);
+ return ret;
+}
+
+
+/* Password related sysfs methods */
+static ssize_t show_auth(struct thinkpad_wmi *thinkpad, char *buf,
+ const char *data, size_t size)
+{
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return sprintf(buf, "%s\n", data ? : "(nil)");
+}
+
+/* Create the auth string from password chunks */
+static void update_auth_string(struct thinkpad_wmi *thinkpad)
+{
+ if (!*thinkpad->password) {
+ /* No password at all */
+ thinkpad->auth_string[0] = '\0';
+ return;
+ }
+ strcpy(thinkpad->auth_string, thinkpad->password);
+
+ if (*thinkpad->password_encoding) {
+ strcat(thinkpad->auth_string, ",");
+ strcat(thinkpad->auth_string, thinkpad->password_encoding);
+ }
+
+ if (*thinkpad->password_kbdlang) {
+ strcat(thinkpad->auth_string, ",");
+ strcat(thinkpad->auth_string, thinkpad->password_kbdlang);
+ }
+}
+
+static ssize_t store_auth(struct thinkpad_wmi *thinkpad,
+ const char *buf, size_t count,
+ char *dst, size_t size)
+{
+ ssize_t ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (count > size - 1)
+ return -EINVAL;
+
+ /* dst may be being reused, NUL-terminate */
+ ret = strscpy(dst, buf, size);
+ if (ret < 0)
+ return ret;
+ if (count)
+ strim(dst);
+
+ update_auth_string(thinkpad);
+
+ return count;
+}
+
+#define THINKPAD_WMI_CREATE_AUTH_ATTR(_name, _uname, _mode) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev); \
+ \
+ return show_auth(thinkpad, buf, \
+ thinkpad->_name, \
+ sizeof(thinkpad->_name)); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev); \
+ \
+ return store_auth(thinkpad, buf, count, \
+ thinkpad->_name, \
+ sizeof(thinkpad->_name)); \
+ } \
+ static struct device_attribute dev_attr_##_name = { \
+ .attr = { \
+ .name = _uname, \
+ .mode = _mode }, \
+ .show = show_##_name, \
+ .store = store_##_name, \
+ }
+
+THINKPAD_WMI_CREATE_AUTH_ATTR(password, "password", S_IRUSR|S_IWUSR);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_encoding, "password_encoding",
+ S_IRUSR|S_IWUSR);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_kbdlang, "password_kbd_lang",
+ S_IRUSR|S_IWUSR);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_type, "password_type", S_IRUSR|S_IWUSR);
+
+static ssize_t show_password_settings(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct thinkpad_wmi_pcfg pcfg;
+ ssize_t ret;
+
+ ret = thinkpad_wmi_password_settings(&pcfg);
+ if (ret)
+ return ret;
+ ret += sprintf(buf, "password_mode: %#x\n", pcfg.password_mode);
+ ret += sprintf(buf + ret, "password_state: %#x\n",
+ pcfg.password_state);
+ ret += sprintf(buf + ret, "min_length: %d\n", pcfg.min_length);
+ ret += sprintf(buf + ret, "max_length: %d\n", pcfg.max_length);
+ ret += sprintf(buf + ret, "supported_encodings: %#x\n",
+ pcfg.supported_encodings);
+ ret += sprintf(buf + ret, "supported_keyboard: %#x\n",
+ pcfg.supported_keyboard);
+ return ret;
+}
+
+static DEVICE_ATTR(password_settings, S_IRUSR, show_password_settings, NULL);
+
+static ssize_t store_password_change(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ size_t buffer_size;
+ char *buffer;
+ ssize_t ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
+
+ /* auth_string is the size of CurrentPassword,Encoding,KbdLang */
+ buffer_size = (sizeof(thinkpad->password_type) + 1 + count + 1 +
+ sizeof(thinkpad->auth_string) + 2);
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ strcpy(buffer, thinkpad->password_type);
+
+ if (*thinkpad->password) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password);
+ }
+ strcat(buffer, ",");
+ strncat(buffer, buf, count);
+ if (count)
+ strim(buffer);
+
+ if (*thinkpad->password_encoding) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password_encoding);
+ }
+ if (*thinkpad->password_kbdlang) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password_kbdlang);
+ }
+ strcat(buffer, ";");
+
+ ret = thinkpad_wmi_set_bios_password(buffer);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static struct device_attribute dev_attr_password_change = {
+ .attr = {
+ .name = "password_change",
+ .mode = S_IWUSR },
+ .store = store_password_change,
+};
+
+
+static ssize_t store_load_default(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+
+ return thinkpad_wmi_load_default(thinkpad->auth_string);
+}
+
+static DEVICE_ATTR(load_default_settings, S_IWUSR, NULL, store_load_default);
+
+static struct attribute *platform_attributes[] = {
+ &dev_attr_password_settings.attr,
+ &dev_attr_password.attr,
+ &dev_attr_password_encoding.attr,
+ &dev_attr_password_kbdlang.attr,
+ &dev_attr_password_type.attr,
+ &dev_attr_password_change.attr,
+ &dev_attr_load_default_settings.attr,
+ NULL
+};
+
+static umode_t thinkpad_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ bool supported = true;
+
+ return supported ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+ .is_visible = thinkpad_sysfs_is_visible,
+ .attrs = platform_attributes
+};
+
+static void thinkpad_wmi_sysfs_exit(struct platform_device *device)
+{
+ struct thinkpad_wmi *thinkpad = platform_get_drvdata(device);
+ int i;
+
+ sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
+
+ if (!thinkpad->devattrs)
+ return;
+
+ for (i = 0; i < thinkpad->settings_count; ++i) {
+ struct dev_ext_attribute *deveattr = &thinkpad->devattrs[i];
+ struct device_attribute *devattr = &deveattr->attr;
+
+ if (devattr->attr.name)
+ device_remove_file(&device->dev, devattr);
+ }
+ kfree(thinkpad->devattrs);
+ thinkpad->devattrs = NULL;
+}
+
+static int __init thinkpad_wmi_sysfs_init(struct platform_device *device)
+{
+ struct thinkpad_wmi *thinkpad = platform_get_drvdata(device);
+ struct dev_ext_attribute *devattrs;
+ int count = thinkpad->settings_count;
+ int i, ret;
+
+ devattrs = kmalloc(sizeof(*devattrs) * count, GFP_KERNEL);
+ if (!devattrs)
+ return -ENOMEM;
+ thinkpad->devattrs = devattrs;
+
+ for (i = 0; i < count; ++i) {
+ struct dev_ext_attribute *deveattr = &devattrs[i];
+ struct device_attribute *devattr = &deveattr->attr;
+
+ sysfs_attr_init(&devattr->attr);
+ devattr->attr.name = thinkpad->settings[i];
+ devattr->attr.mode = S_IRUGO | S_IWUSR;
+ devattr->show = show_setting;
+ devattr->store = store_setting;
+ deveattr->var = (void *)(uintptr_t)i;
+ ret = device_create_file(&device->dev, devattr);
+ if (ret) {
+ /* Name is used to check is file has been created. */
+ devattr->attr.name = NULL;
+ return ret;
+ }
+ }
+
+ return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+/*
+ * Platform device
+ */
+static int __init thinkpad_wmi_platform_init(struct thinkpad_wmi *thinkpad)
+{
+ return thinkpad_wmi_sysfs_init(thinkpad->platform_device);
+}
+
+static void thinkpad_wmi_platform_exit(struct thinkpad_wmi *thinkpad)
+{
+ thinkpad_wmi_sysfs_exit(thinkpad->platform_device);
+}
+
+/* debugfs */
+
+static ssize_t dbgfs_write_argument(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *pos)
+{
+ struct thinkpad_wmi *thinkpad = file->f_path.dentry->d_inode->i_private;
+ char *kernbuf = thinkpad->debug.argument;
+ size_t size = sizeof(thinkpad->debug.argument);
+
+ if (count > PAGE_SIZE - 1)
+ return -EINVAL;
+
+ if (count > size - 1)
+ return -EINVAL;
+
+ if (copy_from_user(kernbuf, userbuf, count))
+ return -EFAULT;
+
+ kernbuf[count] = 0;
+
+ strim(kernbuf);
+
+ return count;
+}
+
+static int dbgfs_show_argument(struct seq_file *m, void *v)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ seq_printf(m, "%s\n", thinkpad->debug.argument);
+ return 0;
+}
+
+static int thinkpad_wmi_debugfs_argument_open(struct inode *inode,
+ struct file *file)
+{
+ struct thinkpad_wmi *thinkpad = inode->i_private;
+
+ return single_open(file, dbgfs_show_argument, thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_argument_fops = {
+ .owner = THIS_MODULE,
+ .open = thinkpad_wmi_debugfs_argument_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = dbgfs_write_argument,
+};
+
+struct thinkpad_wmi_debugfs_node {
+ struct thinkpad_wmi *thinkpad;
+ char *name;
+ int (*show)(struct seq_file *m, void *data);
+};
+
+static void show_bios_setting_line(struct thinkpad_wmi *thinkpad,
+ struct seq_file *m, int i, bool list_valid)
+{
+ int ret;
+ char *settings = NULL, *choices = NULL, *p;
+
+ ret = thinkpad_wmi_bios_setting(i, &settings);
+ if (ret || !settings)
+ return;
+
+ p = strchr(settings, ',');
+ if (p)
+ *p = '=';
+ seq_printf(m, "%s", settings);
+
+
+ if (!thinkpad->can_get_bios_selections)
+ goto line_feed;
+
+ if (p)
+ *p = '\0';
+
+ ret = thinkpad_wmi_get_bios_selections(settings, &choices);
+ if (ret || !choices || !*choices)
+ goto line_feed;
+
+ seq_printf(m, "\t[%s]", choices);
+
+line_feed:
+ kfree(settings);
+ kfree(choices);
+ seq_puts(m, "\n");
+}
+
+static int dbgfs_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+ int i;
+
+ for (i = 0; i < thinkpad->settings_count; ++i)
+ show_bios_setting_line(thinkpad, m, i, true);
+
+ return 0;
+}
+
+static int dbgfs_bios_setting(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ show_bios_setting_line(m->private, m, thinkpad->debug.instance, false);
+ return 0;
+}
+
+static int dbgfs_list_valid_choices(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+ char *choices = NULL;
+ int ret;
+
+ ret = thinkpad_wmi_get_bios_selections(thinkpad->debug.argument,
+ &choices);
+
+ if (ret || !choices || !*choices) {
+ kfree(choices);
+ return -EIO;
+ }
+
+ seq_printf(m, "%s\n", choices);
+ kfree(choices);
+ return 0;
+}
+
+static int dbgfs_set_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_set_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_save_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_save_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_discard_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_discard_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_load_default(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_load_default(thinkpad->debug.argument);
+}
+
+static int dbgfs_set_bios_password(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_set_bios_password(thinkpad->debug.argument);
+}
+
+static int dbgfs_bios_password_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi_pcfg pcfg;
+ int ret;
+
+ ret = thinkpad_wmi_password_settings(&pcfg);
+ if (ret)
+ return ret;
+ seq_printf(m, "password_mode: %#x\n", pcfg.password_mode);
+ seq_printf(m, "password_state: %#x\n", pcfg.password_state);
+ seq_printf(m, "min_length: %d\n", pcfg.min_length);
+ seq_printf(m, "max_length: %d\n", pcfg.max_length);
+ seq_printf(m, "supported_encodings: %#x\n", pcfg.supported_encodings);
+ seq_printf(m, "supported_keyboard: %#x\n", pcfg.supported_keyboard);
+ return 0;
+}
+
+static struct thinkpad_wmi_debugfs_node thinkpad_wmi_debug_files[] = {
+ { NULL, "bios_settings", dbgfs_bios_settings },
+ { NULL, "bios_setting", dbgfs_bios_setting },
+ { NULL, "list_valid_choices", dbgfs_list_valid_choices },
+ { NULL, "set_bios_settings", dbgfs_set_bios_settings },
+ { NULL, "save_bios_settings", dbgfs_save_bios_settings },
+ { NULL, "discard_bios_settings", dbgfs_discard_bios_settings },
+ { NULL, "load_default", dbgfs_load_default },
+ { NULL, "set_bios_password", dbgfs_set_bios_password },
+ { NULL, "bios_password_settings", dbgfs_bios_password_settings },
+};
+
+static int thinkpad_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct thinkpad_wmi_debugfs_node *node = inode->i_private;
+
+ return single_open(file, node->show, node->thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_io_ops = {
+ .owner = THIS_MODULE,
+ .open = thinkpad_wmi_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void __init thinkpad_wmi_debugfs_exit(struct thinkpad_wmi *thinkpad)
+{
+ debugfs_remove_recursive(thinkpad->debug.root);
+}
+
+static int thinkpad_wmi_debugfs_init(struct thinkpad_wmi *thinkpad)
+{
+ struct dentry *dent;
+ int i;
+
+ thinkpad->debug.instances_count = thinkpad->settings_count;
+
+ thinkpad->debug.root = debugfs_create_dir(THINKPAD_WMI_FILE, NULL);
+ if (!thinkpad->debug.root) {
+ pr_err("failed to create debugfs directory");
+ goto error_debugfs;
+ }
+
+ dent = debugfs_create_file("argument", S_IRUGO | S_IWUSR,
+ thinkpad->debug.root, thinkpad,
+ &thinkpad_wmi_debugfs_argument_fops);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("instance", S_IRUGO | S_IWUSR,
+ thinkpad->debug.root,
+ &thinkpad->debug.instance);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("instances_count", S_IRUGO,
+ thinkpad->debug.root,
+ &thinkpad->debug.instances_count);
+ if (!dent)
+ goto error_debugfs;
+
+ for (i = 0; i < ARRAY_SIZE(thinkpad_wmi_debug_files); i++) {
+ struct thinkpad_wmi_debugfs_node *node;
+
+ node = &thinkpad_wmi_debug_files[i];
+
+ /* Filter non-present interfaces */
+ if (!strcmp(node->name, "set_bios_settings") &&
+ !thinkpad->can_set_bios_settings)
+ continue;
+ if (!strcmp(node->name, "dicard_bios_settings") &&
+ !thinkpad->can_discard_bios_settings)
+ continue;
+ if (!strcmp(node->name, "load_default_settings") &&
+ !thinkpad->can_load_default_settings)
+ continue;
+ if (!strcmp(node->name, "get_bios_selections") &&
+ !thinkpad->can_get_bios_selections)
+ continue;
+ if (!strcmp(node->name, "set_bios_password") &&
+ !thinkpad->can_set_bios_password)
+ continue;
+ if (!strcmp(node->name, "bios_password_settings") &&
+ !thinkpad->can_get_password_settings)
+ continue;
+
+ node->thinkpad = thinkpad;
+ dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+ thinkpad->debug.root, node,
+ &thinkpad_wmi_debugfs_io_ops);
+ if (!dent) {
+ pr_err("failed to create debug file: %s\n", node->name);
+ goto error_debugfs;
+ }
+ }
+
+
+ return 0;
+
+error_debugfs:
+ thinkpad_wmi_debugfs_exit(thinkpad);
+ return -ENOMEM;
+}
+
+/* Base driver */
+static void __init thinkpad_wmi_analyze(struct thinkpad_wmi *thinkpad)
+{
+ acpi_status status;
+ int i = 0;
+
+ /* Try to find the number of valid settings of this machine
+ * and use it to create sysfs attributes */
+ for (i = 0; i < 0xFF; ++i) {
+ char *item = NULL;
+ char *p;
+
+ status = thinkpad_wmi_bios_setting(i, &item);
+ if (ACPI_FAILURE(status))
+ break;
+ if (!item || !*item)
+ break;
+ /* Remove the value part */
+ p = strchr(item, ',');
+ if (p)
+ *p = '\0';
+ thinkpad->settings[i] = item; /* Cache setting name */
+ }
+
+ thinkpad->settings_count = i;
+ pr_info("Found %d settings", thinkpad->settings_count);
+
+ if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) &&
+ wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID)) {
+ thinkpad->can_set_bios_settings = true;
+ }
+
+ if (wmi_has_guid(LENOVO_DISCARD_BIOS_SETTINGS_GUID))
+ thinkpad->can_discard_bios_settings = true;
+
+ if (wmi_has_guid(LENOVO_LOAD_DEFAULT_SETTINGS_GUID))
+ thinkpad->can_load_default_settings = true;
+
+ if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID))
+ thinkpad->can_get_bios_selections = true;
+
+ if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID))
+ thinkpad->can_set_bios_password = true;
+
+ if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID))
+ thinkpad->can_get_password_settings = true;
+}
+
+static int __init thinkpad_wmi_add(struct platform_device *pdev)
+{
+ struct thinkpad_wmi *thinkpad;
+ int err;
+
+ thinkpad = kzalloc(sizeof(struct thinkpad_wmi), GFP_KERNEL);
+ if (!thinkpad)
+ return -ENOMEM;
+
+ thinkpad->platform_device = pdev;
+ platform_set_drvdata(thinkpad->platform_device, thinkpad);
+
+ thinkpad_wmi_analyze(thinkpad);
+
+ err = thinkpad_wmi_platform_init(thinkpad);
+ if (err)
+ goto error_platform;
+
+ err = thinkpad_wmi_debugfs_init(thinkpad);
+ if (err)
+ goto error_debugfs;
+
+ return 0;
+
+error_debugfs:
+ thinkpad_wmi_platform_exit(thinkpad);
+error_platform:
+ kfree(thinkpad);
+ return err;
+}
+
+static int __exit thinkpad_wmi_remove(struct platform_device *device)
+{
+ struct thinkpad_wmi *thinkpad;
+ int i;
+
+ thinkpad = platform_get_drvdata(device);
+ thinkpad_wmi_debugfs_exit(thinkpad);
+ thinkpad_wmi_platform_exit(thinkpad);
+
+ for (i = 0; thinkpad->settings[i]; ++i) {
+ kfree(thinkpad->settings[i]);
+ thinkpad->settings[i] = NULL;
+ }
+
+ kfree(thinkpad);
+ return 0;
+}
+
+static struct platform_device *platform_device;
+
+static int __init thinkpad_wmi_probe(struct platform_device *pdev)
+{
+ if (!wmi_has_guid(LENOVO_BIOS_SETTING_GUID)) {
+ pr_warn("Lenovo_BiosSetting GUID missing\n");
+ return -ENODEV;
+ }
+
+ return thinkpad_wmi_add(pdev);
+}
+
+static struct platform_driver platform_driver = {
+ .remove = __exit_p(thinkpad_wmi_remove),
+ .driver = {
+ .name = THINKPAD_WMI_FILE,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init thinkpad_wmi_init(void)
+{
+ platform_device = platform_create_bundle(&platform_driver,
+ thinkpad_wmi_probe,
+ NULL, 0, NULL, 0);
+ if (IS_ERR(platform_device))
+ return PTR_ERR(platform_device);
+ return 0;
+}
+
+static void __exit thinkpad_wmi_exit(void)
+{
+ platform_device_unregister(platform_device);
+ platform_driver_unregister(&platform_driver);
+}
+
+module_init(thinkpad_wmi_init);
+module_exit(thinkpad_wmi_exit);
--
2.11.0


2017-09-04 17:15:35

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

On Mon, Sep 4, 2017 at 11:21 AM, Corentin Chary
<[email protected]> wrote:
> This driver has been available on https://github.com/iksaif/thinkpad-wmi for
> a few year and is already deployed on large fleets of thinkpad laptops.
>
> The WMI interface is documented here: http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf
> It mostly focused on changing BIOS/Firmware settings.

I will do full review later, few comments right now though.
Thanks for doing this btw.

> +Date: Aug 2017
> +KernelVersion: 4.14

v4.15 apparently

> + * Thinkpad WMI hotkey driver

Solely for hot keys?

> + *
> + * Copyright(C) 2012 Corentin Chary <[email protected]>

2012,2017?


> + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Remove this. It had been changed once, no guarantee it will not again.


> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/debugfs.h>
> +#include <linux/uaccess.h>
> +#include <linux/seq_file.h>
> +#include <linux/platform_device.h>
> +#include <linux/device.h>
> +#include <linux/acpi.h>

Alphabetical.

> +static int __init thinkpad_wmi_init(void)
> +{
> + platform_device = platform_create_bundle(&platform_driver,
> + thinkpad_wmi_probe,
> + NULL, 0, NULL, 0);
> + if (IS_ERR(platform_device))
> + return PTR_ERR(platform_device);
> + return 0;
> +}
> +
> +static void __exit thinkpad_wmi_exit(void)
> +{
> + platform_device_unregister(platform_device);
> + platform_driver_unregister(&platform_driver);
> +}
> +
> +module_init(thinkpad_wmi_init);
> +module_exit(thinkpad_wmi_exit);

I didn't read the code, does it use WMI bus which Andy L. introduced recently?

--
With Best Regards,
Andy Shevchenko

2017-09-05 07:07:32

by Corentin Chary

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

[re-send for the mailing list, I forgot that gmail was stupid]

On Tue, Sep 5, 2017 at 9:05 AM, Corentin Chary <[email protected]> wrote:
>
>
> On Mon, Sep 4, 2017 at 7:15 PM, Andy Shevchenko <[email protected]>
> wrote:
>>
>> On Mon, Sep 4, 2017 at 11:21 AM, Corentin Chary
>> <[email protected]> wrote:
>> > This driver has been available on https://github.com/iksaif/thinkpad-wmi
>> > for
>> > a few year and is already deployed on large fleets of thinkpad laptops.
>> >
>> > The WMI interface is documented here:
>> > http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf
>> > It mostly focused on changing BIOS/Firmware settings.
>>
>> I will do full review later, few comments right now though.
>> Thanks for doing this btw.
>>
>> > +Date: Aug 2017
>> > +KernelVersion: 4.14
>>
>> v4.15 apparently
>
>
> Done
>
>>
>>
>> > + * Thinkpad WMI hotkey driver
>>
>> Solely for hot keys?
>>
>
> /hotkey/configuration/
>
>>
>> > + *
>> > + * Copyright(C) 2012 Corentin Chary <[email protected]>
>>
>> 2012,2017?
>>
>
> done
>
>>
>>
>> > + * 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., 59 Temple Place, Suite 330, Boston, MA
>> > 02111-1307 USA
>>
>> Remove this. It had been changed once, no guarantee it will not again.
>
>
> done
>
>>
>>
>>
>> > +#include <linux/kernel.h>
>> > +#include <linux/module.h>
>> > +#include <linux/init.h>
>> > +#include <linux/types.h>
>> > +#include <linux/debugfs.h>
>> > +#include <linux/uaccess.h>
>> > +#include <linux/seq_file.h>
>> > +#include <linux/platform_device.h>
>> > +#include <linux/device.h>
>> > +#include <linux/acpi.h>
>>
>> Alphabetical.
>>
>
> done
>
>>
>> > +static int __init thinkpad_wmi_init(void)
>> > +{
>> > + platform_device = platform_create_bundle(&platform_driver,
>> > + thinkpad_wmi_probe,
>> > + NULL, 0, NULL, 0);
>> > + if (IS_ERR(platform_device))
>> > + return PTR_ERR(platform_device);
>> > + return 0;
>> > +}
>> > +
>> > +static void __exit thinkpad_wmi_exit(void)
>> > +{
>> > + platform_device_unregister(platform_device);
>> > + platform_driver_unregister(&platform_driver);
>> > +}
>> > +
>> > +module_init(thinkpad_wmi_init);
>> > +module_exit(thinkpad_wmi_exit);
>>
>> I didn't read the code, does it use WMI bus which Andy L. introduced
>> recently?
>>
>
> No, I wasn't aware of it. I checked the dell-wmi conversion patch and it
> should not be too hard.
> I'll probably sent that as a patch on top the existing driver (in the same
> series).
>
>>
>> --
>> With Best Regards,
>> Andy Shevchenko
>
>
>
>
> --
> Corentin Chary
> http://xf.iksaif.net



--
Corentin Chary
http://xf.iksaif.net

2017-09-07 10:02:48

by kernel test robot

[permalink] [raw]
Subject: [PATCH] drivers/x86: fix ptr_ret.cocci warnings

drivers/platform/x86/thinkpad-wmi.c:1205:1-3: WARNING: PTR_ERR_OR_ZERO can be used


Use PTR_ERR_OR_ZERO rather than if(IS_ERR(...)) + PTR_ERR

Generated by: scripts/coccinelle/api/ptr_ret.cocci

Fixes: 3af63e23450d ("drivers/x86: add thinkpad-wmi")
CC: Corentin Chary <[email protected]>
Signed-off-by: Fengguang Wu <[email protected]>
---

thinkpad-wmi.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)

--- a/drivers/platform/x86/thinkpad-wmi.c
+++ b/drivers/platform/x86/thinkpad-wmi.c
@@ -1202,9 +1202,7 @@ static int __init thinkpad_wmi_init(void
platform_device = platform_create_bundle(&platform_driver,
thinkpad_wmi_probe,
NULL, 0, NULL, 0);
- if (IS_ERR(platform_device))
- return PTR_ERR(platform_device);
- return 0;
+ return PTR_ERR_OR_ZERO(platform_device);
}

static void __exit thinkpad_wmi_exit(void)

2017-09-07 10:03:21

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

Hi Corentin,

[auto build test WARNING on platform-drivers-x86/for-next]
[also build test WARNING on v4.13 next-20170906]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Corentin-Chary/drivers-x86-add-thinkpad-wmi/20170907-125653
base: git://git.infradead.org/users/dvhart/linux-platform-drivers-x86.git for-next


coccinelle warnings: (new ones prefixed by >>)

>> drivers/platform/x86/thinkpad-wmi.c:1196:3-8: No need to set .owner here. The core will do it.
--
>> drivers/platform/x86/thinkpad-wmi.c:1205:1-3: WARNING: PTR_ERR_OR_ZERO can be used

Please review and possibly fold the followup patch.

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation

2017-09-07 10:03:37

by kernel test robot

[permalink] [raw]
Subject: [PATCH] drivers/x86: fix platform_no_drv_owner.cocci warnings

drivers/platform/x86/thinkpad-wmi.c:1196:3-8: No need to set .owner here. The core will do it.

Remove .owner field if calls are used which set it automatically

Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci

Fixes: 3af63e23450d ("drivers/x86: add thinkpad-wmi")
CC: Corentin Chary <[email protected]>
Signed-off-by: Fengguang Wu <[email protected]>
---

thinkpad-wmi.c | 1 -
1 file changed, 1 deletion(-)

--- a/drivers/platform/x86/thinkpad-wmi.c
+++ b/drivers/platform/x86/thinkpad-wmi.c
@@ -1193,7 +1193,6 @@ static struct platform_driver platform_d
.remove = __exit_p(thinkpad_wmi_remove),
.driver = {
.name = THINKPAD_WMI_FILE,
- .owner = THIS_MODULE,
},
};


2017-11-15 10:05:13

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

Hi!

> diff --git a/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
> new file mode 100644
> index 000000000000..c3673876c5b3
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
> @@ -0,0 +1,50 @@
> +What: /sys/devices/platform/thinkpad-wmi/password
> +Date: Aug 2017
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + BIOS password needs to be written in this file if set
> + to be able to change BIOS settings.

Should this go under platform/thinkpad-wmi? Seems like similar
interface is potentially useful on different machines?

> +What: /sys/devices/platform/thinkpad-wmi/password_encoding
> +Date: Aug 2017
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Password encoding ('ascii' or 'scanmode').
> +
> +What: /sys/devices/platform/thinkpad-wmi/password_kbd_lang
> +Date: Aug 2017
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Keyboard language used for password. One of 'us', 'fr' and 'gr'.
> +
> +What: /sys/devices/platform/thinkpad-wmi/password_type
> +Date: Aug 2017
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Password type to be changed when password_change is written to, e.g. 'pap'.

> +What: /sys/devices/platform/thinkpad-wmi/password_change
> +Date: Aug 2017
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Writing to this file will set the password specified in password_type.
> + The new password will not take effect until the next reboot.

With the different "encoding" and "keyboard language" fields, this
looks like great way to lock user out of his own machine ;-(.

> +What: /sys/devices/platform/thinkpad-wmi/password_settings
> +Date: Oct 2015
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Display various password settings.

Umm. We have one value per file in sysfs?

> +What: /sys/devices/platform/thinkpad-wmi/load_default_settings
> +Date: Oct 2015
> +KernelVersion: 4.14
> +Contact: "Corentin Chary" <[email protected]>
> +Description:
> + Write anything to this file to load default BIOS
> settings.

Is that reasonable?

> +### password_type
> +
> +Specify the password type to be changed when password_change is written to.
> +Can be:
> +* 'pap': supervisor password
> +* 'pop': power-on-password
> +
> +Other types may be valid, e.g. for user and master disk passwords.

This is extremely weird for /sysfs interface. Sounds like you should
have supervisor_password_change and power_on_password_change, etc...

> +### password_settings
> +
> +Display password related settings. This includes:
> +
> +* password_state: which passwords are set, if any
> + * bit 0: user password (power on password) is installed / 'pop'
> + * bit 1: admin/supervisor password is installed / 'pap'
> + * bit 2: hdd password(s) installed
> +* supported_encodings: supported keyboard encoding(s)
> + * bit 0: ASCII
> + * bit 1: scancode
> +* supported_keyboard: support keyboard language(s)
> + * bit 0: us
> + * bit 1: fr
> + * bit 2: gr
> +

If this belongs to the kernel (I'm not convinced), it certainly needs
different/better interface.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html


Attachments:
(No filename) (3.56 kB)
signature.asc (188.00 B)
Digital signature
Download all attachments

2017-10-21 06:42:50

by Corentin Chary

[permalink] [raw]
Subject: [PATCH] drivers/x86: add thinkpad-wmi

This driver has been available on
https://github.com/iksaif/thinkpad-wmi for
a few year and is already deployed on large
fleets of thinkpad laptops.

The WMI interface is documented here:
http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf

It mostly focused on changing BIOS/Firmware settings.

Signed-off-by: Corentin Chary <[email protected]>
---
.../ABI/testing/sysfs-platform-thinkpad-wmi | 50 +
Documentation/platform/thinkpad-wmi.txt | 92 ++
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/thinkpad-wmi.c | 1210 ++++++++++++++++++++
5 files changed, 1363 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
create mode 100644 Documentation/platform/thinkpad-wmi.txt
create mode 100644 drivers/platform/x86/thinkpad-wmi.c

diff --git a/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
new file mode 100644
index 000000000000..c3673876c5b3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-thinkpad-wmi
@@ -0,0 +1,50 @@
+What: /sys/devices/platform/thinkpad-wmi/password
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ BIOS password needs to be written in this file if set
+ to be able to change BIOS settings.
+
+What: /sys/devices/platform/thinkpad-wmi/password_encoding
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Password encoding ('ascii' or 'scanmode').
+
+What: /sys/devices/platform/thinkpad-wmi/password_kbd_lang
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Keyboard language used for password. One of 'us', 'fr' and 'gr'.
+
+What: /sys/devices/platform/thinkpad-wmi/password_type
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Password type to be changed when password_change is written to, e.g. 'pap'.
+
+What: /sys/devices/platform/thinkpad-wmi/password_change
+Date: Aug 2017
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Writing to this file will set the password specified in password_type.
+ The new password will not take effect until the next reboot.
+
+What: /sys/devices/platform/thinkpad-wmi/password_settings
+Date: Oct 2015
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Display various password settings.
+
+What: /sys/devices/platform/thinkpad-wmi/load_default_settings
+Date: Oct 2015
+KernelVersion: 4.14
+Contact: "Corentin Chary" <[email protected]>
+Description:
+ Write anything to this file to load default BIOS settings.
diff --git a/Documentation/platform/thinkpad-wmi.txt b/Documentation/platform/thinkpad-wmi.txt
new file mode 100644
index 000000000000..40d141aecc7b
--- /dev/null
+++ b/Documentation/platform/thinkpad-wmi.txt
@@ -0,0 +1,92 @@
+# thinkpad-wmi
+
+Linux Driver for Thinkpad WMI interface, allows you to control most
+BIOS settings from Linux, and maybe more.
+
+## sysfs interface
+
+Directory: /sys/bus/wmi/drivers/thinkpad-wmi/
+
+Each setting exposed by the WMI interface is available under its own name
+in this sysfs directory. Read from the file to get the current value (line 1)
+and list of options (line 2), and write an option to the file to set it.
+
+Additionally, there are some extra files for querying and managing BIOS
+password(s).
+
+### password
+
+Must contain the BIOS supervisor password (aka 'pap'), if set, to be able to do
+any change.
+
+Every subsequent password change will be authorized with this password. The
+password may be unloaded by writing an empty string. Writing an invalid
+password may trigger the BIOS' invalid password limit, such that a reboot will
+be required in order to make any further BIOS changes.
+
+### password_encoding
+
+Encoding used for the password, either '', 'scancode' or 'ascii'.
+
+Scan-code encoding appears to require the key-down scan codes, e.g. 0x1e, 0x30,
+0x2e for the ASCII encoded password 'abc'.
+
+### password_kbd_lang
+
+Keyboard language mapping, can be '', 'us', 'fr' or 'gr'.
+
+### password_type
+
+Specify the password type to be changed when password_change is written to.
+Can be:
+* 'pap': supervisor password
+* 'pop': power-on-password
+
+Other types may be valid, e.g. for user and master disk passwords.
+
+### password_change
+
+Writing to this file will change the password specified by password_type. The
+new password will not take effect until the next reboot.
+
+### password_settings
+
+Display password related settings. This includes:
+
+* password_state: which passwords are set, if any
+ * bit 0: user password (power on password) is installed / 'pop'
+ * bit 1: admin/supervisor password is installed / 'pap'
+ * bit 2: hdd password(s) installed
+* supported_encodings: supported keyboard encoding(s)
+ * bit 0: ASCII
+ * bit 1: scancode
+* supported_keyboard: support keyboard language(s)
+ * bit 0: us
+ * bit 1: fr
+ * bit 2: gr
+
+### load_default_settings
+
+Reset all settings to factory default.
+
+## debugfs interface
+
+The debugfs interface maps closely to the WMI Interface (see driver and doc).
+
+* bios_settings: show all BIOS settings
+* bios_setting: show BIOS setting for <instance>
+* list_valid_choices: list settings for <argument>
+* set_bios_settings: call set bios settings command with <argument>.
+* save_bios_settings call save bios settings command with <argument>.
+* discard_bios_settings: call discard bios settings command with <argument>.
+* load_default: call load default with <argument>.
+* set_bios_password: call set BIOS password with <argument>.
+* argument: argument to be used in various commands.
+* instance: setting instance.
+* instance_count: number of settings.
+* password_settings: password settings.
+
+## References
+
+Thinkpad WMI interface documentation:
+http://download.lenovo.com/ibmdl/pub/pc/pccbbs/thinkcentre_pdf/hrdeploy_en.pdf
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 80b87954f6dd..4e2e8a04228a 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -511,6 +511,16 @@ config THINKPAD_ACPI_HOTKEY_POLL
If you are not sure, say Y here. The driver enables polling only if
it is strictly necessary to do so.

+config THINKPAD_WMI
+ tristate "THINKPAD WMI Driver (EXPERIMENTAL)"
+ depends on ACPI_WMI
+ ---help---
+ This driver allow you to modify BIOS passwords, settings, and boot order
+ using Windows Management Instrumentation (WMI) through the Lenovo
+ client-management interface.
+
+ Say Y here if you have a WMI aware Thinkpad.
+
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 91cec1751461..3b03f0744794 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
+obj-$(CONFIG_THINKPAD_WMI) += thinkpad-wmi.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o
diff --git a/drivers/platform/x86/thinkpad-wmi.c b/drivers/platform/x86/thinkpad-wmi.c
new file mode 100644
index 000000000000..c102971f2979
--- /dev/null
+++ b/drivers/platform/x86/thinkpad-wmi.c
@@ -0,0 +1,1210 @@
+/*
+ * Thinkpad WMI configuration driver
+ *
+ * Copyright(C) 2017 Corentin Chary <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wmi.h>
+
+#define THINKPAD_WMI_FILE "thinkpad-wmi"
+
+MODULE_AUTHOR("Corentin Chary <[email protected]>");
+MODULE_DESCRIPTION("Thinkpad WMI Driver");
+MODULE_LICENSE("GPL");
+
+/* WMI interface */
+
+/**
+ * Name:
+ * Lenovo_BiosSetting
+ * Description:
+ * Get item name and settings for current WMI instance.
+ * Type:
+ * Query
+ * Returns:
+ * "Item,Value"
+ * Example:
+ * "WakeOnLAN,Enable"
+ */
+#define LENOVO_BIOS_SETTING_GUID \
+ "51F5230E-9677-46CD-A1CF-C0B23EE34DB7"
+
+/**
+ * Name:
+ * Lenovo_SetBiosSetting
+ * Description:
+ * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting
+ * class. To save the settings, use the Lenovo_SaveBiosSetting class.
+ * BIOS settings and values are case sensitive.
+ * After making changes to the BIOS settings, you must reboot the computer
+ * before the changes will take effect.
+ * Type:
+ * Method
+ * Arguments:
+ * "Item,Value,Password,Encoding,KbdLang;"
+ * Example:
+ * "WakeOnLAN,Disable,pswd,ascii,us;"
+ */
+#define LENOVO_SET_BIOS_SETTINGS_GUID \
+ "98479A64-33F5-4E33-A707-8E251EBBC3A1"
+
+/**
+ * Name:
+ * Lenovo_SaveBiosSettings
+ * Description:
+ * Save any pending changes in settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_SAVE_BIOS_SETTINGS_GUID \
+ "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3"
+
+
+/**
+ * Name:
+ * Lenovo_DiscardBiosSettings
+ * Description:
+ * Discard any pending changes in settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_DISCARD_BIOS_SETTINGS_GUID \
+ "74F1EBB6-927A-4C7D-95DF-698E21E80EB5"
+
+/**
+ * Name:
+ * Lenovo_LoadDefaultSettings
+ * Description:
+ * Load default BIOS settings. Use Lenovo_SaveBiosSettings to save the
+ * settings.
+ * Type:
+ * Method
+ * Arguments:
+ * "Password,Encoding,KbdLang;"
+ * Example:
+ * "pswd,ascii,us;"
+ */
+#define LENOVO_LOAD_DEFAULT_SETTINGS_GUID \
+ "7EEF04FF-4328-447C-B5BB-D449925D538D"
+
+/**
+ * Name:
+ * Lenovo_BiosPasswordSettings
+ * Description:
+ * Return BIOS Password settings
+ * Type:
+ * Query
+ * Returns:
+ * PasswordMode, PasswordState, MinLength, MaxLength,
+ * SupportedEncoding, SupportedKeyboard
+ */
+#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID \
+ "8ADB159E-1E32-455C-BC93-308A7ED98246"
+
+/**
+ * Name:
+ * Lenovo_SetBiosPassword
+ * Description:
+ * Change a specific password.
+ * - BIOS settings cannot be changed at the same boot as power-on
+ * passwords (POP) and hard disk passwords (HDP). If you want to change
+ * BIOS settings and POP or HDP, you must reboot the system after changing
+ * one of them.
+ * - A password cannot be set using this method when one does not already
+ * exist. Passwords can only be updated or cleared.
+ * Type:
+ * Method
+ * Arguments:
+ * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
+ * Example:
+ * "pop,oldpop,newpop,ascii,us;”
+ */
+#define LENOVO_SET_BIOS_PASSWORD_GUID \
+ "2651D9FD-911C-4B69-B94E-D0DED5963BD7"
+
+/**
+ * Name:
+ * Lenovo_GetBiosSelections
+ * Description:
+ * Return a list valid settings for a given item.
+ * Type:
+ * Method
+ * Arguments:
+ * "Item"
+ * Returns:
+ * "Value1,Value2,Value3,..."
+ * Example:
+ * -> "FlashOverLAN"
+ * <- "Enabled,Disabled"
+ */
+#define LENOVO_GET_BIOS_SELECTIONS_GUID \
+ "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B"
+
+/**
+ * Name:
+ * ???
+ * Type:
+ * Method
+ * Arguments:
+ * ???
+ * Example:
+ * ???
+ * WMI-Internals:
+ * Return big chunk of data
+ */
+#define LENOVO_QUERY_GUID \
+ "05901221-D566-11D1-B2F0-00A0C9062910"
+
+/* Return values */
+
+enum {
+ /*
+ * "Success"
+ * Operation completed successfully.
+ */
+ THINKPAD_WMI_SUCCESS = 0,
+ /*
+ * "Not Supported"
+ * The feature is not supported on this system.
+ */
+ THINKPAD_WMI_NOT_SUPPORTED = -ENODEV,
+ /*
+ * "Invalid"
+ * The item or value provided is not valid parameter
+ */
+ THINKPAD_WMI_INVALID = -EINVAL,
+ /*
+ * "Access Denied"
+ * The change could not be made due to an authentication problem.
+ * If a supervisor password exists, the correct supervisor password
+ * must be provided.
+ */
+ THINKPAD_WMI_ACCESS_DENIED = -EPERM,
+ /* "System Busy"
+ * BIOS changes have already been made that need to be committed.
+ * Reboot the system and try again.
+ */
+ THINKPAD_WMI_SYSTEM_BUSY = -EBUSY
+};
+
+/* Only add an alias on this one, since it's the one used
+ * in thinkpad_wmi_probe.
+ */
+MODULE_ALIAS("wmi:"LENOVO_BIOS_SETTING_GUID);
+
+struct thinkpad_wmi_pcfg {
+ uint32_t password_mode;
+ uint32_t password_state;
+ uint32_t min_length;
+ uint32_t max_length;
+ uint32_t supported_encodings;
+ uint32_t supported_keyboard;
+};
+
+/*
+ * thinkpad_wmi/ - debugfs root directory
+ * bios_settings
+ * bios_setting
+ * list_valid_choices
+ * set_bios_settings
+ * save_bios_settings
+ * discard_bios_settings
+ * load_default
+ * set_bios_password
+ * argument
+ * instance
+ * instance_count
+ * bios_password_settings
+ */
+struct thinkpad_wmi_debug {
+ struct dentry *root;
+
+ u8 instances_count;
+ u8 instance;
+ char argument[512];
+};
+
+struct thinkpad_wmi {
+ struct wmi_device *wmi_device;
+
+ int settings_count;
+
+ char password[64];
+ char password_encoding[64];
+ char password_kbdlang[4]; /* 2 bytes for \n\0 */
+ char auth_string[256];
+ char password_type[64];
+
+ bool can_set_bios_settings;
+ bool can_discard_bios_settings;
+ bool can_load_default_settings;
+ bool can_get_bios_selections;
+ bool can_set_bios_password;
+ bool can_get_password_settings;
+
+ char *settings[256];
+ struct dev_ext_attribute *devattrs;
+ struct thinkpad_wmi_debug debug;
+};
+
+/* helpers */
+static int thinkpad_wmi_errstr_to_err(const char *errstr)
+{
+ if (!strcmp(errstr, "Success"))
+ return THINKPAD_WMI_SUCCESS;
+ if (!strcmp(errstr, "Not Supported"))
+ return THINKPAD_WMI_NOT_SUPPORTED;
+ if (!strcmp(errstr, "Invalid"))
+ return THINKPAD_WMI_INVALID;
+ if (!strcmp(errstr, "Access Denied"))
+ return THINKPAD_WMI_ACCESS_DENIED;
+ if (!strcmp(errstr, "System Busy"))
+ return THINKPAD_WMI_SYSTEM_BUSY;
+
+ pr_debug("Unknown error string: '%s'", errstr);
+
+ return -EINVAL;
+}
+
+static int thinkpad_wmi_extract_error(const struct acpi_buffer *output)
+{
+ const union acpi_object *obj;
+ int ret;
+
+ obj = output->pointer;
+ if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+ return -EIO;
+
+ ret = thinkpad_wmi_errstr_to_err(obj->string.pointer);
+ kfree(obj);
+ return ret;
+}
+
+static int thinkpad_wmi_simple_call(const char *guid,
+ const char *arg)
+{
+ const struct acpi_buffer input = { strlen(arg), (char *)arg };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_evaluate_method(guid, 0, 0, &input, &output);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_error(&output);
+}
+
+static int thinkpad_wmi_extract_output_string(const struct acpi_buffer *output,
+ char **string)
+{
+ const union acpi_object *obj;
+
+ obj = output->pointer;
+ if (!obj || obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
+ return -EIO;
+
+ *string = kstrdup(obj->string.pointer, GFP_KERNEL);
+ kfree(obj);
+ return *string ? 0 : -ENOMEM;
+}
+
+static int thinkpad_wmi_bios_setting(int item, char **value)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_query_block(LENOVO_BIOS_SETTING_GUID, item, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_get_bios_selections(const char *item, char **value)
+{
+ const struct acpi_buffer input = { strlen(item), (char *)item };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID,
+ 0, 0, &input, &output);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return thinkpad_wmi_extract_output_string(&output, value);
+}
+
+static int thinkpad_wmi_set_bios_settings(const char *settings)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID,
+ settings);
+}
+
+static int thinkpad_wmi_save_bios_settings(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_discard_bios_settings(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_DISCARD_BIOS_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_load_default(const char *password)
+{
+ return thinkpad_wmi_simple_call(LENOVO_LOAD_DEFAULT_SETTINGS_GUID,
+ password);
+}
+
+static int thinkpad_wmi_set_bios_password(const char *settings)
+{
+ return thinkpad_wmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID,
+ settings);
+}
+
+static int thinkpad_wmi_password_settings(struct thinkpad_wmi_pcfg *pcfg)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ const union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0,
+ &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = output.pointer;
+ if (!obj || obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer)
+ return -EIO;
+ if (obj->buffer.length != sizeof(*pcfg)) {
+ pr_warn("Unknown pcfg buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return -EIO;
+ }
+
+ memcpy(pcfg, obj->buffer.pointer, obj->buffer.length);
+ kfree(obj);
+ return 0;
+}
+
+/* sysfs */
+
+#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr)
+
+static ssize_t show_setting(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ int item = (uintptr_t)ea->var;
+ char *name = thinkpad->settings[item];
+ char *settings = NULL, *choices = NULL, *value;
+ ssize_t count = 0;
+ int ret;
+
+ ret = thinkpad_wmi_bios_setting(item, &settings);
+ if (ret)
+ return ret;
+ if (!settings)
+ return -EIO;
+
+ if (thinkpad->can_get_bios_selections) {
+ ret = thinkpad_wmi_get_bios_selections(name, &choices);
+ if (ret)
+ goto error;
+ if (!choices || !*choices) {
+ ret = -EIO;
+ goto error;
+ }
+ }
+
+ value = strchr(settings, ',');
+ if (!value)
+ goto error;
+ value++;
+
+ count = sprintf(buf, "%s\n", value);
+ if (choices)
+ count += sprintf(buf + count, "%s\n", choices);
+
+error:
+ kfree(settings);
+ kfree(choices);
+ return ret ? ret : count;
+}
+
+static ssize_t store_setting(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ int item_idx = (uintptr_t)ea->var;
+ const char *item = thinkpad->settings[item_idx];
+ int ret;
+ size_t buffer_size;
+ char *buffer;
+
+ /* Format: 'Item,Value,Authstring;' */
+ buffer_size = (strlen(item) + 1 + count + 1 +
+ sizeof(thinkpad->auth_string) + 2);
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ strcpy(buffer, item);
+ strcat(buffer, ",");
+ strncat(buffer, buf, count);
+ if (count)
+ strim(buffer);
+ if (*thinkpad->auth_string) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->auth_string);
+ }
+ strcat(buffer, ";");
+
+ ret = thinkpad_wmi_set_bios_settings(buffer);
+ if (ret)
+ goto end;
+
+ ret = thinkpad_wmi_save_bios_settings(thinkpad->auth_string);
+ if (ret) {
+ /* Try to discard the settings if we failed to apply them. */
+ thinkpad_wmi_discard_bios_settings(thinkpad->auth_string);
+ goto end;
+ }
+ ret = count;
+
+end:
+ kfree(buffer);
+ return ret;
+}
+
+
+/* Password related sysfs methods */
+static ssize_t show_auth(struct thinkpad_wmi *thinkpad, char *buf,
+ const char *data, size_t size)
+{
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return sprintf(buf, "%s\n", data ? : "(nil)");
+}
+
+/* Create the auth string from password chunks */
+static void update_auth_string(struct thinkpad_wmi *thinkpad)
+{
+ if (!*thinkpad->password) {
+ /* No password at all */
+ thinkpad->auth_string[0] = '\0';
+ return;
+ }
+ strcpy(thinkpad->auth_string, thinkpad->password);
+
+ if (*thinkpad->password_encoding) {
+ strcat(thinkpad->auth_string, ",");
+ strcat(thinkpad->auth_string, thinkpad->password_encoding);
+ }
+
+ if (*thinkpad->password_kbdlang) {
+ strcat(thinkpad->auth_string, ",");
+ strcat(thinkpad->auth_string, thinkpad->password_kbdlang);
+ }
+}
+
+static ssize_t store_auth(struct thinkpad_wmi *thinkpad,
+ const char *buf, size_t count,
+ char *dst, size_t size)
+{
+ ssize_t ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (count > size - 1)
+ return -EINVAL;
+
+ /* dst may be being reused, NUL-terminate */
+ ret = strscpy(dst, buf, size);
+ if (ret < 0)
+ return ret;
+ if (count)
+ strim(dst);
+
+ update_auth_string(thinkpad);
+
+ return count;
+}
+
+#define THINKPAD_WMI_CREATE_AUTH_ATTR(_name, _uname, _mode) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev); \
+ \
+ return show_auth(thinkpad, buf, \
+ thinkpad->_name, \
+ sizeof(thinkpad->_name)); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev); \
+ \
+ return store_auth(thinkpad, buf, count, \
+ thinkpad->_name, \
+ sizeof(thinkpad->_name)); \
+ } \
+ static struct device_attribute dev_attr_##_name = { \
+ .attr = { \
+ .name = _uname, \
+ .mode = _mode }, \
+ .show = show_##_name, \
+ .store = store_##_name, \
+ }
+
+THINKPAD_WMI_CREATE_AUTH_ATTR(password, "password", 0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_encoding, "password_encoding",
+ 0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_kbdlang, "password_kbd_lang",
+ 0600);
+THINKPAD_WMI_CREATE_AUTH_ATTR(password_type, "password_type", 0600);
+
+static ssize_t show_password_settings(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct thinkpad_wmi_pcfg pcfg;
+ ssize_t ret;
+
+ ret = thinkpad_wmi_password_settings(&pcfg);
+ if (ret)
+ return ret;
+ ret += sprintf(buf, "password_mode: %#x\n", pcfg.password_mode);
+ ret += sprintf(buf + ret, "password_state: %#x\n",
+ pcfg.password_state);
+ ret += sprintf(buf + ret, "min_length: %d\n", pcfg.min_length);
+ ret += sprintf(buf + ret, "max_length: %d\n", pcfg.max_length);
+ ret += sprintf(buf + ret, "supported_encodings: %#x\n",
+ pcfg.supported_encodings);
+ ret += sprintf(buf + ret, "supported_keyboard: %#x\n",
+ pcfg.supported_keyboard);
+ return ret;
+}
+
+static DEVICE_ATTR(password_settings, 0400, show_password_settings, NULL);
+
+static ssize_t store_password_change(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+ size_t buffer_size;
+ char *buffer;
+ ssize_t ret;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
+
+ /* auth_string is the size of CurrentPassword,Encoding,KbdLang */
+ buffer_size = (sizeof(thinkpad->password_type) + 1 + count + 1 +
+ sizeof(thinkpad->auth_string) + 2);
+ buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ strcpy(buffer, thinkpad->password_type);
+
+ if (*thinkpad->password) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password);
+ }
+ strcat(buffer, ",");
+ strncat(buffer, buf, count);
+ if (count)
+ strim(buffer);
+
+ if (*thinkpad->password_encoding) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password_encoding);
+ }
+ if (*thinkpad->password_kbdlang) {
+ strcat(buffer, ",");
+ strcat(buffer, thinkpad->password_kbdlang);
+ }
+ strcat(buffer, ";");
+
+ ret = thinkpad_wmi_set_bios_password(buffer);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static struct device_attribute dev_attr_password_change = {
+ .attr = {
+ .name = "password_change",
+ .mode = 0200 },
+ .store = store_password_change,
+};
+
+
+static ssize_t store_load_default(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(dev);
+
+ return thinkpad_wmi_load_default(thinkpad->auth_string);
+}
+
+static DEVICE_ATTR(load_default_settings, 0200, NULL, store_load_default);
+
+static struct attribute *platform_attributes[] = {
+ &dev_attr_password_settings.attr,
+ &dev_attr_password.attr,
+ &dev_attr_password_encoding.attr,
+ &dev_attr_password_kbdlang.attr,
+ &dev_attr_password_type.attr,
+ &dev_attr_password_change.attr,
+ &dev_attr_load_default_settings.attr,
+ NULL
+};
+
+static umode_t thinkpad_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ bool supported = true;
+
+ return supported ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+ .is_visible = thinkpad_sysfs_is_visible,
+ .attrs = platform_attributes
+};
+
+static void thinkpad_wmi_sysfs_exit(struct wmi_device *wdev)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(&wdev->dev);
+ int i;
+
+ sysfs_remove_group(&wdev->dev.kobj, &platform_attribute_group);
+
+ if (!thinkpad->devattrs)
+ return;
+
+ for (i = 0; i < thinkpad->settings_count; ++i) {
+ struct dev_ext_attribute *deveattr = &thinkpad->devattrs[i];
+ struct device_attribute *devattr = &deveattr->attr;
+
+ if (devattr->attr.name)
+ device_remove_file(&wdev->dev, devattr);
+ }
+ kfree(thinkpad->devattrs);
+ thinkpad->devattrs = NULL;
+}
+
+static int __init thinkpad_wmi_sysfs_init(struct wmi_device *wdev)
+{
+ struct thinkpad_wmi *thinkpad = dev_get_drvdata(&wdev->dev);
+ struct dev_ext_attribute *devattrs;
+ int count = thinkpad->settings_count;
+ int i, ret;
+
+ devattrs = kmalloc_array(count, sizeof(*devattrs), GFP_KERNEL);
+ if (!devattrs)
+ return -ENOMEM;
+ thinkpad->devattrs = devattrs;
+
+ for (i = 0; i < count; ++i) {
+ struct dev_ext_attribute *deveattr = &devattrs[i];
+ struct device_attribute *devattr = &deveattr->attr;
+
+ sysfs_attr_init(&devattr->attr);
+ devattr->attr.name = thinkpad->settings[i];
+ devattr->attr.mode = 0644;
+ devattr->show = show_setting;
+ devattr->store = store_setting;
+ deveattr->var = (void *)(uintptr_t)i;
+ ret = device_create_file(&wdev->dev, devattr);
+ if (ret) {
+ /* Name is used to check is file has been created. */
+ devattr->attr.name = NULL;
+ return ret;
+ }
+ }
+
+ return sysfs_create_group(&wdev->dev.kobj, &platform_attribute_group);
+}
+
+/*
+ * Platform device
+ */
+static int __init thinkpad_wmi_platform_init(struct thinkpad_wmi *thinkpad)
+{
+ return thinkpad_wmi_sysfs_init(thinkpad->wmi_device);
+}
+
+static void thinkpad_wmi_platform_exit(struct thinkpad_wmi *thinkpad)
+{
+ thinkpad_wmi_sysfs_exit(thinkpad->wmi_device);
+}
+
+/* debugfs */
+
+static ssize_t dbgfs_write_argument(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *pos)
+{
+ struct thinkpad_wmi *thinkpad = file->f_path.dentry->d_inode->i_private;
+ char *kernbuf = thinkpad->debug.argument;
+ size_t size = sizeof(thinkpad->debug.argument);
+
+ if (count > PAGE_SIZE - 1)
+ return -EINVAL;
+
+ if (count > size - 1)
+ return -EINVAL;
+
+ if (copy_from_user(kernbuf, userbuf, count))
+ return -EFAULT;
+
+ kernbuf[count] = 0;
+
+ strim(kernbuf);
+
+ return count;
+}
+
+static int dbgfs_show_argument(struct seq_file *m, void *v)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ seq_printf(m, "%s\n", thinkpad->debug.argument);
+ return 0;
+}
+
+static int thinkpad_wmi_debugfs_argument_open(struct inode *inode,
+ struct file *file)
+{
+ struct thinkpad_wmi *thinkpad = inode->i_private;
+
+ return single_open(file, dbgfs_show_argument, thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_argument_fops = {
+ .open = thinkpad_wmi_debugfs_argument_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = dbgfs_write_argument,
+};
+
+struct thinkpad_wmi_debugfs_node {
+ struct thinkpad_wmi *thinkpad;
+ char *name;
+ int (*show)(struct seq_file *m, void *data);
+};
+
+static void show_bios_setting_line(struct thinkpad_wmi *thinkpad,
+ struct seq_file *m, int i, bool list_valid)
+{
+ int ret;
+ char *settings = NULL, *choices = NULL, *p;
+
+ ret = thinkpad_wmi_bios_setting(i, &settings);
+ if (ret || !settings)
+ return;
+
+ p = strchr(settings, ',');
+ if (p)
+ *p = '=';
+ seq_printf(m, "%s", settings);
+
+
+ if (!thinkpad->can_get_bios_selections)
+ goto line_feed;
+
+ if (p)
+ *p = '\0';
+
+ ret = thinkpad_wmi_get_bios_selections(settings, &choices);
+ if (ret || !choices || !*choices)
+ goto line_feed;
+
+ seq_printf(m, "\t[%s]", choices);
+
+line_feed:
+ kfree(settings);
+ kfree(choices);
+ seq_puts(m, "\n");
+}
+
+static int dbgfs_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+ int i;
+
+ for (i = 0; i < thinkpad->settings_count; ++i)
+ show_bios_setting_line(thinkpad, m, i, true);
+
+ return 0;
+}
+
+static int dbgfs_bios_setting(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ show_bios_setting_line(m->private, m, thinkpad->debug.instance, false);
+ return 0;
+}
+
+static int dbgfs_list_valid_choices(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+ char *choices = NULL;
+ int ret;
+
+ ret = thinkpad_wmi_get_bios_selections(thinkpad->debug.argument,
+ &choices);
+
+ if (ret || !choices || !*choices) {
+ kfree(choices);
+ return -EIO;
+ }
+
+ seq_printf(m, "%s\n", choices);
+ kfree(choices);
+ return 0;
+}
+
+static int dbgfs_set_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_set_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_save_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_save_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_discard_bios_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_discard_bios_settings(thinkpad->debug.argument);
+}
+
+static int dbgfs_load_default(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_load_default(thinkpad->debug.argument);
+}
+
+static int dbgfs_set_bios_password(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi *thinkpad = m->private;
+
+ return thinkpad_wmi_set_bios_password(thinkpad->debug.argument);
+}
+
+static int dbgfs_bios_password_settings(struct seq_file *m, void *data)
+{
+ struct thinkpad_wmi_pcfg pcfg;
+ int ret;
+
+ ret = thinkpad_wmi_password_settings(&pcfg);
+ if (ret)
+ return ret;
+ seq_printf(m, "password_mode: %#x\n", pcfg.password_mode);
+ seq_printf(m, "password_state: %#x\n", pcfg.password_state);
+ seq_printf(m, "min_length: %d\n", pcfg.min_length);
+ seq_printf(m, "max_length: %d\n", pcfg.max_length);
+ seq_printf(m, "supported_encodings: %#x\n", pcfg.supported_encodings);
+ seq_printf(m, "supported_keyboard: %#x\n", pcfg.supported_keyboard);
+ return 0;
+}
+
+static struct thinkpad_wmi_debugfs_node thinkpad_wmi_debug_files[] = {
+ { NULL, "bios_settings", dbgfs_bios_settings },
+ { NULL, "bios_setting", dbgfs_bios_setting },
+ { NULL, "list_valid_choices", dbgfs_list_valid_choices },
+ { NULL, "set_bios_settings", dbgfs_set_bios_settings },
+ { NULL, "save_bios_settings", dbgfs_save_bios_settings },
+ { NULL, "discard_bios_settings", dbgfs_discard_bios_settings },
+ { NULL, "load_default", dbgfs_load_default },
+ { NULL, "set_bios_password", dbgfs_set_bios_password },
+ { NULL, "bios_password_settings", dbgfs_bios_password_settings },
+};
+
+static int thinkpad_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct thinkpad_wmi_debugfs_node *node = inode->i_private;
+
+ return single_open(file, node->show, node->thinkpad);
+}
+
+static const struct file_operations thinkpad_wmi_debugfs_io_ops = {
+ .owner = THIS_MODULE,
+ .open = thinkpad_wmi_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void __init thinkpad_wmi_debugfs_exit(struct thinkpad_wmi *thinkpad)
+{
+ debugfs_remove_recursive(thinkpad->debug.root);
+}
+
+static int thinkpad_wmi_debugfs_init(struct thinkpad_wmi *thinkpad)
+{
+ struct dentry *dent;
+ int i;
+
+ thinkpad->debug.instances_count = thinkpad->settings_count;
+
+ thinkpad->debug.root = debugfs_create_dir(THINKPAD_WMI_FILE, NULL);
+ if (!thinkpad->debug.root) {
+ pr_err("failed to create debugfs directory");
+ goto error_debugfs;
+ }
+
+ dent = debugfs_create_file("argument", 0644,
+ thinkpad->debug.root, thinkpad,
+ &thinkpad_wmi_debugfs_argument_fops);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("instance", 0644,
+ thinkpad->debug.root,
+ &thinkpad->debug.instance);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("instances_count", 0444,
+ thinkpad->debug.root,
+ &thinkpad->debug.instances_count);
+ if (!dent)
+ goto error_debugfs;
+
+ for (i = 0; i < ARRAY_SIZE(thinkpad_wmi_debug_files); i++) {
+ struct thinkpad_wmi_debugfs_node *node;
+
+ node = &thinkpad_wmi_debug_files[i];
+
+ /* Filter non-present interfaces */
+ if (!strcmp(node->name, "set_bios_settings") &&
+ !thinkpad->can_set_bios_settings)
+ continue;
+ if (!strcmp(node->name, "dicard_bios_settings") &&
+ !thinkpad->can_discard_bios_settings)
+ continue;
+ if (!strcmp(node->name, "load_default_settings") &&
+ !thinkpad->can_load_default_settings)
+ continue;
+ if (!strcmp(node->name, "get_bios_selections") &&
+ !thinkpad->can_get_bios_selections)
+ continue;
+ if (!strcmp(node->name, "set_bios_password") &&
+ !thinkpad->can_set_bios_password)
+ continue;
+ if (!strcmp(node->name, "bios_password_settings") &&
+ !thinkpad->can_get_password_settings)
+ continue;
+
+ node->thinkpad = thinkpad;
+ dent = debugfs_create_file(node->name, S_IFREG | 0444,
+ thinkpad->debug.root, node,
+ &thinkpad_wmi_debugfs_io_ops);
+ if (!dent) {
+ pr_err("failed to create debug file: %s\n", node->name);
+ goto error_debugfs;
+ }
+ }
+
+
+ return 0;
+
+error_debugfs:
+ thinkpad_wmi_debugfs_exit(thinkpad);
+ return -ENOMEM;
+}
+
+/* Base driver */
+static void __init thinkpad_wmi_analyze(struct thinkpad_wmi *thinkpad)
+{
+ acpi_status status;
+ int i = 0;
+
+ /* Try to find the number of valid settings of this machine
+ * and use it to create sysfs attributes.
+ */
+ for (i = 0; i < 0xFF; ++i) {
+ char *item = NULL;
+ char *p;
+
+ status = thinkpad_wmi_bios_setting(i, &item);
+ if (ACPI_FAILURE(status))
+ break;
+ if (!item || !*item)
+ break;
+ /* Remove the value part */
+ p = strchr(item, ',');
+ if (p)
+ *p = '\0';
+ thinkpad->settings[i] = item; /* Cache setting name */
+ }
+
+ thinkpad->settings_count = i;
+ pr_info("Found %d settings", thinkpad->settings_count);
+
+ if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) &&
+ wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID)) {
+ thinkpad->can_set_bios_settings = true;
+ }
+
+ if (wmi_has_guid(LENOVO_DISCARD_BIOS_SETTINGS_GUID))
+ thinkpad->can_discard_bios_settings = true;
+
+ if (wmi_has_guid(LENOVO_LOAD_DEFAULT_SETTINGS_GUID))
+ thinkpad->can_load_default_settings = true;
+
+ if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID))
+ thinkpad->can_get_bios_selections = true;
+
+ if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID))
+ thinkpad->can_set_bios_password = true;
+
+ if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID))
+ thinkpad->can_get_password_settings = true;
+}
+
+static int __init thinkpad_wmi_add(struct wmi_device *wdev)
+{
+ struct thinkpad_wmi *thinkpad;
+ int err;
+
+ thinkpad = kzalloc(sizeof(struct thinkpad_wmi), GFP_KERNEL);
+ if (!thinkpad)
+ return -ENOMEM;
+
+ thinkpad->wmi_device = wdev;
+ dev_set_drvdata(&wdev->dev, thinkpad);
+
+ thinkpad_wmi_analyze(thinkpad);
+
+ err = thinkpad_wmi_platform_init(thinkpad);
+ if (err)
+ goto error_platform;
+
+ err = thinkpad_wmi_debugfs_init(thinkpad);
+ if (err)
+ goto error_debugfs;
+
+ return 0;
+
+error_debugfs:
+ thinkpad_wmi_platform_exit(thinkpad);
+error_platform:
+ kfree(thinkpad);
+ return err;
+}
+
+static int __exit thinkpad_wmi_remove(struct wmi_device *wdev)
+{
+ struct thinkpad_wmi *thinkpad;
+ int i;
+
+ thinkpad = dev_get_drvdata(&wdev->dev);
+ thinkpad_wmi_debugfs_exit(thinkpad);
+ thinkpad_wmi_platform_exit(thinkpad);
+
+ for (i = 0; thinkpad->settings[i]; ++i) {
+ kfree(thinkpad->settings[i]);
+ thinkpad->settings[i] = NULL;
+ }
+
+ kfree(thinkpad);
+ return 0;
+}
+
+static int __init thinkpad_wmi_probe(struct wmi_device *wdev)
+{
+ return thinkpad_wmi_add(wdev);
+}
+
+static const struct wmi_device_id thinkpad_wmi_id_table[] = {
+ // Search for Lenovo_BiosSetting
+ { .guid_string = LENOVO_BIOS_SETTING_GUID },
+ { },
+};
+
+static struct wmi_driver thinkpad_wmi_driver = {
+ .driver = {
+ .name = "thinkpad-wmi",
+ },
+ .id_table = thinkpad_wmi_id_table,
+ .probe = thinkpad_wmi_probe,
+ .remove = thinkpad_wmi_remove,
+};
+
+static int __init thinkpad_wmi_init(void)
+{
+ return wmi_driver_register(&thinkpad_wmi_driver);
+}
+
+static void __exit thinkpad_wmi_exit(void)
+{
+ wmi_driver_unregister(&thinkpad_wmi_driver);
+}
+
+module_init(thinkpad_wmi_init);
+module_exit(thinkpad_wmi_exit);
--
2.14.1


From 1580539016466413668@xxx Fri Oct 06 19:51:06 +0000 2017
X-GM-THRID: 1577596588945948664
X-Gmail-Labels: Inbox,Category Forums

2017-10-06 19:51:06

by Darren Hart

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

On Fri, Oct 06, 2017 at 09:06:14PM +0200, Corentin Chary wrote:
> Yes, I'm just having trouble finding time to write it :)
> I'll try to make that happen next week.

OK, no problem. Marking the patch as "changes requested" and will keep an eye
out.

Thanks,

--
Darren Hart
VMware Open Source Technology Center

From 1580536325397329898@xxx Fri Oct 06 19:08:19 +0000 2017
X-GM-THRID: 1577596588945948664
X-Gmail-Labels: Inbox,Category Forums

2017-10-06 19:08:19

by Corentin Chary

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

Yes, I'm just having trouble finding time to write it :)
I'll try to make that happen next week.

On Thu, Oct 5, 2017 at 4:49 AM, Darren Hart <[email protected]> wrote:
> On Tue, Sep 05, 2017 at 09:07:27AM +0200, Corentin Chary wrote:
>> [re-send for the mailing list, I forgot that gmail was stupid]
>>
>> On Tue, Sep 5, 2017 at 9:05 AM, Corentin Chary <[email protected]> wrote:
>> >>
>> >> I didn't read the code, does it use WMI bus which Andy L. introduced
>> >> recently?
>> >>
>> >
>> > No, I wasn't aware of it. I checked the dell-wmi conversion patch and it
>> > should not be too hard.
>> > I'll probably sent that as a patch on top the existing driver (in the same
>> > series).
>
> Hi Corentin,
>
> Just to make sure I haven't missed it - I believe we're waiting for a v2 of this
> patch. Is that right?
>
> --
> Darren Hart
> VMware Open Source Technology Center



--
Corentin Chary
http://xf.iksaif.net

From 1580384206151066687@xxx Thu Oct 05 02:50:27 +0000 2017
X-GM-THRID: 1577596588945948664
X-Gmail-Labels: Inbox,Category Forums

2017-10-05 02:50:27

by Darren Hart

[permalink] [raw]
Subject: Re: [PATCH] drivers/x86: add thinkpad-wmi

On Tue, Sep 05, 2017 at 09:07:27AM +0200, Corentin Chary wrote:
> [re-send for the mailing list, I forgot that gmail was stupid]
>
> On Tue, Sep 5, 2017 at 9:05 AM, Corentin Chary <[email protected]> wrote:
> >>
> >> I didn't read the code, does it use WMI bus which Andy L. introduced
> >> recently?
> >>
> >
> > No, I wasn't aware of it. I checked the dell-wmi conversion patch and it
> > should not be too hard.
> > I'll probably sent that as a patch on top the existing driver (in the same
> > series).

Hi Corentin,

Just to make sure I haven't missed it - I believe we're waiting for a v2 of this
patch. Is that right?

--
Darren Hart
VMware Open Source Technology Center

From 1577874886953164823@xxx Thu Sep 07 10:05:54 +0000 2017
X-GM-THRID: 1577596588945948664
X-Gmail-Labels: Inbox,Category Forums