diff mbox series

platform/x86: support to store/show powermode for Inspur

Message ID 20231008092149.967239-1-aichao@kylinos.cn (mailing list archive)
State Changes Requested, archived
Headers show
Series platform/x86: support to store/show powermode for Inspur | expand

Commit Message

Ai Chao Oct. 8, 2023, 9:21 a.m. UTC
Support to store/show powermode for Inspur.

Signed-off-by: Ai Chao <aichao@kylinos.cn>
---
 drivers/platform/x86/Kconfig      |  11 ++
 drivers/platform/x86/Makefile     |   3 +
 drivers/platform/x86/inspur-wmi.c | 180 ++++++++++++++++++++++++++++++
 3 files changed, 194 insertions(+)
 create mode 100644 drivers/platform/x86/inspur-wmi.c

Comments

Armin Wolf Oct. 8, 2023, 6:41 p.m. UTC | #1
Am 08.10.23 um 11:21 schrieb Ai Chao:

> Support to store/show powermode for Inspur.
>
> Signed-off-by: Ai Chao <aichao@kylinos.cn>
> ---
>   drivers/platform/x86/Kconfig      |  11 ++
>   drivers/platform/x86/Makefile     |   3 +
>   drivers/platform/x86/inspur-wmi.c | 180 ++++++++++++++++++++++++++++++
>   3 files changed, 194 insertions(+)
>   create mode 100644 drivers/platform/x86/inspur-wmi.c
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 2a1070543391..9e565ee01a9f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -988,6 +988,17 @@ config TOUCHSCREEN_DMI
>   	  the OS-image for the device. This option supplies the missing info.
>   	  Enable this for x86 tablets with Silead or Chipone touchscreens.
>
> +config INSPUR_WMI
> +	tristate "Inspur WMI hotkeys driver"
> +	depends on ACPI_WMI
> +	depends on INPUT
> +	help
> +	This driver provides support for Inspur WMI hotkeys.
> +	It's support to store/show powermode.
> +

Maybe "It supports to change the power mode."? A short explanation that this power mode
does would also be helpful here.

> +	To compile this driver as a module, choose M here: the module
> +	will be called inspur-wmi.
> +
>   source "drivers/platform/x86/x86-android-tablets/Kconfig"
>
>   config FW_ATTR_CLASS
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index b457de5abf7d..9285c252757e 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -98,6 +98,9 @@ obj-$(CONFIG_TOSHIBA_WMI)	+= toshiba-wmi.o
>   # before toshiba_acpi initializes
>   obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
>
> +# Inspur
> +obj-$(CONFIG_INSPUR_WMI)	+= inspur-wmi.o
> +
>   # Laptop drivers
>   obj-$(CONFIG_ACPI_CMPC)		+= classmate-laptop.o
>   obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> diff --git a/drivers/platform/x86/inspur-wmi.c b/drivers/platform/x86/inspur-wmi.c
> new file mode 100644
> index 000000000000..6c4d722e98dc
> --- /dev/null
> +++ b/drivers/platform/x86/inspur-wmi.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + *  Inspur WMI hotkeys
> + *
> + *  Copyright (C) 2018	      Ai Chao <aichao@kylinos.cn>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +
> +#define WMI_INSPUR_POWERMODE_EVENT_GUID "854FA5AC-58C7-451D-AAB1-57D6F4E6DDD4"
> +#define WMI_INSPUR_POWERMODE_BIOS_GUID "596C31E3-332D-43C9-AEE9-585493284F5D"
> +
> +enum inspur_wmi_method_ids {
> +	INSPUR_WMI_GET_POWERMODE = 0x02,
> +	INSPUR_WMI_SET_POWERMODE = 0x03,
> +};
> +
> +struct inspur_wmi_priv {
> +	struct input_dev *idev;
> +};
> +
> +static int inspur_wmi_perform_query(char *guid,
> +		enum inspur_wmi_method_ids query_id,
> +		void *buffer, size_t insize, size_t outsize)
> +{
> +	union acpi_object *obj;
> +	int ret = 0;
> +	u32 wmi_outsize;
> +	struct acpi_buffer input = { insize, buffer};
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +
> +	wmi_evaluate_method(guid, 0, query_id, &input, &output);
> +

Please use wmidev_evaluate_method() instead of the deprecated GUID-based interface.
And check the return value of this function.

> +	obj = output.pointer;
> +	if (!obj)
> +		return -EINVAL;
> +
> +	if (obj->type != ACPI_TYPE_BUFFER) {
> +		ret = -EINVAL;
> +		goto out_free;
> +	}
> +
> +	/* Ignore output data of zero size */
> +	if (!outsize)
> +		goto out_free;
> +
> +	wmi_outsize = min_t(size_t, outsize, obj->buffer.length);

Please return an error if the size of the returned buffer does not match the size
requested by the caller. Otherwise the caller might process bogus values if the buffer size
is smaller than requested.

> +	memcpy(buffer, obj->buffer.pointer, wmi_outsize);
> +
> +out_free:
> +	kfree(obj);
> +	return ret;
> +}
> +
> +static ssize_t powermode_store(struct device *dev,
> +		struct device_attribute *attr,
> +		const char *buf, size_t count)
> +{
> +	int ret, mode;
> +
> +	ret = kstrtoint(buf, 0, &mode);
> +	if (ret)
> +		return ret;
> +
> +	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
> +			INSPUR_WMI_SET_POWERMODE,
> +			&mode, sizeof(mode), sizeof(mode));

Maybe use a fixed-width integer like u32 here? Also the return value of
inspur_wmi_perform_query() should be checked.

> +
> +	return count;
> +}
> +
> +
> +static ssize_t powermode_show(struct device *dev,
> +		struct device_attribute *attr,
> +		char *buf)
> +{
> +	int mode = 0;
> +	u8 ret;
> +	u8 *ret_code;
> +
> +	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
> +			INSPUR_WMI_GET_POWERMODE,
> +			&mode, sizeof(mode), sizeof(mode));

Same as above.

> +	/*
> +	 *Byte [0]: Return code, 0x0 No error, 0x01 Error
> +	 *Byte [1]: Power Mode
> +	 */
> +	ret_code = (u8 *)(&mode);
> +	ret = ret_code[1];

Please check ret_code[0] for 0x01, and return an appropriate error code in such a case.

> +
> +	return sprintf(buf, "%u\n", ret);
> +}
> +
> +DEVICE_ATTR_RW(powermode);

Please make this static.

> +
> +static struct attribute *inspur_wmi_attrs[] = {
> +	&dev_attr_powermode.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group inspur_wmi_group = {
> +	.attrs = inspur_wmi_attrs,
> +};
> +
> +const struct attribute_group *inspur_wmi_groups[] = {
> +	&inspur_wmi_group,
> +	NULL,
> +};

Same as above

> +
> +static void inspur_wmi_notify(struct wmi_device *wdev,
> +		union acpi_object *obj)
> +{
> +	//to do

Please insert something like "dev_notice(&wdev->dev, "Unknown WMI event: %u\n", event)" here
so people can find out the necessary hotkey events.

> +}
> +
> +static int inspur_wmi_input_setup(struct wmi_device *wdev)
> +{
> +	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	priv->idev = devm_input_allocate_device(&wdev->dev);
> +	if (!priv->idev)
> +		return -ENOMEM;
> +
> +	priv->idev->name = "Inspur WMI hotkeys";
> +	priv->idev->phys = "wmi/input0";
> +	priv->idev->id.bustype = BUS_HOST;
> +	priv->idev->dev.parent = &wdev->dev;
> +
> +	return input_register_device(priv->idev);
> +}
> +
> +static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct inspur_wmi_priv *priv;
> +	int err;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(struct inspur_wmi_priv),
> +			GFP_KERNEL);

Please use "sizeof(*priv)" instead of "sizeof(struct inspur_wmi_priv)".
Also the alignment should match open parenthesis, please run "checkpatch --strict".

> +	if (!priv)
> +		return -ENOMEM;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	err = inspur_wmi_input_setup(wdev);
> +	return err;

The variable "err" can be omitted, just do "return inspur_wmi_input_setup(...)"

> +}
> +
> +static void inspur_wmi_remove(struct wmi_device *wdev)
> +{
> +	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	input_unregister_device(priv->idev);

The kerneldoc comment for devm_input_allocate_device() says:

"Managed input devices do not need to be explicitly unregistered or freed as it will be done
automatically when owner device unbinds from its driver (or binding fails)."

Please drop inspur_wmi_remove().

> +}
> +
> +static const struct wmi_device_id inspur_wmi_id_table[] = {
> +	{ .guid_string = WMI_INSPUR_POWERMODE_EVENT_GUID },
> +	{  }
> +};

Can you share the bmof buffer describing this WMI device? Currently, both WMI GUIDs seem
to be independent from each other, so it would make more sense to create two separate WMI drivers.
The first driver would continue to handle the hotkey events, while the second drivers allows to change
the power mode.

Thanks,
Armin Wolf

> +
> +static struct wmi_driver inspur_wmi_driver = {
> +	.driver = {
> +		.name = "inspur-wmi",
> +		.dev_groups = inspur_wmi_groups,
> +	},
> +	.id_table = inspur_wmi_id_table,
> +	.probe = inspur_wmi_probe,
> +	.notify = inspur_wmi_notify,
> +	.remove = inspur_wmi_remove,
> +};
> +
> +module_wmi_driver(inspur_wmi_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table);
> +MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
> +MODULE_DESCRIPTION("Inspur WMI hotkeys");
> +MODULE_LICENSE("GPL");
Hans de Goede Oct. 12, 2023, 12:26 p.m. UTC | #2
Hi Ai,

On 10/8/23 11:21, Ai Chao wrote:
> Support to store/show powermode for Inspur.
> 
> Signed-off-by: Ai Chao <aichao@kylinos.cn>

Thank you for your patch.

Comments inline.

> ---
>  drivers/platform/x86/Kconfig      |  11 ++
>  drivers/platform/x86/Makefile     |   3 +
>  drivers/platform/x86/inspur-wmi.c | 180 ++++++++++++++++++++++++++++++
>  3 files changed, 194 insertions(+)
>  create mode 100644 drivers/platform/x86/inspur-wmi.c
> 
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 2a1070543391..9e565ee01a9f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -988,6 +988,17 @@ config TOUCHSCREEN_DMI
>  	  the OS-image for the device. This option supplies the missing info.
>  	  Enable this for x86 tablets with Silead or Chipone touchscreens.
>  
> +config INSPUR_WMI
> +	tristate "Inspur WMI hotkeys driver"
> +	depends on ACPI_WMI
> +	depends on INPUT
> +	help
> +	This driver provides support for Inspur WMI hotkeys.
> +	It's support to store/show powermode.

The use of the work "hotkeys" here seems wrong since this
only supports the powermode WMI interface.


> +
> +	To compile this driver as a module, choose M here: the module
> +	will be called inspur-wmi.
> +
>  source "drivers/platform/x86/x86-android-tablets/Kconfig"
>  
>  config FW_ATTR_CLASS
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index b457de5abf7d..9285c252757e 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -98,6 +98,9 @@ obj-$(CONFIG_TOSHIBA_WMI)	+= toshiba-wmi.o
>  # before toshiba_acpi initializes
>  obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
>  
> +# Inspur
> +obj-$(CONFIG_INSPUR_WMI)	+= inspur-wmi.o
> +
>  # Laptop drivers
>  obj-$(CONFIG_ACPI_CMPC)		+= classmate-laptop.o
>  obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
> diff --git a/drivers/platform/x86/inspur-wmi.c b/drivers/platform/x86/inspur-wmi.c
> new file mode 100644
> index 000000000000..6c4d722e98dc
> --- /dev/null
> +++ b/drivers/platform/x86/inspur-wmi.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + *  Inspur WMI hotkeys
> + *
> + *  Copyright (C) 2018	      Ai Chao <aichao@kylinos.cn>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +
> +#define WMI_INSPUR_POWERMODE_EVENT_GUID "854FA5AC-58C7-451D-AAB1-57D6F4E6DDD4"
> +#define WMI_INSPUR_POWERMODE_BIOS_GUID "596C31E3-332D-43C9-AEE9-585493284F5D"
> +
> +enum inspur_wmi_method_ids {
> +	INSPUR_WMI_GET_POWERMODE = 0x02,
> +	INSPUR_WMI_SET_POWERMODE = 0x03,
> +};
> +
> +struct inspur_wmi_priv {
> +	struct input_dev *idev;
> +};
> +
> +static int inspur_wmi_perform_query(char *guid,
> +		enum inspur_wmi_method_ids query_id,
> +		void *buffer, size_t insize, size_t outsize)
> +{
> +	union acpi_object *obj;
> +	int ret = 0;
> +	u32 wmi_outsize;
> +	struct acpi_buffer input = { insize, buffer};
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +
> +	wmi_evaluate_method(guid, 0, query_id, &input, &output);
> +
> +	obj = output.pointer;
> +	if (!obj)
> +		return -EINVAL;
> +
> +	if (obj->type != ACPI_TYPE_BUFFER) {
> +		ret = -EINVAL;
> +		goto out_free;
> +	}
> +
> +	/* Ignore output data of zero size */
> +	if (!outsize)
> +		goto out_free;
> +
> +	wmi_outsize = min_t(size_t, outsize, obj->buffer.length);
> +	memcpy(buffer, obj->buffer.pointer, wmi_outsize);
> +
> +out_free:
> +	kfree(obj);
> +	return ret;
> +}
> +
> +static ssize_t powermode_store(struct device *dev,
> +		struct device_attribute *attr,
> +		const char *buf, size_t count)
> +{
> +	int ret, mode;
> +
> +	ret = kstrtoint(buf, 0, &mode);
> +	if (ret)
> +		return ret;

So this allows sending any value of
-2^31 to +2^31 to the WMI interface
I have a hard time believing that that entire
range is valid ?

Do you know which values are valid and do you know
what the meaning of the valid values are ?

Typically these values have meanings like:

low-power / balanced / high-performance

and assuming that is the case then this really
should be using the drivers/acpi/platform_profile.c
userspace API instead of registering a custom
sysfs attribute for this.

Regards,

Hans




> +
> +	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
> +			INSPUR_WMI_SET_POWERMODE,
> +			&mode, sizeof(mode), sizeof(mode));
> +
> +	return count;
> +}
> +
> +
> +static ssize_t powermode_show(struct device *dev,
> +		struct device_attribute *attr,
> +		char *buf)
> +{
> +	int mode = 0;
> +	u8 ret;
> +	u8 *ret_code;
> +
> +	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
> +			INSPUR_WMI_GET_POWERMODE,
> +			&mode, sizeof(mode), sizeof(mode));
> +	/*
> +	 *Byte [0]: Return code, 0x0 No error, 0x01 Error
> +	 *Byte [1]: Power Mode
> +	 */
> +	ret_code = (u8 *)(&mode);
> +	ret = ret_code[1];
> +
> +	return sprintf(buf, "%u\n", ret);
> +}
> +
> +DEVICE_ATTR_RW(powermode);
> +
> +static struct attribute *inspur_wmi_attrs[] = {
> +	&dev_attr_powermode.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group inspur_wmi_group = {
> +	.attrs = inspur_wmi_attrs,
> +};
> +
> +const struct attribute_group *inspur_wmi_groups[] = {
> +	&inspur_wmi_group,
> +	NULL,
> +};
> +
> +static void inspur_wmi_notify(struct wmi_device *wdev,
> +		union acpi_object *obj)
> +{
> +	//to do
> +}
> +
> +static int inspur_wmi_input_setup(struct wmi_device *wdev)
> +{
> +	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	priv->idev = devm_input_allocate_device(&wdev->dev);
> +	if (!priv->idev)
> +		return -ENOMEM;
> +
> +	priv->idev->name = "Inspur WMI hotkeys";
> +	priv->idev->phys = "wmi/input0";
> +	priv->idev->id.bustype = BUS_HOST;
> +	priv->idev->dev.parent = &wdev->dev;
> +
> +	return input_register_device(priv->idev);
> +}
> +
> +static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
> +{
> +	struct inspur_wmi_priv *priv;
> +	int err;
> +
> +	priv = devm_kzalloc(&wdev->dev, sizeof(struct inspur_wmi_priv),
> +			GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	dev_set_drvdata(&wdev->dev, priv);
> +
> +	err = inspur_wmi_input_setup(wdev);
> +	return err;
> +}
> +
> +static void inspur_wmi_remove(struct wmi_device *wdev)
> +{
> +	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
> +
> +	input_unregister_device(priv->idev);
> +}
> +
> +static const struct wmi_device_id inspur_wmi_id_table[] = {
> +	{ .guid_string = WMI_INSPUR_POWERMODE_EVENT_GUID },
> +	{  }
> +};
> +
> +static struct wmi_driver inspur_wmi_driver = {
> +	.driver = {
> +		.name = "inspur-wmi",
> +		.dev_groups = inspur_wmi_groups,
> +	},
> +	.id_table = inspur_wmi_id_table,
> +	.probe = inspur_wmi_probe,
> +	.notify = inspur_wmi_notify,
> +	.remove = inspur_wmi_remove,
> +};
> +
> +module_wmi_driver(inspur_wmi_driver);
> +
> +MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table);
> +MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
> +MODULE_DESCRIPTION("Inspur WMI hotkeys");
> +MODULE_LICENSE("GPL");
diff mbox series

Patch

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 2a1070543391..9e565ee01a9f 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -988,6 +988,17 @@  config TOUCHSCREEN_DMI
 	  the OS-image for the device. This option supplies the missing info.
 	  Enable this for x86 tablets with Silead or Chipone touchscreens.
 
+config INSPUR_WMI
+	tristate "Inspur WMI hotkeys driver"
+	depends on ACPI_WMI
+	depends on INPUT
+	help
+	This driver provides support for Inspur WMI hotkeys.
+	It's support to store/show powermode.
+
+	To compile this driver as a module, choose M here: the module
+	will be called inspur-wmi.
+
 source "drivers/platform/x86/x86-android-tablets/Kconfig"
 
 config FW_ATTR_CLASS
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index b457de5abf7d..9285c252757e 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -98,6 +98,9 @@  obj-$(CONFIG_TOSHIBA_WMI)	+= toshiba-wmi.o
 # before toshiba_acpi initializes
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 
+# Inspur
+obj-$(CONFIG_INSPUR_WMI)	+= inspur-wmi.o
+
 # Laptop drivers
 obj-$(CONFIG_ACPI_CMPC)		+= classmate-laptop.o
 obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o
diff --git a/drivers/platform/x86/inspur-wmi.c b/drivers/platform/x86/inspur-wmi.c
new file mode 100644
index 000000000000..6c4d722e98dc
--- /dev/null
+++ b/drivers/platform/x86/inspur-wmi.c
@@ -0,0 +1,180 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Inspur WMI hotkeys
+ *
+ *  Copyright (C) 2018	      Ai Chao <aichao@kylinos.cn>
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+#define WMI_INSPUR_POWERMODE_EVENT_GUID "854FA5AC-58C7-451D-AAB1-57D6F4E6DDD4"
+#define WMI_INSPUR_POWERMODE_BIOS_GUID "596C31E3-332D-43C9-AEE9-585493284F5D"
+
+enum inspur_wmi_method_ids {
+	INSPUR_WMI_GET_POWERMODE = 0x02,
+	INSPUR_WMI_SET_POWERMODE = 0x03,
+};
+
+struct inspur_wmi_priv {
+	struct input_dev *idev;
+};
+
+static int inspur_wmi_perform_query(char *guid,
+		enum inspur_wmi_method_ids query_id,
+		void *buffer, size_t insize, size_t outsize)
+{
+	union acpi_object *obj;
+	int ret = 0;
+	u32 wmi_outsize;
+	struct acpi_buffer input = { insize, buffer};
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	wmi_evaluate_method(guid, 0, query_id, &input, &output);
+
+	obj = output.pointer;
+	if (!obj)
+		return -EINVAL;
+
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		ret = -EINVAL;
+		goto out_free;
+	}
+
+	/* Ignore output data of zero size */
+	if (!outsize)
+		goto out_free;
+
+	wmi_outsize = min_t(size_t, outsize, obj->buffer.length);
+	memcpy(buffer, obj->buffer.pointer, wmi_outsize);
+
+out_free:
+	kfree(obj);
+	return ret;
+}
+
+static ssize_t powermode_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	int ret, mode;
+
+	ret = kstrtoint(buf, 0, &mode);
+	if (ret)
+		return ret;
+
+	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
+			INSPUR_WMI_SET_POWERMODE,
+			&mode, sizeof(mode), sizeof(mode));
+
+	return count;
+}
+
+
+static ssize_t powermode_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	int mode = 0;
+	u8 ret;
+	u8 *ret_code;
+
+	inspur_wmi_perform_query(WMI_INSPUR_POWERMODE_BIOS_GUID,
+			INSPUR_WMI_GET_POWERMODE,
+			&mode, sizeof(mode), sizeof(mode));
+	/*
+	 *Byte [0]: Return code, 0x0 No error, 0x01 Error
+	 *Byte [1]: Power Mode
+	 */
+	ret_code = (u8 *)(&mode);
+	ret = ret_code[1];
+
+	return sprintf(buf, "%u\n", ret);
+}
+
+DEVICE_ATTR_RW(powermode);
+
+static struct attribute *inspur_wmi_attrs[] = {
+	&dev_attr_powermode.attr,
+	NULL,
+};
+
+static const struct attribute_group inspur_wmi_group = {
+	.attrs = inspur_wmi_attrs,
+};
+
+const struct attribute_group *inspur_wmi_groups[] = {
+	&inspur_wmi_group,
+	NULL,
+};
+
+static void inspur_wmi_notify(struct wmi_device *wdev,
+		union acpi_object *obj)
+{
+	//to do
+}
+
+static int inspur_wmi_input_setup(struct wmi_device *wdev)
+{
+	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+
+	priv->idev = devm_input_allocate_device(&wdev->dev);
+	if (!priv->idev)
+		return -ENOMEM;
+
+	priv->idev->name = "Inspur WMI hotkeys";
+	priv->idev->phys = "wmi/input0";
+	priv->idev->id.bustype = BUS_HOST;
+	priv->idev->dev.parent = &wdev->dev;
+
+	return input_register_device(priv->idev);
+}
+
+static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	struct inspur_wmi_priv *priv;
+	int err;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(struct inspur_wmi_priv),
+			GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(&wdev->dev, priv);
+
+	err = inspur_wmi_input_setup(wdev);
+	return err;
+}
+
+static void inspur_wmi_remove(struct wmi_device *wdev)
+{
+	struct inspur_wmi_priv *priv = dev_get_drvdata(&wdev->dev);
+
+	input_unregister_device(priv->idev);
+}
+
+static const struct wmi_device_id inspur_wmi_id_table[] = {
+	{ .guid_string = WMI_INSPUR_POWERMODE_EVENT_GUID },
+	{  }
+};
+
+static struct wmi_driver inspur_wmi_driver = {
+	.driver = {
+		.name = "inspur-wmi",
+		.dev_groups = inspur_wmi_groups,
+	},
+	.id_table = inspur_wmi_id_table,
+	.probe = inspur_wmi_probe,
+	.notify = inspur_wmi_notify,
+	.remove = inspur_wmi_remove,
+};
+
+module_wmi_driver(inspur_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, inspur_wmi_id_table);
+MODULE_AUTHOR("Ai Chao <aichao@kylinos.cn>");
+MODULE_DESCRIPTION("Inspur WMI hotkeys");
+MODULE_LICENSE("GPL");