From patchwork Mon Dec 10 02:29:15 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alex Hung X-Patchwork-Id: 204813 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from chlorine.canonical.com (chlorine.canonical.com [91.189.94.204]) by ozlabs.org (Postfix) with ESMTP id CE7F42C0253 for ; Mon, 10 Dec 2012 13:29:36 +1100 (EST) Received: from localhost ([127.0.0.1] helo=chlorine.canonical.com) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1Tht7f-0006Ra-Lo; Mon, 10 Dec 2012 02:29:35 +0000 Received: from youngberry.canonical.com ([91.189.89.112]) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1Tht7d-0006RQ-5Q for fwts-devel@lists.ubuntu.com; Mon, 10 Dec 2012 02:29:33 +0000 Received: from [175.41.48.77] (helo=canonical.com) by youngberry.canonical.com with esmtpsa (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1Tht7b-0008AG-L0; Mon, 10 Dec 2012 02:29:33 +0000 From: Alex Hung To: fwts-devel@lists.ubuntu.com Subject: [PATCH 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. Date: Mon, 10 Dec 2012 10:29:15 +0800 Message-Id: <1355106555-21026-3-git-send-email-alex.hung@canonical.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1355106555-21026-1-git-send-email-alex.hung@canonical.com> References: <1355106555-21026-1-git-send-email-alex.hung@canonical.com> X-BeenThere: fwts-devel@lists.ubuntu.com X-Mailman-Version: 2.1.13 Precedence: list List-Id: Firmware Test Suite Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: fwts-devel-bounces@lists.ubuntu.com Errors-To: fwts-devel-bounces@lists.ubuntu.com Signed-off-by: Alex Hung --- 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 + * Copyright (C) 2010, 2011 Anssi Hannula + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fwts-oem.h" + +MODULE_AUTHOR("Alex Hung "); +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);