diff options
Diffstat (limited to 'drivers/acpi/dock.c')
| -rw-r--r-- | drivers/acpi/dock.c | 839 |
1 files changed, 383 insertions, 456 deletions
diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 578b99b71d9..d9339b442a4 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -1,7 +1,9 @@ /* * dock.c - ACPI dock station driver * - * Copyright (C) 2006 Kristen Carlson Accardi <kristen.c.accardi@intel.com> + * Copyright (C) 2006, 2014, Intel Corp. + * Author: Kristen Carlson Accardi <kristen.c.accardi@intel.com> + * Rafael J. Wysocki <rafael.j.wysocki@intel.com> * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * @@ -24,181 +26,199 @@ #include <linux/kernel.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/init.h> #include <linux/types.h> #include <linux/notifier.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> +#include <linux/stddef.h> +#include <linux/acpi.h> -#define ACPI_DOCK_DRIVER_NAME "ACPI Dock Station Driver" +#include "internal.h" -ACPI_MODULE_NAME("dock") +#define ACPI_DOCK_DRIVER_DESCRIPTION "ACPI Dock Station Driver" + +ACPI_MODULE_NAME("dock"); MODULE_AUTHOR("Kristen Carlson Accardi"); -MODULE_DESCRIPTION(ACPI_DOCK_DRIVER_NAME); +MODULE_DESCRIPTION(ACPI_DOCK_DRIVER_DESCRIPTION); MODULE_LICENSE("GPL"); -static struct atomic_notifier_head dock_notifier_list; +static bool immediate_undock = 1; +module_param(immediate_undock, bool, 0644); +MODULE_PARM_DESC(immediate_undock, "1 (default) will cause the driver to " + "undock immediately when the undock button is pressed, 0 will cause" + " the driver to wait for userspace to write the undock sysfs file " + " before undocking"); struct dock_station { acpi_handle handle; unsigned long last_dock_time; u32 flags; - spinlock_t dd_lock; - spinlock_t hp_lock; struct list_head dependent_devices; - struct list_head hotplug_devices; + + struct list_head sibling; + struct platform_device *dock_device; }; +static LIST_HEAD(dock_stations); +static int dock_station_count; struct dock_dependent_device { struct list_head list; - struct list_head hotplug_list; - acpi_handle handle; - acpi_notify_handler handler; - void *context; + struct acpi_device *adev; }; #define DOCK_DOCKING 0x00000001 +#define DOCK_UNDOCKING 0x00000002 +#define DOCK_IS_DOCK 0x00000010 +#define DOCK_IS_ATA 0x00000020 +#define DOCK_IS_BAT 0x00000040 #define DOCK_EVENT 3 #define UNDOCK_EVENT 2 -static struct dock_station *dock_station; +enum dock_callback_type { + DOCK_CALL_HANDLER, + DOCK_CALL_FIXUP, + DOCK_CALL_UEVENT, +}; /***************************************************************************** * Dock Dependent device functions * *****************************************************************************/ /** - * alloc_dock_dependent_device - allocate and init a dependent device - * @handle: the acpi_handle of the dependent device + * add_dock_dependent_device - associate a device with the dock station + * @ds: Dock station. + * @adev: Dependent ACPI device object. * - * Allocate memory for a dependent device structure for a device referenced - * by the acpi handle + * Add the dependent device to the dock's dependent device list. */ -static struct dock_dependent_device * -alloc_dock_dependent_device(acpi_handle handle) +static int add_dock_dependent_device(struct dock_station *ds, + struct acpi_device *adev) { struct dock_dependent_device *dd; dd = kzalloc(sizeof(*dd), GFP_KERNEL); - if (dd) { - dd->handle = handle; - INIT_LIST_HEAD(&dd->list); - INIT_LIST_HEAD(&dd->hotplug_list); - } - return dd; -} + if (!dd) + return -ENOMEM; -/** - * add_dock_dependent_device - associate a device with the dock station - * @ds: The dock station - * @dd: The dependent device - * - * Add the dependent device to the dock's dependent device list. - */ -static void -add_dock_dependent_device(struct dock_station *ds, - struct dock_dependent_device *dd) -{ - spin_lock(&ds->dd_lock); + dd->adev = adev; + INIT_LIST_HEAD(&dd->list); list_add_tail(&dd->list, &ds->dependent_devices); - spin_unlock(&ds->dd_lock); + + return 0; } -/** - * dock_add_hotplug_device - associate a hotplug handler with the dock station - * @ds: The dock station - * @dd: The dependent device struct - * - * Add the dependent device to the dock's hotplug device list - */ -static void -dock_add_hotplug_device(struct dock_station *ds, - struct dock_dependent_device *dd) +static void dock_hotplug_event(struct dock_dependent_device *dd, u32 event, + enum dock_callback_type cb_type) { - spin_lock(&ds->hp_lock); - list_add_tail(&dd->hotplug_list, &ds->hotplug_devices); - spin_unlock(&ds->hp_lock); + struct acpi_device *adev = dd->adev; + + acpi_lock_hp_context(); + + if (!adev->hp) + goto out; + + if (cb_type == DOCK_CALL_FIXUP) { + void (*fixup)(struct acpi_device *); + + fixup = adev->hp->fixup; + if (fixup) { + acpi_unlock_hp_context(); + fixup(adev); + return; + } + } else if (cb_type == DOCK_CALL_UEVENT) { + void (*uevent)(struct acpi_device *, u32); + + uevent = adev->hp->uevent; + if (uevent) { + acpi_unlock_hp_context(); + uevent(adev, event); + return; + } + } else { + int (*notify)(struct acpi_device *, u32); + + notify = adev->hp->notify; + if (notify) { + acpi_unlock_hp_context(); + notify(adev, event); + return; + } + } + + out: + acpi_unlock_hp_context(); } -/** - * dock_del_hotplug_device - remove a hotplug handler from the dock station - * @ds: The dock station - * @dd: the dependent device struct - * - * Delete the dependent device from the dock's hotplug device list - */ -static void -dock_del_hotplug_device(struct dock_station *ds, - struct dock_dependent_device *dd) +static struct dock_station *find_dock_station(acpi_handle handle) { - spin_lock(&ds->hp_lock); - list_del(&dd->hotplug_list); - spin_unlock(&ds->hp_lock); + struct dock_station *ds; + + list_for_each_entry(ds, &dock_stations, sibling) + if (ds->handle == handle) + return ds; + + return NULL; } /** * find_dock_dependent_device - get a device dependent on this dock * @ds: the dock station - * @handle: the acpi_handle of the device we want + * @adev: ACPI device object to find. * * iterate over the dependent device list for this dock. If the * dependent device matches the handle, return. */ static struct dock_dependent_device * -find_dock_dependent_device(struct dock_station *ds, acpi_handle handle) +find_dock_dependent_device(struct dock_station *ds, struct acpi_device *adev) { struct dock_dependent_device *dd; - spin_lock(&ds->dd_lock); - list_for_each_entry(dd, &ds->dependent_devices, list) { - if (handle == dd->handle) { - spin_unlock(&ds->dd_lock); + list_for_each_entry(dd, &ds->dependent_devices, list) + if (adev == dd->adev) return dd; - } - } - spin_unlock(&ds->dd_lock); + return NULL; } -/***************************************************************************** - * Dock functions * - *****************************************************************************/ -/** - * is_dock - see if a device is a dock station - * @handle: acpi handle of the device - * - * If an acpi object has a _DCK method, then it is by definition a dock - * station, so return true. - */ -static int is_dock(acpi_handle handle) +void register_dock_dependent_device(struct acpi_device *adev, + acpi_handle dshandle) { - acpi_status status; - acpi_handle tmp; + struct dock_station *ds = find_dock_station(dshandle); - status = acpi_get_handle(handle, "_DCK", &tmp); - if (ACPI_FAILURE(status)) - return 0; - return 1; + if (ds && !find_dock_dependent_device(ds, adev)) + add_dock_dependent_device(ds, adev); } +/***************************************************************************** + * Dock functions * + *****************************************************************************/ + /** * is_dock_device - see if a device is on a dock station - * @handle: acpi handle of the device + * @adev: ACPI device object to check. * * If this device is either the dock station itself, * or is a device dependent on the dock station, then it * is a dock device */ -int is_dock_device(acpi_handle handle) +int is_dock_device(struct acpi_device *adev) { - if (!dock_station) + struct dock_station *dock_station; + + if (!dock_station_count) return 0; - if (is_dock(handle) || find_dock_dependent_device(dock_station, handle)) + if (acpi_dock_match(adev->handle)) return 1; + list_for_each_entry(dock_station, &dock_stations, sibling) + if (find_dock_dependent_device(dock_station, adev)) + return 1; + return 0; } - EXPORT_SYMBOL_GPL(is_dock_device); /** @@ -210,7 +230,7 @@ EXPORT_SYMBOL_GPL(is_dock_device); */ static int dock_present(struct dock_station *ds) { - unsigned long sta; + unsigned long long sta; acpi_status status; if (ds) { @@ -221,70 +241,30 @@ static int dock_present(struct dock_station *ds) return 0; } - - /** - * dock_create_acpi_device - add new devices to acpi - * @handle - handle of the device to add - * - * This function will create a new acpi_device for the given - * handle if one does not exist already. This should cause - * acpi to scan for drivers for the given devices, and call - * matching driver's add routine. - * - * Returns a pointer to the acpi_device corresponding to the handle. + * hot_remove_dock_devices - Remove dock station devices. + * @ds: Dock station. */ -static struct acpi_device * dock_create_acpi_device(acpi_handle handle) +static void hot_remove_dock_devices(struct dock_station *ds) { - struct acpi_device *device = NULL; - struct acpi_device *parent_device; - acpi_handle parent; - int ret; - - if (acpi_bus_get_device(handle, &device)) { - /* - * no device created for this object, - * so we should create one. - */ - acpi_get_parent(handle, &parent); - if (acpi_bus_get_device(parent, &parent_device)) - parent_device = NULL; - - ret = acpi_bus_add(&device, parent_device, handle, - ACPI_BUS_TYPE_DEVICE); - if (ret) { - pr_debug("error adding bus, %x\n", - -ret); - return NULL; - } - } - return device; -} + struct dock_dependent_device *dd; -/** - * dock_remove_acpi_device - remove the acpi_device struct from acpi - * @handle - the handle of the device to remove - * - * Tell acpi to remove the acpi_device. This should cause any loaded - * driver to have it's remove routine called. - */ -static void dock_remove_acpi_device(acpi_handle handle) -{ - struct acpi_device *device; - int ret; + /* + * Walk the list in reverse order so that devices that have been added + * last are removed first (in case there are some indirect dependencies + * between them). + */ + list_for_each_entry_reverse(dd, &ds->dependent_devices, list) + dock_hotplug_event(dd, ACPI_NOTIFY_EJECT_REQUEST, false); - if (!acpi_bus_get_device(handle, &device)) { - ret = acpi_bus_trim(device, 1); - if (ret) - pr_debug("error removing bus, %x\n", -ret); - } + list_for_each_entry_reverse(dd, &ds->dependent_devices, list) + acpi_bus_trim(dd->adev); } - /** - * hotplug_dock_devices - insert or remove devices on the dock station + * hotplug_dock_devices - Insert devices on a dock station. * @ds: the dock station - * @event: either bus check or eject request + * @event: either bus check or device check request * * Some devices on the dock station need to have drivers called * to perform hotplug operations after a dock event has occurred. @@ -295,68 +275,55 @@ static void hotplug_dock_devices(struct dock_station *ds, u32 event) { struct dock_dependent_device *dd; - spin_lock(&ds->hp_lock); + /* Call driver specific post-dock fixups. */ + list_for_each_entry(dd, &ds->dependent_devices, list) + dock_hotplug_event(dd, event, DOCK_CALL_FIXUP); - /* - * First call driver specific hotplug functions - */ - list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list) { - if (dd->handler) - dd->handler(dd->handle, event, dd->context); - } + /* Call driver specific hotplug functions. */ + list_for_each_entry(dd, &ds->dependent_devices, list) + dock_hotplug_event(dd, event, DOCK_CALL_HANDLER); /* - * Now make sure that an acpi_device is created for each - * dependent device, or removed if this is an eject request. - * This will cause acpi_drivers to be stopped/started if they - * exist + * Check if all devices have been enumerated already. If not, run + * acpi_bus_scan() for them and that will cause scan handlers to be + * attached to device objects or acpi_drivers to be stopped/started if + * they are present. */ list_for_each_entry(dd, &ds->dependent_devices, list) { - if (event == ACPI_NOTIFY_EJECT_REQUEST) - dock_remove_acpi_device(dd->handle); - else - dock_create_acpi_device(dd->handle); + struct acpi_device *adev = dd->adev; + + if (!acpi_device_enumerated(adev)) { + int ret = acpi_bus_scan(adev->handle); + if (ret) + dev_dbg(&adev->dev, "scan error %d\n", -ret); + } } - spin_unlock(&ds->hp_lock); } static void dock_event(struct dock_station *ds, u32 event, int num) { - /* - * we don't do events until someone tells me that - * they would like to have them. - */ -} + struct device *dev = &ds->dock_device->dev; + char event_string[13]; + char *envp[] = { event_string, NULL }; + struct dock_dependent_device *dd; -/** - * eject_dock - respond to a dock eject request - * @ds: the dock station - * - * This is called after _DCK is called, to execute the dock station's - * _EJ0 method. - */ -static void eject_dock(struct dock_station *ds) -{ - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status; - acpi_handle tmp; + if (num == UNDOCK_EVENT) + sprintf(event_string, "EVENT=undock"); + else + sprintf(event_string, "EVENT=dock"); - /* all dock devices should have _EJ0, but check anyway */ - status = acpi_get_handle(ds->handle, "_EJ0", &tmp); - if (ACPI_FAILURE(status)) { - pr_debug("No _EJ0 support for dock device\n"); - return; - } + /* + * Indicate that the status of the dock station has + * changed. + */ + if (num == DOCK_EVENT) + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = 1; + list_for_each_entry(dd, &ds->dependent_devices, list) + dock_hotplug_event(dd, event, DOCK_CALL_UEVENT); - if (ACPI_FAILURE(acpi_evaluate_object(ds->handle, "_EJ0", - &arg_list, NULL))) - pr_debug("Failed to evaluate _EJ0!\n"); + if (num != DOCK_EVENT) + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); } /** @@ -371,25 +338,19 @@ static void handle_dock(struct dock_station *ds, int dock) acpi_status status; struct acpi_object_list arg_list; union acpi_object arg; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer name_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; + unsigned long long value; - acpi_get_name(ds->handle, ACPI_FULL_PATHNAME, &name_buffer); - obj = name_buffer.pointer; - - printk(KERN_INFO PREFIX "%s\n", dock ? "docking" : "undocking"); + acpi_handle_info(ds->handle, "%s\n", dock ? "docking" : "undocking"); /* _DCK method has one argument */ arg_list.count = 1; arg_list.pointer = &arg; arg.type = ACPI_TYPE_INTEGER; arg.integer.value = dock; - status = acpi_evaluate_object(ds->handle, "_DCK", &arg_list, &buffer); - if (ACPI_FAILURE(status)) - pr_debug("%s: failed to execute _DCK\n", obj->string.pointer); - kfree(buffer.pointer); - kfree(name_buffer.pointer); + status = acpi_evaluate_integer(ds->handle, "_DCK", &arg_list, &value); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) + acpi_handle_err(ds->handle, "Failed to execute _DCK (0x%x)\n", + status); } static inline void dock(struct dock_station *ds) @@ -413,6 +374,16 @@ static inline void complete_dock(struct dock_station *ds) ds->last_dock_time = jiffies; } +static inline void begin_undock(struct dock_station *ds) +{ + ds->flags |= DOCK_UNDOCKING; +} + +static inline void complete_undock(struct dock_station *ds) +{ + ds->flags &= ~(DOCK_UNDOCKING); +} + /** * dock_in_progress - see if we are in the middle of handling a dock event * @ds: the dock station @@ -430,310 +401,266 @@ static int dock_in_progress(struct dock_station *ds) } /** - * register_dock_notifier - add yourself to the dock notifier list - * @nb: the callers notifier block + * handle_eject_request - handle an undock request checking for error conditions * - * If a driver wishes to be notified about dock events, they can - * use this function to put a notifier block on the dock notifier list. - * this notifier call chain will be called after a dock event, but - * before hotplugging any new devices. - */ -int register_dock_notifier(struct notifier_block *nb) -{ - return atomic_notifier_chain_register(&dock_notifier_list, nb); -} - -EXPORT_SYMBOL_GPL(register_dock_notifier); - -/** - * unregister_dock_notifier - remove yourself from the dock notifier list - * @nb: the callers notifier block + * Check to make sure the dock device is still present, then undock and + * hotremove all the devices that may need removing. */ -void unregister_dock_notifier(struct notifier_block *nb) +static int handle_eject_request(struct dock_station *ds, u32 event) { - atomic_notifier_chain_unregister(&dock_notifier_list, nb); -} - -EXPORT_SYMBOL_GPL(unregister_dock_notifier); - -/** - * register_hotplug_dock_device - register a hotplug function - * @handle: the handle of the device - * @handler: the acpi_notifier_handler to call after docking - * @context: device specific data - * - * If a driver would like to perform a hotplug operation after a dock - * event, they can register an acpi_notifiy_handler to be called by - * the dock driver after _DCK is executed. - */ -int -register_hotplug_dock_device(acpi_handle handle, acpi_notify_handler handler, - void *context) -{ - struct dock_dependent_device *dd; - - if (!dock_station) - return -ENODEV; + if (dock_in_progress(ds)) + return -EBUSY; /* - * make sure this handle is for a device dependent on the dock, - * this would include the dock station itself + * here we need to generate the undock + * event prior to actually doing the undock + * so that the device struct still exists. + * Also, even send the dock event if the + * device is not present anymore */ - dd = find_dock_dependent_device(dock_station, handle); - if (dd) { - dd->handler = handler; - dd->context = context; - dock_add_hotplug_device(dock_station, dd); - return 0; + dock_event(ds, event, UNDOCK_EVENT); + + hot_remove_dock_devices(ds); + undock(ds); + acpi_evaluate_lck(ds->handle, 0); + acpi_evaluate_ej0(ds->handle); + if (dock_present(ds)) { + acpi_handle_err(ds->handle, "Unable to undock!\n"); + return -EBUSY; } - - return -EINVAL; -} - -EXPORT_SYMBOL_GPL(register_hotplug_dock_device); - -/** - * unregister_hotplug_dock_device - remove yourself from the hotplug list - * @handle: the acpi handle of the device - */ -void unregister_hotplug_dock_device(acpi_handle handle) -{ - struct dock_dependent_device *dd; - - if (!dock_station) - return; - - dd = find_dock_dependent_device(dock_station, handle); - if (dd) - dock_del_hotplug_device(dock_station, dd); + complete_undock(ds); + return 0; } -EXPORT_SYMBOL_GPL(unregister_hotplug_dock_device); - /** - * dock_notify - act upon an acpi dock notification - * @handle: the dock station handle - * @event: the acpi event - * @data: our driver data struct + * dock_notify - Handle ACPI dock notification. + * @adev: Dock station's ACPI device object. + * @event: Event code. * * If we are notified to dock, then check to see if the dock is * present and then dock. Notify all drivers of the dock event, - * and then hotplug and devices that may need hotplugging. For undock - * check to make sure the dock device is still present, then undock - * and hotremove all the devices that may need removing. + * and then hotplug and devices that may need hotplugging. */ -static void dock_notify(acpi_handle handle, u32 event, void *data) +int dock_notify(struct acpi_device *adev, u32 event) { - struct dock_station *ds = (struct dock_station *)data; + acpi_handle handle = adev->handle; + struct dock_station *ds = find_dock_station(handle); + int surprise_removal = 0; + + if (!ds) + return -ENODEV; + /* + * According to acpi spec 3.0a, if a DEVICE_CHECK notification + * is sent and _DCK is present, it is assumed to mean an undock + * request. + */ + if ((ds->flags & DOCK_IS_DOCK) && event == ACPI_NOTIFY_DEVICE_CHECK) + event = ACPI_NOTIFY_EJECT_REQUEST; + + /* + * dock station: BUS_CHECK - docked or surprise removal + * DEVICE_CHECK - undocked + * other device: BUS_CHECK/DEVICE_CHECK - added or surprise removal + * + * To simplify event handling, dock dependent device handler always + * get ACPI_NOTIFY_BUS_CHECK/ACPI_NOTIFY_DEVICE_CHECK for add and + * ACPI_NOTIFY_EJECT_REQUEST for removal + */ switch (event) { case ACPI_NOTIFY_BUS_CHECK: - if (!dock_in_progress(ds) && dock_present(ds)) { + case ACPI_NOTIFY_DEVICE_CHECK: + if (!dock_in_progress(ds) && !acpi_device_enumerated(adev)) { begin_dock(ds); dock(ds); if (!dock_present(ds)) { - printk(KERN_ERR PREFIX "Unable to dock!\n"); + acpi_handle_err(handle, "Unable to dock!\n"); + complete_dock(ds); break; } - atomic_notifier_call_chain(&dock_notifier_list, - event, NULL); hotplug_dock_devices(ds, event); complete_dock(ds); dock_event(ds, event, DOCK_EVENT); + acpi_evaluate_lck(ds->handle, 1); + acpi_update_all_gpes(); + break; } - break; - case ACPI_NOTIFY_DEVICE_CHECK: - /* - * According to acpi spec 3.0a, if a DEVICE_CHECK notification - * is sent and _DCK is present, it is assumed to mean an - * undock request. This notify routine will only be called - * for objects defining _DCK, so we will fall through to eject - * request here. However, we will pass an eject request through - * to the driver who wish to hotplug. - */ + if (dock_present(ds) || dock_in_progress(ds)) + break; + /* This is a surprise removal */ + surprise_removal = 1; + event = ACPI_NOTIFY_EJECT_REQUEST; + /* Fall back */ case ACPI_NOTIFY_EJECT_REQUEST: - if (!dock_in_progress(ds) && dock_present(ds)) { - /* - * here we need to generate the undock - * event prior to actually doing the undock - * so that the device struct still exists. - */ + begin_undock(ds); + if ((immediate_undock && !(ds->flags & DOCK_IS_ATA)) + || surprise_removal) + handle_eject_request(ds, event); + else dock_event(ds, event, UNDOCK_EVENT); - hotplug_dock_devices(ds, ACPI_NOTIFY_EJECT_REQUEST); - undock(ds); - eject_dock(ds); - if (dock_present(ds)) - printk(KERN_ERR PREFIX "Unable to undock!\n"); - } break; - default: - printk(KERN_ERR PREFIX "Unknown dock event %d\n", event); } + return 0; } -/** - * find_dock_devices - find devices on the dock station - * @handle: the handle of the device we are examining - * @lvl: unused - * @context: the dock station private data - * @rv: unused - * - * This function is called by acpi_walk_namespace. It will - * check to see if an object has an _EJD method. If it does, then it - * will see if it is dependent on the dock station. +/* + * show_docked - read method for "docked" file in sysfs */ -static acpi_status -find_dock_devices(acpi_handle handle, u32 lvl, void *context, void **rv) +static ssize_t show_docked(struct device *dev, + struct device_attribute *attr, char *buf) { - acpi_status status; - acpi_handle tmp; - struct dock_station *ds = (struct dock_station *)context; - struct dock_dependent_device *dd; + struct dock_station *dock_station = dev->platform_data; + struct acpi_device *adev = NULL; - status = acpi_bus_get_ejd(handle, &tmp); - if (ACPI_FAILURE(status)) - return AE_OK; + acpi_bus_get_device(dock_station->handle, &adev); + return snprintf(buf, PAGE_SIZE, "%u\n", acpi_device_enumerated(adev)); +} +static DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL); - if (tmp == ds->handle) { - dd = alloc_dock_dependent_device(handle); - if (dd) - add_dock_dependent_device(ds, dd); - } +/* + * show_flags - read method for flags file in sysfs + */ +static ssize_t show_flags(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dock_station *dock_station = dev->platform_data; + return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags); - return AE_OK; } +static DEVICE_ATTR(flags, S_IRUGO, show_flags, NULL); -/** - * dock_add - add a new dock station - * @handle: the dock station handle - * - * allocated and initialize a new dock station device. Find all devices - * that are on the dock station, and register for dock event notifications. +/* + * write_undock - write method for "undock" file in sysfs */ -static int dock_add(acpi_handle handle) +static ssize_t write_undock(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { int ret; - acpi_status status; - struct dock_dependent_device *dd; + struct dock_station *dock_station = dev->platform_data; - /* allocate & initialize the dock_station private data */ - dock_station = kzalloc(sizeof(*dock_station), GFP_KERNEL); - if (!dock_station) - return -ENOMEM; - dock_station->handle = handle; - dock_station->last_dock_time = jiffies - HZ; - INIT_LIST_HEAD(&dock_station->dependent_devices); - INIT_LIST_HEAD(&dock_station->hotplug_devices); - spin_lock_init(&dock_station->dd_lock); - spin_lock_init(&dock_station->hp_lock); - ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list); - - /* Find dependent devices */ - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, find_dock_devices, dock_station, - NULL); + if (!count) + return -EINVAL; - /* add the dock station as a device dependent on itself */ - dd = alloc_dock_dependent_device(handle); - if (!dd) { - kfree(dock_station); - return -ENOMEM; - } - add_dock_dependent_device(dock_station, dd); - - /* register for dock events */ - status = acpi_install_notify_handler(dock_station->handle, - ACPI_SYSTEM_NOTIFY, - dock_notify, dock_station); - - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "Error installing notify handler\n"); - ret = -ENODEV; - goto dock_add_err; - } - - printk(KERN_INFO PREFIX "%s \n", ACPI_DOCK_DRIVER_NAME); - - return 0; - -dock_add_err: - kfree(dock_station); - kfree(dd); - return ret; + acpi_scan_lock_acquire(); + begin_undock(dock_station); + ret = handle_eject_request(dock_station, ACPI_NOTIFY_EJECT_REQUEST); + acpi_scan_lock_release(); + return ret ? ret: count; } +static DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); -/** - * dock_remove - free up resources related to the dock station +/* + * show_dock_uid - read method for "uid" file in sysfs */ -static int dock_remove(void) +static ssize_t show_dock_uid(struct device *dev, + struct device_attribute *attr, char *buf) { - struct dock_dependent_device *dd, *tmp; - acpi_status status; - - if (!dock_station) - return 0; - - /* remove dependent devices */ - list_for_each_entry_safe(dd, tmp, &dock_station->dependent_devices, - list) - kfree(dd); - - /* remove dock notify handler */ - status = acpi_remove_notify_handler(dock_station->handle, - ACPI_SYSTEM_NOTIFY, - dock_notify); + unsigned long long lbuf; + struct dock_station *dock_station = dev->platform_data; + acpi_status status = acpi_evaluate_integer(dock_station->handle, + "_UID", NULL, &lbuf); if (ACPI_FAILURE(status)) - printk(KERN_ERR "Error removing notify handler\n"); + return 0; - /* free dock station memory */ - kfree(dock_station); - return 0; + return snprintf(buf, PAGE_SIZE, "%llx\n", lbuf); } +static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); + +static ssize_t show_dock_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dock_station *dock_station = dev->platform_data; + char *type; + + if (dock_station->flags & DOCK_IS_DOCK) + type = "dock_station"; + else if (dock_station->flags & DOCK_IS_ATA) + type = "ata_bay"; + else if (dock_station->flags & DOCK_IS_BAT) + type = "battery_bay"; + else + type = "unknown"; + + return snprintf(buf, PAGE_SIZE, "%s\n", type); +} +static DEVICE_ATTR(type, S_IRUGO, show_dock_type, NULL); + +static struct attribute *dock_attributes[] = { + &dev_attr_docked.attr, + &dev_attr_flags.attr, + &dev_attr_undock.attr, + &dev_attr_uid.attr, + &dev_attr_type.attr, + NULL +}; + +static struct attribute_group dock_attribute_group = { + .attrs = dock_attributes +}; /** - * find_dock - look for a dock station - * @handle: acpi handle of a device - * @lvl: unused - * @context: counter of dock stations found - * @rv: unused + * acpi_dock_add - Add a new dock station + * @adev: Dock station ACPI device object. * - * This is called by acpi_walk_namespace to look for dock stations. + * allocated and initialize a new dock station device. */ -static acpi_status -find_dock(acpi_handle handle, u32 lvl, void *context, void **rv) +void acpi_dock_add(struct acpi_device *adev) { - int *count = (int *)context; - acpi_status status = AE_OK; + struct dock_station *dock_station, ds = { NULL, }; + struct platform_device_info pdevinfo; + acpi_handle handle = adev->handle; + struct platform_device *dd; + int ret; - if (is_dock(handle)) { - if (dock_add(handle) >= 0) { - (*count)++; - status = AE_CTRL_TERMINATE; - } - } - return status; -} + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.name = "dock"; + pdevinfo.id = dock_station_count; + pdevinfo.acpi_node.companion = adev; + pdevinfo.data = &ds; + pdevinfo.size_data = sizeof(ds); + dd = platform_device_register_full(&pdevinfo); + if (IS_ERR(dd)) + return; -static int __init dock_init(void) -{ - int num = 0; + dock_station = dd->dev.platform_data; - dock_station = NULL; + dock_station->handle = handle; + dock_station->dock_device = dd; + dock_station->last_dock_time = jiffies - HZ; - /* look for a dock station */ - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, find_dock, &num, NULL); + INIT_LIST_HEAD(&dock_station->sibling); + INIT_LIST_HEAD(&dock_station->dependent_devices); - if (!num) - return -ENODEV; + /* we want the dock device to send uevents */ + dev_set_uevent_suppress(&dd->dev, 0); - return 0; -} + if (acpi_dock_match(handle)) + dock_station->flags |= DOCK_IS_DOCK; + if (acpi_ata_match(handle)) + dock_station->flags |= DOCK_IS_ATA; + if (acpi_device_is_battery(adev)) + dock_station->flags |= DOCK_IS_BAT; -static void __exit dock_exit(void) -{ - dock_remove(); -} + ret = sysfs_create_group(&dd->dev.kobj, &dock_attribute_group); + if (ret) + goto err_unregister; -postcore_initcall(dock_init); -module_exit(dock_exit); + /* add the dock station as a device dependent on itself */ + ret = add_dock_dependent_device(dock_station, adev); + if (ret) + goto err_rmgroup; + + dock_station_count++; + list_add(&dock_station->sibling, &dock_stations); + adev->flags.is_dock_station = true; + dev_info(&adev->dev, "ACPI dock station (docks/bays count: %d)\n", + dock_station_count); + return; + +err_rmgroup: + sysfs_remove_group(&dd->dev.kobj, &dock_attribute_group); + +err_unregister: + platform_device_unregister(dd); + acpi_handle_err(handle, "%s encountered error %d\n", __func__, ret); +} |
