Commit 1caab3c1 authored by Matthew Garrett's avatar Matthew Garrett Committed by Len Brown

wmi: Add support for module autoloading

WMI provides interface-specific GUIDs that are exported from modules as
modalises, but the core currently generates no events to trigger module
loading. This patch adds support for registering devices for each WMI GUID
and generating the appropriate uevent.

Based heavily on a patch by Carlos Corbacho (<carlos@strangeworlds.co.uk>).
Signed-off-by: default avatarMatthew Garrett <mjg@redhat.com>
Tested-by: default avatarCarlos Corbacho <carlos@strangeworlds.co.uk>
Acked-by: default avatarCarlos Corbacho <carlos@strangeworlds.co.uk>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent b419148e
......@@ -30,6 +30,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
......@@ -65,6 +66,7 @@ struct wmi_block {
acpi_handle handle;
wmi_notify_handler handler;
void *handler_data;
struct device *dev;
};
static struct wmi_block wmi_blocks;
......@@ -195,6 +197,34 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest)
return true;
}
/*
* Convert a raw GUID to the ACII string representation
*/
static int wmi_gtoa(const char *in, char *out)
{
int i;
for (i = 3; i >= 0; i--)
out += sprintf(out, "%02X", in[i] & 0xFF);
out += sprintf(out, "-");
out += sprintf(out, "%02X", in[5] & 0xFF);
out += sprintf(out, "%02X", in[4] & 0xFF);
out += sprintf(out, "-");
out += sprintf(out, "%02X", in[7] & 0xFF);
out += sprintf(out, "%02X", in[6] & 0xFF);
out += sprintf(out, "-");
out += sprintf(out, "%02X", in[8] & 0xFF);
out += sprintf(out, "%02X", in[9] & 0xFF);
out += sprintf(out, "-");
for (i = 10; i <= 15; i++)
out += sprintf(out, "%02X", in[i] & 0xFF);
out = '\0';
return 0;
}
static bool find_guid(const char *guid_string, struct wmi_block **out)
{
char tmp[16], guid_input[16];
......@@ -554,6 +584,138 @@ bool wmi_has_guid(const char *guid_string)
}
EXPORT_SYMBOL_GPL(wmi_has_guid);
/*
* sysfs interface
*/
static ssize_t show_modalias(struct device *dev, struct device_attribute *attr,
char *buf)
{
char guid_string[37];
struct wmi_block *wblock;
wblock = dev_get_drvdata(dev);
if (!wblock)
return -ENOMEM;
wmi_gtoa(wblock->gblock.guid, guid_string);
return sprintf(buf, "wmi:%s\n", guid_string);
}
static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL);
static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
{
char guid_string[37];
struct wmi_block *wblock;
if (add_uevent_var(env, "MODALIAS="))
return -ENOMEM;
wblock = dev_get_drvdata(dev);
if (!wblock)
return -ENOMEM;
wmi_gtoa(wblock->gblock.guid, guid_string);
strcpy(&env->buf[env->buflen - 1], "wmi:");
memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
env->buflen += 40;
return 0;
}
static void wmi_dev_free(struct device *dev)
{
kfree(dev);
}
static struct class wmi_class = {
.name = "wmi",
.dev_release = wmi_dev_free,
.dev_uevent = wmi_dev_uevent,
};
static int wmi_create_devs(void)
{
int result;
char guid_string[37];
struct guid_block *gblock;
struct wmi_block *wblock;
struct list_head *p;
struct device *guid_dev;
/* Create devices for all the GUIDs */
list_for_each(p, &wmi_blocks.list) {
wblock = list_entry(p, struct wmi_block, list);
guid_dev = kzalloc(sizeof(struct device), GFP_KERNEL);
if (!guid_dev)
return -ENOMEM;
wblock->dev = guid_dev;
guid_dev->class = &wmi_class;
dev_set_drvdata(guid_dev, wblock);
gblock = &wblock->gblock;
wmi_gtoa(gblock->guid, guid_string);
dev_set_name(guid_dev, guid_string);
result = device_register(guid_dev);
if (result)
return result;
result = device_create_file(guid_dev, &dev_attr_modalias);
if (result)
return result;
}
return 0;
}
static void wmi_remove_devs(void)
{
struct guid_block *gblock;
struct wmi_block *wblock;
struct list_head *p;
struct device *guid_dev;
/* Delete devices for all the GUIDs */
list_for_each(p, &wmi_blocks.list) {
wblock = list_entry(p, struct wmi_block, list);
guid_dev = wblock->dev;
gblock = &wblock->gblock;
device_remove_file(guid_dev, &dev_attr_modalias);
device_unregister(guid_dev);
}
}
static void wmi_class_exit(void)
{
wmi_remove_devs();
class_unregister(&wmi_class);
}
static int wmi_class_init(void)
{
int ret;
ret = class_register(&wmi_class);
if (ret)
return ret;
ret = wmi_create_devs();
if (ret)
wmi_class_exit();
return ret;
}
/*
* Parse the _WDG method for the GUID data blocks
*/
......@@ -709,10 +871,17 @@ static int __init acpi_wmi_init(void)
if (result < 0) {
printk(KERN_INFO PREFIX "Error loading mapper\n");
} else {
printk(KERN_INFO PREFIX "Mapper loaded\n");
return -ENODEV;
}
result = wmi_class_init();
if (result) {
acpi_bus_unregister_driver(&acpi_wmi_driver);
return result;
}
printk(KERN_INFO PREFIX "Mapper loaded\n");
return result;
}
......@@ -721,6 +890,8 @@ static void __exit acpi_wmi_exit(void)
struct list_head *p, *tmp;
struct wmi_block *wblock;
wmi_class_exit();
acpi_bus_unregister_driver(&acpi_wmi_driver);
list_for_each_safe(p, tmp, &wmi_blocks.list) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment