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: 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>
This commit is contained in:
parent
b419148e56
commit
1caab3c1a9
|
@ -30,6 +30,7 @@
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
#include <linux/device.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
#include <linux/acpi.h>
|
#include <linux/acpi.h>
|
||||||
#include <acpi/acpi_bus.h>
|
#include <acpi/acpi_bus.h>
|
||||||
|
@ -65,6 +66,7 @@ struct wmi_block {
|
||||||
acpi_handle handle;
|
acpi_handle handle;
|
||||||
wmi_notify_handler handler;
|
wmi_notify_handler handler;
|
||||||
void *handler_data;
|
void *handler_data;
|
||||||
|
struct device *dev;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct wmi_block wmi_blocks;
|
static struct wmi_block wmi_blocks;
|
||||||
|
@ -195,6 +197,34 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest)
|
||||||
return true;
|
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)
|
static bool find_guid(const char *guid_string, struct wmi_block **out)
|
||||||
{
|
{
|
||||||
char tmp[16], guid_input[16];
|
char tmp[16], guid_input[16];
|
||||||
|
@ -554,6 +584,138 @@ bool wmi_has_guid(const char *guid_string)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(wmi_has_guid);
|
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
|
* Parse the _WDG method for the GUID data blocks
|
||||||
*/
|
*/
|
||||||
|
@ -709,10 +871,17 @@ static int __init acpi_wmi_init(void)
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
printk(KERN_INFO PREFIX "Error loading mapper\n");
|
printk(KERN_INFO PREFIX "Error loading mapper\n");
|
||||||
} else {
|
return -ENODEV;
|
||||||
printk(KERN_INFO PREFIX "Mapper loaded\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = wmi_class_init();
|
||||||
|
if (result) {
|
||||||
|
acpi_bus_unregister_driver(&acpi_wmi_driver);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
printk(KERN_INFO PREFIX "Mapper loaded\n");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,6 +890,8 @@ static void __exit acpi_wmi_exit(void)
|
||||||
struct list_head *p, *tmp;
|
struct list_head *p, *tmp;
|
||||||
struct wmi_block *wblock;
|
struct wmi_block *wblock;
|
||||||
|
|
||||||
|
wmi_class_exit();
|
||||||
|
|
||||||
acpi_bus_unregister_driver(&acpi_wmi_driver);
|
acpi_bus_unregister_driver(&acpi_wmi_driver);
|
||||||
|
|
||||||
list_for_each_safe(p, tmp, &wmi_blocks.list) {
|
list_for_each_safe(p, tmp, &wmi_blocks.list) {
|
||||||
|
|
Loading…
Reference in a new issue