Skip to content

Commit

Permalink
wmi: Add support for module autoloading
Browse files Browse the repository at this point in the history
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: Matthew Garrett <mjg@redhat.com>
Tested-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
Acked-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
Signed-off-by: Len Brown <len.brown@intel.com>
  • Loading branch information
Matthew Garrett authored and Len Brown committed Nov 5, 2009
1 parent b419148 commit 1caab3c
Showing 1 changed file with 173 additions and 2 deletions.
175 changes: 173 additions & 2 deletions drivers/platform/x86/wmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}

Expand All @@ -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) {
Expand Down

0 comments on commit 1caab3c

Please sign in to comment.