diff mbox

[2/2] oem: the kernel module creates an ioctl sys nodes for testing hp's WMI interface. This interface currently supports WMI command 0x1b, including get and set functions.

Message ID 1355106555-21026-3-git-send-email-alex.hung@canonical.com
State Rejected
Headers show

Commit Message

Alex Hung Dec. 10, 2012, 2:29 a.m. UTC
Signed-off-by: Alex Hung <alex.hung@canonical.com>
---
 oem/Makefile |    6 +
 oem/hp-wmi.c |  533 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 539 insertions(+)
 create mode 100644 oem/Makefile
 create mode 100644 oem/hp-wmi.c

Comments

Alex Hung Dec. 10, 2012, 6:41 a.m. UTC | #1
Hi,

I found out that I didn't include a .h file. I will re-send the patch.

On 12/10/2012 10:29 AM, Alex Hung wrote:
> Signed-off-by: Alex Hung <alex.hung@canonical.com>
> ---
>   oem/Makefile |    6 +
>   oem/hp-wmi.c |  533 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 539 insertions(+)
>   create mode 100644 oem/Makefile
>   create mode 100644 oem/hp-wmi.c
>
> diff --git a/oem/Makefile b/oem/Makefile
> new file mode 100644
> index 0000000..ab73846
> --- /dev/null
> +++ b/oem/Makefile
> @@ -0,0 +1,6 @@
> +obj-m += hp-wmi.o
> +all:
> +	make -C /lib/modules/`uname -r`/build M=`pwd` modules
> +
> +clean:
> +	make -C /lib/modules/`uname -r`/build M=`pwd` clean
> diff --git a/oem/hp-wmi.c b/oem/hp-wmi.c
> new file mode 100644
> index 0000000..eff6faf
> --- /dev/null
> +++ b/oem/hp-wmi.c
> @@ -0,0 +1,533 @@
> +/*
> + * Copyright (C) 2011-2012 Canonical
> + *
> + * Portions based on hp-wmi.c:
> + * Copyright (C) 2008 Red Hat <mjg@redhat.com>
> + * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi>
> + *
> + *  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 "-fwts: " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <linux/platform_device.h>
> +#include <linux/acpi.h>
> +#include <linux/string.h>
> +
> +#include <linux/proc_fs.h>
> +#include <linux/miscdevice.h>
> +
> +#include "fwts-oem.h"
> +
> +MODULE_AUTHOR("Alex Hung <alex.hung@canonical.com>");
> +MODULE_LICENSE("GPL");
> +
> +MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
> +MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
> +
> +#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
> +#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
> +
> +#define HPWMI_DISPLAY_QUERY	0x1
> +#define HPWMI_HDDTEMP_QUERY	0x2
> +#define HPWMI_ALS_QUERY		0x3
> +#define HPWMI_HARDWARE_QUERY	0x4
> +#define HPWMI_WIRELESS_QUERY	0x5
> +#define HPWMI_HOTKEY_QUERY	0xc
> +#define HPWMI_WIRELESS2_QUERY	0x1b
> +
> +enum hp_wmi_radio {
> +	HPWMI_WIFI = 0,
> +	HPWMI_BLUETOOTH = 1,
> +	HPWMI_WWAN = 2,
> +};
> +
> +enum hp_wmi_event_ids {
> +	HPWMI_DOCK_EVENT = 1,
> +	HPWMI_PARK_HDD = 2,
> +	HPWMI_SMART_ADAPTER = 3,
> +	HPWMI_BEZEL_BUTTON = 4,
> +	HPWMI_WIRELESS = 5,
> +	HPWMI_CPU_BATTERY_THROTTLE = 6,
> +	HPWMI_LOCK_SWITCH = 7,
> +};
> +
> +static int __devinit hp_wmi_bios_setup(struct platform_device *device);
> +static int __exit hp_wmi_bios_remove(struct platform_device *device);
> +static int hp_wmi_resume_handler(struct device *device);
> +
> +struct bios_args {
> +	u32 signature;
> +	u32 command;
> +	u32 commandtype;
> +	u32 datasize;
> +	u32 data;
> +};
> +
> +struct bios_return {
> +	u32 sigpass;
> +	u32 return_code;
> +};
> +
> +enum hp_return_value {
> +	HPWMI_RET_WRONG_SIGNATURE	= 0x02,
> +	HPWMI_RET_UNKNOWN_COMMAND	= 0x03,
> +	HPWMI_RET_UNKNOWN_CMDTYPE	= 0x04,
> +	HPWMI_RET_INVALID_PARAMETERS	= 0x05,
> +};
> +
> +enum hp_wireless2_bits {
> +	HPWMI_POWER_STATE	= 0x01,
> +	HPWMI_POWER_SOFT	= 0x02,
> +	HPWMI_POWER_BIOS	= 0x04,
> +	HPWMI_POWER_HARD	= 0x08,
> +};
> +
> +#define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \
> +			 != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD))
> +#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
> +
> +struct bios_rfkill2_device_state {
> +	u8 radio_type;
> +	u8 bus_type;
> +	u16 vendor_id;
> +	u16 product_id;
> +	u16 subsys_vendor_id;
> +	u16 subsys_product_id;
> +	u8 rfkill_id;
> +	u8 power;
> +	u8 unknown[4];
> +};
> +
> +/* 7 devices fit into the 128 byte buffer */
> +#define HPWMI_MAX_RFKILL2_DEVICES	7
> +
> +struct bios_rfkill2_state {
> +	u8 unknown[7];
> +	u8 count;
> +	u8 pad[8];
> +	struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES];
> +};
> +
> +static struct platform_device *hp_wmi_platform_dev;
> +
> +static const struct dev_pm_ops hp_wmi_pm_ops = {
> +	.resume  = hp_wmi_resume_handler,
> +	.restore  = hp_wmi_resume_handler,
> +};
> +
> +static struct platform_driver hp_wmi_driver = {
> +	.driver = {
> +		.name = "hp-wmi",
> +		.owner = THIS_MODULE,
> +		.pm = &hp_wmi_pm_ops,
> +	},
> +	.probe = hp_wmi_bios_setup,
> +	.remove = hp_wmi_bios_remove,
> +};
> +
> +static int hp_wmi_perform_query(int query, int write, void *buffer,
> +				int insize, int outsize)
> +{
> +	struct bios_return *bios_return;
> +	int actual_outsize;
> +	union acpi_object *obj;
> +	struct bios_args args = {
> +		.signature = 0x55434553,
> +		.command = write ? 0x2 : 0x1,
> +		.commandtype = query,
> +		.datasize = insize,
> +		.data = 0,
> +	};
> +	struct acpi_buffer input = { sizeof(struct bios_args), &args };
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	u32 rc;
> +
> +	if (WARN_ON(insize > sizeof(args.data)))
> +		return -EINVAL;
> +	memcpy(&args.data, buffer, insize);
> +
> +	wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
> +
> +	obj = output.pointer;
> +
> +	if (!obj)
> +		return -EINVAL;
> +	else if (obj->type != ACPI_TYPE_BUFFER) {
> +		kfree(obj);
> +		return -EINVAL;
> +	}
> +
> +	bios_return = (struct bios_return *)obj->buffer.pointer;
> +	rc = bios_return->return_code;
> +
> +	if (rc) {
> +		if (rc != HPWMI_RET_UNKNOWN_CMDTYPE)
> +			pr_warn("query 0x%x returned error 0x%x\n", query, rc);
> +		kfree(obj);
> +		return rc;
> +	}
> +
> +	if (!outsize) {
> +		/* ignore output data */
> +		kfree(obj);
> +		return 0;
> +	}
> +
> +	actual_outsize = min(outsize,
> +			(int)(obj->buffer.length - sizeof(*bios_return)));
> +	memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return),
> +							actual_outsize);
> +	memset(buffer + actual_outsize, 0, outsize - actual_outsize);
> +	kfree(obj);
> +	return 0;
> +}
> +
> +static int hp_wmi_type_1b_write(u8 power_ctrl, int state)
> +{
> +	char buffer[4] = { 0x01, 0x00, power_ctrl, state };
> +
> +	if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1,
> +				buffer, sizeof(buffer), 0)) {
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int hp_wmi_type_1b_read(struct bios_rfkill2_state *rf_info)
> +{
> +	int err;
> +
> +	err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, rf_info,
> +			0, sizeof(struct bios_rfkill2_state));
> +	if (err)
> +		return err;
> +	
> +	return 0;
> +}
> +
> +static int get_wireless_dev_structure_wmi_1b(
> +	struct bios_rfkill2_device_state *rfkill_dev, enum hp_wmi_radio r)
> +{
> +	struct bios_rfkill2_state rf_info;
> +	struct bios_rfkill2_device_state *dev = NULL;
> +	int err, i;
> +	bool found = false;
> +
> +	err = hp_wmi_type_1b_read(&rf_info);
> +	if (err)
> +		return -EINVAL;
> +	
> +	for (i = 0; i < rf_info.count; i++) {
> +		dev = &rf_info.device[i];
> +		if (dev->radio_type == r) {
> +			found = true;
> +			break;
> +		}
> +	}
> +	if (!found)
> +		return FWTS_NOT_PRESENT;
> +	memcpy(rfkill_dev, dev, sizeof(struct bios_rfkill2_device_state));
> +
> +	return 0;
> +}
> +
> +static int convert_wireless_device_type(int radio)
> +{
> +	int dev_type = HPWMI_WIFI;
> +
> +	switch (radio) {
> +	case FWTS_WIFI:
> +		dev_type = HPWMI_WIFI;
> +		break;
> +	case FWTS_BLUETOOTH:
> +		dev_type = HPWMI_BLUETOOTH;
> +		break;
> +	case FWTS_WWAN:
> +		dev_type = HPWMI_WWAN;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return dev_type;
> +}
> +
> +static int get_wireless_device(fwts_oem_wireless __user *wd)
> +{
> +	struct bios_rfkill2_device_state rfdev;
> +	int err;
> +	int dev_type;
> +
> +	dev_type = convert_wireless_device_type(wd->device.device_type);
> +	err = get_wireless_dev_structure_wmi_1b(&rfdev, dev_type);
> +	if (err == FWTS_NOT_PRESENT) {
> +		wd->oem_parameter.func_status = FWTS_NOT_PRESENT;
> +		return 0;
> +	} else if (err == FWTS_SUCCESS) {
> +		wd->oem_parameter.func_status = FWTS_SUCCESS;
> +		wd->device.device_bus = rfdev.bus_type;
> +		wd->device.vendor_id = rfdev.vendor_id;
> +		wd->device.device_id = rfdev.product_id;
> +		wd->device.soft_kill_status = IS_SWBLOCKED(rfdev.power);
> +		wd->device.hard_kill_status = IS_HWBLOCKED(rfdev.power);
> +		return 0;
> +	}
> +
> +	return err;
> +}
> +
> +static int set_wireless_device(fwts_oem_wireless __user *wd)
> +{
> +	int err;
> +	struct bios_rfkill2_device_state dev;
> +	int dev_type;
> +	int target_power;
> +
> +	dev_type = convert_wireless_device_type(wd->device.device_type);
> +	err = get_wireless_dev_structure_wmi_1b(&dev, dev_type);
> +	if (err)
> +		return err;
> +	target_power = !wd->device.soft_kill_status;
> +	err = hp_wmi_type_1b_write(dev.rfkill_id, target_power);
> +	if (err)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static int handle_oem_wireless_cmd(fwts_oem_wireless __user *wd)
> +{
> +	int ret = 0;
> +	u8 cmd;
> +
> +	cmd = wd->oem_parameter.func;
> +	switch(cmd) {
> +	case GET_DEVICE:
> +		ret = get_wireless_device(wd);
> +		break;
> +	case SET_DEVICE:
> +		ret = set_wireless_device(wd);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static long fwts_oem_runtime_ioctl(struct file *file, unsigned int cmd,
> +							unsigned long arg)
> +{
> +	switch(cmd) {
> +	case OEM_WIRELESS_CMD:
> +		handle_oem_wireless_cmd((fwts_oem_wireless __user *) arg);
> +		break;
> +	default:
> +		break;
> +	}
> +	return 0;
> +}
> +
> +static int fwts_oem_runtime_open(struct inode *inode, struct file *file)
> +{
> +	return 0;
> +}
> +
> +static int fwts_oem_runtime_close(struct inode *inode, struct file *file)
> +{
> +	return 0;
> +}
> +
> +static const struct file_operations fwts_oem_runtime_fops = {
> +	.owner		= THIS_MODULE,
> +	.unlocked_ioctl = fwts_oem_runtime_ioctl,
> +	.open		= fwts_oem_runtime_open,
> +	.release	= fwts_oem_runtime_close,
> +	.llseek		= no_llseek,
> +};
> +
> +static struct miscdevice fwts_oem_runtime_dev = {
> +	MISC_DYNAMIC_MINOR,
> +	"fwts_oem",
> +	&fwts_oem_runtime_fops
> +};
> +
> +static void hp_wmi_notify(u32 value, void *context)
> +{
> +	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
> +	union acpi_object *obj;
> +	u32 event_id, event_data;
> +	u32 *location;
> +	acpi_status status;
> +
> +	status = wmi_get_event_data(value, &response);
> +	if (status != AE_OK) {
> +		pr_info("bad event status 0x%x\n", status);
> +		return;
> +	}
> +
> +	obj = (union acpi_object *)response.pointer;
> +
> +	if (!obj)
> +		return;
> +	if (obj->type != ACPI_TYPE_BUFFER) {
> +		pr_info("Unknown response received %d\n", obj->type);
> +		kfree(obj);
> +		return;
> +	}
> +
> +	location = (u32 *)obj->buffer.pointer;
> +	if (obj->buffer.length == 8) {
> +		event_id = *location;
> +		event_data = *(location + 1);
> +	} else if (obj->buffer.length == 16) {
> +		event_id = *location;
> +		event_data = *(location + 2);
> +	} else {
> +		pr_info("Unknown buffer length %d\n", obj->buffer.length);
> +		kfree(obj);
> +		return;
> +	}
> +	kfree(obj);
> +
> +	switch (event_id) {
> +	case HPWMI_WIRELESS:
> +		pr_info("HPWMI_WIRELESS event is received\n");
> +		break;
> +	default:
> +		pr_info("Event_id - %d - 0x%x\n", event_id, event_data);
> +		break;
> +	}
> +}
> +
> +static int __init hp_wmi_input_setup(void)
> +{
> +	acpi_status status;
> +	int err;
> +
> +	status = wmi_install_notify_handler(HPWMI_EVENT_GUID,
> +						hp_wmi_notify, NULL);
> +	if (ACPI_FAILURE(status)) {
> +		pr_info("input_setup failed\n");
> +		err = -EIO;
> +		goto err_uninstall_notifier;
> +	}
> +
> +	return 0;
> +
> + err_uninstall_notifier:
> +	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
> +	return err;
> +}
> +
> +static void hp_wmi_input_destroy(void)
> +{
> +	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
> +}
> +
> +static void cleanup_sysfs(struct platform_device *device)
> +{
> +
> +}
> +
> +
> +static int __devinit hp_wmi_bios_setup(struct platform_device *device)
> +{
> +	return 0;
> +}
> +
> +static int __exit hp_wmi_bios_remove(struct platform_device *device)
> +{
> +	cleanup_sysfs(device);
> +
> +	return 0;
> +}
> +
> +static int hp_wmi_resume_handler(struct device *device)
> +{
> +	return 0;
> +}
> +
> +static int __init hp_wmi_init(void)
> +{
> +	int err;
> +	int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
> +	int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
> +
> +	if (event_capable) {
> +		err = hp_wmi_input_setup();
> +		if (err)
> +			return err;
> +	}
> +
> +	if (bios_capable) {
> +		err = platform_driver_register(&hp_wmi_driver);
> +		if (err)
> +			goto err_driver_reg;
> +		hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1);
> +		if (!hp_wmi_platform_dev) {
> +			err = -ENOMEM;
> +			goto err_device_alloc;
> +		}
> +		err = platform_device_add(hp_wmi_platform_dev);
> +		if (err)
> +			goto err_device_add;
> +	}
> +
> +	if (!bios_capable && !event_capable)
> +		return -ENODEV;
> +
> +	err = misc_register(&fwts_oem_runtime_dev);
> +	if (err) {
> +		printk(KERN_ERR "fwts_oem: can't misc_register on minor=%d\n",
> +					MISC_DYNAMIC_MINOR);
> +		return err;
> +	}
> +
> +	return 0;
> +
> +err_device_add:
> +	platform_device_put(hp_wmi_platform_dev);
> +err_device_alloc:
> +	platform_driver_unregister(&hp_wmi_driver);
> +err_driver_reg:
> +	if (event_capable)
> +		hp_wmi_input_destroy();
> +
> +	return err;
> +}
> +
> +static void __exit hp_wmi_exit(void)
> +{
> +	if (wmi_has_guid(HPWMI_EVENT_GUID))
> +		hp_wmi_input_destroy();
> +
> +	if (hp_wmi_platform_dev) {
> +		platform_device_unregister(hp_wmi_platform_dev);
> +		platform_driver_unregister(&hp_wmi_driver);
> +	}
> +
> +	misc_deregister(&fwts_oem_runtime_dev);
> +}
> +
> +module_init(hp_wmi_init);
> +module_exit(hp_wmi_exit);
>
diff mbox

Patch

diff --git a/oem/Makefile b/oem/Makefile
new file mode 100644
index 0000000..ab73846
--- /dev/null
+++ b/oem/Makefile
@@ -0,0 +1,6 @@ 
+obj-m += hp-wmi.o
+all:
+	make -C /lib/modules/`uname -r`/build M=`pwd` modules
+
+clean:
+	make -C /lib/modules/`uname -r`/build M=`pwd` clean
diff --git a/oem/hp-wmi.c b/oem/hp-wmi.c
new file mode 100644
index 0000000..eff6faf
--- /dev/null
+++ b/oem/hp-wmi.c
@@ -0,0 +1,533 @@ 
+/*
+ * Copyright (C) 2011-2012 Canonical
+ *
+ * Portions based on hp-wmi.c:
+ * Copyright (C) 2008 Red Hat <mjg@redhat.com>
+ * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ *  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 "-fwts: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/string.h>
+
+#include <linux/proc_fs.h>
+#include <linux/miscdevice.h>
+
+#include "fwts-oem.h"
+
+MODULE_AUTHOR("Alex Hung <alex.hung@canonical.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
+MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
+
+#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
+#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+
+#define HPWMI_DISPLAY_QUERY	0x1
+#define HPWMI_HDDTEMP_QUERY	0x2
+#define HPWMI_ALS_QUERY		0x3
+#define HPWMI_HARDWARE_QUERY	0x4
+#define HPWMI_WIRELESS_QUERY	0x5
+#define HPWMI_HOTKEY_QUERY	0xc
+#define HPWMI_WIRELESS2_QUERY	0x1b
+
+enum hp_wmi_radio {
+	HPWMI_WIFI = 0,
+	HPWMI_BLUETOOTH = 1,
+	HPWMI_WWAN = 2,
+};
+
+enum hp_wmi_event_ids {
+	HPWMI_DOCK_EVENT = 1,
+	HPWMI_PARK_HDD = 2,
+	HPWMI_SMART_ADAPTER = 3,
+	HPWMI_BEZEL_BUTTON = 4,
+	HPWMI_WIRELESS = 5,
+	HPWMI_CPU_BATTERY_THROTTLE = 6,
+	HPWMI_LOCK_SWITCH = 7,
+};
+
+static int __devinit hp_wmi_bios_setup(struct platform_device *device);
+static int __exit hp_wmi_bios_remove(struct platform_device *device);
+static int hp_wmi_resume_handler(struct device *device);
+
+struct bios_args {
+	u32 signature;
+	u32 command;
+	u32 commandtype;
+	u32 datasize;
+	u32 data;
+};
+
+struct bios_return {
+	u32 sigpass;
+	u32 return_code;
+};
+
+enum hp_return_value {
+	HPWMI_RET_WRONG_SIGNATURE	= 0x02,
+	HPWMI_RET_UNKNOWN_COMMAND	= 0x03,
+	HPWMI_RET_UNKNOWN_CMDTYPE	= 0x04,
+	HPWMI_RET_INVALID_PARAMETERS	= 0x05,
+};
+
+enum hp_wireless2_bits {
+	HPWMI_POWER_STATE	= 0x01,
+	HPWMI_POWER_SOFT	= 0x02,
+	HPWMI_POWER_BIOS	= 0x04,
+	HPWMI_POWER_HARD	= 0x08,
+};
+
+#define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \
+			 != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD))
+#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
+
+struct bios_rfkill2_device_state {
+	u8 radio_type;
+	u8 bus_type;
+	u16 vendor_id;
+	u16 product_id;
+	u16 subsys_vendor_id;
+	u16 subsys_product_id;
+	u8 rfkill_id;
+	u8 power;
+	u8 unknown[4];
+};
+
+/* 7 devices fit into the 128 byte buffer */
+#define HPWMI_MAX_RFKILL2_DEVICES	7
+
+struct bios_rfkill2_state {
+	u8 unknown[7];
+	u8 count;
+	u8 pad[8];
+	struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES];
+};
+
+static struct platform_device *hp_wmi_platform_dev;
+
+static const struct dev_pm_ops hp_wmi_pm_ops = {
+	.resume  = hp_wmi_resume_handler,
+	.restore  = hp_wmi_resume_handler,
+};
+
+static struct platform_driver hp_wmi_driver = {
+	.driver = {
+		.name = "hp-wmi",
+		.owner = THIS_MODULE,
+		.pm = &hp_wmi_pm_ops,
+	},
+	.probe = hp_wmi_bios_setup,
+	.remove = hp_wmi_bios_remove,
+};
+
+static int hp_wmi_perform_query(int query, int write, void *buffer,
+				int insize, int outsize)
+{
+	struct bios_return *bios_return;
+	int actual_outsize;
+	union acpi_object *obj;
+	struct bios_args args = {
+		.signature = 0x55434553,
+		.command = write ? 0x2 : 0x1,
+		.commandtype = query,
+		.datasize = insize,
+		.data = 0,
+	};
+	struct acpi_buffer input = { sizeof(struct bios_args), &args };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	u32 rc;
+
+	if (WARN_ON(insize > sizeof(args.data)))
+		return -EINVAL;
+	memcpy(&args.data, buffer, insize);
+
+	wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
+
+	obj = output.pointer;
+
+	if (!obj)
+		return -EINVAL;
+	else if (obj->type != ACPI_TYPE_BUFFER) {
+		kfree(obj);
+		return -EINVAL;
+	}
+
+	bios_return = (struct bios_return *)obj->buffer.pointer;
+	rc = bios_return->return_code;
+
+	if (rc) {
+		if (rc != HPWMI_RET_UNKNOWN_CMDTYPE)
+			pr_warn("query 0x%x returned error 0x%x\n", query, rc);
+		kfree(obj);
+		return rc;
+	}
+
+	if (!outsize) {
+		/* ignore output data */
+		kfree(obj);
+		return 0;
+	}
+
+	actual_outsize = min(outsize,
+			(int)(obj->buffer.length - sizeof(*bios_return)));
+	memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return),
+							actual_outsize);
+	memset(buffer + actual_outsize, 0, outsize - actual_outsize);
+	kfree(obj);
+	return 0;
+}
+
+static int hp_wmi_type_1b_write(u8 power_ctrl, int state)
+{
+	char buffer[4] = { 0x01, 0x00, power_ctrl, state };
+
+	if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1,
+				buffer, sizeof(buffer), 0)) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hp_wmi_type_1b_read(struct bios_rfkill2_state *rf_info)
+{
+	int err;
+
+	err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, rf_info,
+			0, sizeof(struct bios_rfkill2_state));
+	if (err)
+		return err;
+	
+	return 0;
+}
+
+static int get_wireless_dev_structure_wmi_1b(
+	struct bios_rfkill2_device_state *rfkill_dev, enum hp_wmi_radio r)
+{
+	struct bios_rfkill2_state rf_info;
+	struct bios_rfkill2_device_state *dev = NULL;
+	int err, i;
+	bool found = false;
+
+	err = hp_wmi_type_1b_read(&rf_info);
+	if (err)
+		return -EINVAL;
+	
+	for (i = 0; i < rf_info.count; i++) {
+		dev = &rf_info.device[i];
+		if (dev->radio_type == r) {
+			found = true;
+			break;
+		}
+	}
+	if (!found)
+		return FWTS_NOT_PRESENT;
+	memcpy(rfkill_dev, dev, sizeof(struct bios_rfkill2_device_state));
+
+	return 0;
+}
+
+static int convert_wireless_device_type(int radio)
+{
+	int dev_type = HPWMI_WIFI;
+
+	switch (radio) {
+	case FWTS_WIFI:
+		dev_type = HPWMI_WIFI;
+		break;
+	case FWTS_BLUETOOTH:
+		dev_type = HPWMI_BLUETOOTH;
+		break;
+	case FWTS_WWAN:
+		dev_type = HPWMI_WWAN;
+		break;
+	default:
+		break;
+	}
+
+	return dev_type;
+}
+
+static int get_wireless_device(fwts_oem_wireless __user *wd)
+{
+	struct bios_rfkill2_device_state rfdev;
+	int err;
+	int dev_type;
+
+	dev_type = convert_wireless_device_type(wd->device.device_type);
+	err = get_wireless_dev_structure_wmi_1b(&rfdev, dev_type);
+	if (err == FWTS_NOT_PRESENT) {
+		wd->oem_parameter.func_status = FWTS_NOT_PRESENT;
+		return 0;
+	} else if (err == FWTS_SUCCESS) {
+		wd->oem_parameter.func_status = FWTS_SUCCESS;
+		wd->device.device_bus = rfdev.bus_type;
+		wd->device.vendor_id = rfdev.vendor_id;
+		wd->device.device_id = rfdev.product_id;
+		wd->device.soft_kill_status = IS_SWBLOCKED(rfdev.power);
+		wd->device.hard_kill_status = IS_HWBLOCKED(rfdev.power);
+		return 0;
+	} 
+
+	return err;
+}
+
+static int set_wireless_device(fwts_oem_wireless __user *wd)
+{
+	int err;
+	struct bios_rfkill2_device_state dev;
+	int dev_type;
+	int target_power;
+
+	dev_type = convert_wireless_device_type(wd->device.device_type);
+	err = get_wireless_dev_structure_wmi_1b(&dev, dev_type);
+	if (err)
+		return err;
+	target_power = !wd->device.soft_kill_status;
+	err = hp_wmi_type_1b_write(dev.rfkill_id, target_power);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int handle_oem_wireless_cmd(fwts_oem_wireless __user *wd)
+{
+	int ret = 0;
+	u8 cmd;
+
+	cmd = wd->oem_parameter.func;
+	switch(cmd) {
+	case GET_DEVICE:
+		ret = get_wireless_device(wd);
+		break;
+	case SET_DEVICE:
+		ret = set_wireless_device(wd);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static long fwts_oem_runtime_ioctl(struct file *file, unsigned int cmd,
+							unsigned long arg)
+{
+	switch(cmd) {
+	case OEM_WIRELESS_CMD:
+		handle_oem_wireless_cmd((fwts_oem_wireless __user *) arg);
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int fwts_oem_runtime_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int fwts_oem_runtime_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static const struct file_operations fwts_oem_runtime_fops = {
+	.owner		= THIS_MODULE,
+	.unlocked_ioctl = fwts_oem_runtime_ioctl,
+	.open		= fwts_oem_runtime_open,
+	.release	= fwts_oem_runtime_close,
+	.llseek		= no_llseek,
+};
+
+static struct miscdevice fwts_oem_runtime_dev = {
+	MISC_DYNAMIC_MINOR,
+	"fwts_oem",
+	&fwts_oem_runtime_fops
+};
+
+static void hp_wmi_notify(u32 value, void *context)
+{
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	u32 event_id, event_data;
+	u32 *location;
+	acpi_status status;
+
+	status = wmi_get_event_data(value, &response);
+	if (status != AE_OK) {
+		pr_info("bad event status 0x%x\n", status);
+		return;
+	}
+
+	obj = (union acpi_object *)response.pointer;
+
+	if (!obj)
+		return;
+	if (obj->type != ACPI_TYPE_BUFFER) {
+		pr_info("Unknown response received %d\n", obj->type);
+		kfree(obj);
+		return;
+	}
+
+	location = (u32 *)obj->buffer.pointer;
+	if (obj->buffer.length == 8) {
+		event_id = *location;
+		event_data = *(location + 1);
+	} else if (obj->buffer.length == 16) {
+		event_id = *location;
+		event_data = *(location + 2);
+	} else {
+		pr_info("Unknown buffer length %d\n", obj->buffer.length);
+		kfree(obj);
+		return;
+	}
+	kfree(obj);
+
+	switch (event_id) {
+	case HPWMI_WIRELESS:
+		pr_info("HPWMI_WIRELESS event is received\n");
+		break;
+	default:
+		pr_info("Event_id - %d - 0x%x\n", event_id, event_data);
+		break;
+	}
+}
+
+static int __init hp_wmi_input_setup(void)
+{
+	acpi_status status;
+	int err;
+
+	status = wmi_install_notify_handler(HPWMI_EVENT_GUID,
+						hp_wmi_notify, NULL);
+	if (ACPI_FAILURE(status)) {
+		pr_info("input_setup failed\n");
+		err = -EIO;
+		goto err_uninstall_notifier;
+	}
+
+	return 0;
+
+ err_uninstall_notifier:
+	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+	return err;
+}
+
+static void hp_wmi_input_destroy(void)
+{
+	wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+}
+
+static void cleanup_sysfs(struct platform_device *device)
+{
+
+}
+
+
+static int __devinit hp_wmi_bios_setup(struct platform_device *device)
+{
+	return 0;
+}
+
+static int __exit hp_wmi_bios_remove(struct platform_device *device)
+{
+	cleanup_sysfs(device);
+
+	return 0;
+}
+
+static int hp_wmi_resume_handler(struct device *device)
+{
+	return 0;
+}
+
+static int __init hp_wmi_init(void)
+{
+	int err;
+	int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
+	int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
+
+	if (event_capable) {
+		err = hp_wmi_input_setup();
+		if (err)
+			return err;
+	}
+
+	if (bios_capable) {
+		err = platform_driver_register(&hp_wmi_driver);
+		if (err)
+			goto err_driver_reg;
+		hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1);
+		if (!hp_wmi_platform_dev) {
+			err = -ENOMEM;
+			goto err_device_alloc;
+		}
+		err = platform_device_add(hp_wmi_platform_dev);
+		if (err)
+			goto err_device_add;
+	}
+
+	if (!bios_capable && !event_capable)
+		return -ENODEV;
+
+	err = misc_register(&fwts_oem_runtime_dev);
+	if (err) {
+		printk(KERN_ERR "fwts_oem: can't misc_register on minor=%d\n",
+					MISC_DYNAMIC_MINOR);
+		return err;
+	}
+
+	return 0;
+
+err_device_add:
+	platform_device_put(hp_wmi_platform_dev);
+err_device_alloc:
+	platform_driver_unregister(&hp_wmi_driver);
+err_driver_reg:
+	if (event_capable)
+		hp_wmi_input_destroy();
+
+	return err;
+}
+
+static void __exit hp_wmi_exit(void)
+{
+	if (wmi_has_guid(HPWMI_EVENT_GUID))
+		hp_wmi_input_destroy();
+
+	if (hp_wmi_platform_dev) {
+		platform_device_unregister(hp_wmi_platform_dev);
+		platform_driver_unregister(&hp_wmi_driver);
+	}
+
+	misc_deregister(&fwts_oem_runtime_dev);
+}
+
+module_init(hp_wmi_init);
+module_exit(hp_wmi_exit);