#include <linux/init.h>
#include <linux/types.h>
#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/stddef.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
-#define ACPI_DOCK_DRIVER_NAME "ACPI Dock Station Driver"
+#define ACPI_DOCK_DRIVER_DESCRIPTION "ACPI Dock Station Driver"
-ACPI_MODULE_NAME("dock")
+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 int 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");
+
static struct atomic_notifier_head dock_notifier_list;
+static struct platform_device *dock_device;
+static char dock_device_name[] = "dock";
+
+static const struct acpi_device_id dock_device_ids[] = {
+ {"LNXDOCK", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, dock_device_ids);
struct dock_station {
acpi_handle handle;
unsigned long last_dock_time;
u32 flags;
spinlock_t dd_lock;
- spinlock_t hp_lock;
+ struct mutex hp_lock;
struct list_head dependent_devices;
struct list_head hotplug_devices;
};
};
#define DOCK_DOCKING 0x00000001
-#define DOCK_EVENT KOBJ_DOCK
-#define UNDOCK_EVENT KOBJ_UNDOCK
+#define DOCK_UNDOCKING 0x00000002
+#define DOCK_EVENT 3
+#define UNDOCK_EVENT 2
static struct dock_station *dock_station;
dock_add_hotplug_device(struct dock_station *ds,
struct dock_dependent_device *dd)
{
- spin_lock(&ds->hp_lock);
+ mutex_lock(&ds->hp_lock);
list_add_tail(&dd->hotplug_list, &ds->hotplug_devices);
- spin_unlock(&ds->hp_lock);
+ mutex_unlock(&ds->hp_lock);
}
/**
dock_del_hotplug_device(struct dock_station *ds,
struct dock_dependent_device *dd)
{
- spin_lock(&ds->hp_lock);
+ mutex_lock(&ds->hp_lock);
list_del(&dd->hotplug_list);
- spin_unlock(&ds->hp_lock);
+ mutex_unlock(&ds->hp_lock);
}
/**
{
struct dock_dependent_device *dd;
- spin_lock(&ds->hp_lock);
+ mutex_lock(&ds->hp_lock);
/*
* First call driver specific hotplug functions
else
dock_create_acpi_device(dd->handle);
}
- spin_unlock(&ds->hp_lock);
+ mutex_unlock(&ds->hp_lock);
}
static void dock_event(struct dock_station *ds, u32 event, int num)
{
- struct acpi_device *device;
+ struct device *dev = &dock_device->dev;
+ char event_string[13];
+ char *envp[] = { event_string, NULL };
+
+ if (num == UNDOCK_EVENT)
+ sprintf(event_string, "EVENT=undock");
+ else
+ sprintf(event_string, "EVENT=dock");
- device = dock_create_acpi_device(ds->handle);
- if (device)
- kobject_uevent(&device->kobj, num);
+ /*
+ * Indicate that the status of the dock station has
+ * changed.
+ */
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
}
/**
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;
acpi_get_name(ds->handle, ACPI_FULL_PATHNAME, &name_buffer);
- obj = name_buffer.pointer;
- printk(KERN_INFO PREFIX "%s\n", dock ? "docking" : "undocking");
+ printk(KERN_INFO PREFIX "%s - %s\n",
+ (char *)name_buffer.pointer, dock ? "docking" : "undocking");
/* _DCK method has one argument */
arg_list.count = 1;
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);
+ printk(KERN_ERR PREFIX "%s - failed to execute _DCK\n",
+ (char *)name_buffer.pointer);
kfree(buffer.pointer);
kfree(name_buffer.pointer);
}
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
*/
int register_dock_notifier(struct notifier_block *nb)
{
+ if (!dock_station)
+ return -ENODEV;
+
return atomic_notifier_chain_register(&dock_notifier_list, nb);
}
*/
void unregister_dock_notifier(struct notifier_block *nb)
{
+ if (!dock_station)
+ return;
+
atomic_notifier_chain_unregister(&dock_notifier_list, nb);
}
EXPORT_SYMBOL_GPL(unregister_hotplug_dock_device);
/**
+ * handle_eject_request - handle an undock request checking for error conditions
+ *
+ * Check to make sure the dock device is still present, then undock and
+ * hotremove all the devices that may need removing.
+ */
+static int handle_eject_request(struct dock_station *ds, u32 event)
+{
+ if (!dock_present(ds))
+ return -ENODEV;
+
+ if (dock_in_progress(ds))
+ return -EBUSY;
+
+ /*
+ * here we need to generate the undock
+ * event prior to actually doing the undock
+ * so that the device struct still exists.
+ */
+ 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");
+ return -EBUSY;
+ }
+ complete_undock(ds);
+ return 0;
+}
+
+/**
* dock_notify - act upon an acpi dock notification
* @handle: the dock station handle
* @event: the acpi event
*
* 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)
{
- struct dock_station *ds = (struct dock_station *)data;
+ struct dock_station *ds = data;
switch (event) {
case ACPI_NOTIFY_BUS_CHECK:
* to the driver who wish to hotplug.
*/
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)
+ 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);
find_dock_devices(acpi_handle handle, u32 lvl, void *context, void **rv)
{
acpi_status status;
- acpi_handle tmp;
- struct dock_station *ds = (struct dock_station *)context;
+ acpi_handle tmp, parent;
+ struct dock_station *ds = context;
struct dock_dependent_device *dd;
status = acpi_bus_get_ejd(handle, &tmp);
- if (ACPI_FAILURE(status))
- return AE_OK;
+ if (ACPI_FAILURE(status)) {
+ /* try the parent device as well */
+ status = acpi_get_parent(handle, &parent);
+ if (ACPI_FAILURE(status))
+ goto fdd_out;
+ /* see if parent is dependent on dock */
+ status = acpi_bus_get_ejd(parent, &tmp);
+ if (ACPI_FAILURE(status))
+ goto fdd_out;
+ }
if (tmp == ds->handle) {
dd = alloc_dock_dependent_device(handle);
if (dd)
add_dock_dependent_device(ds, dd);
}
-
+fdd_out:
return AE_OK;
}
+/*
+ * show_docked - read method for "docked" file in sysfs
+ */
+static ssize_t show_docked(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", dock_present(dock_station));
+
+}
+static DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL);
+
+/*
+ * show_flags - read method for flags file in sysfs
+ */
+static ssize_t show_flags(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags);
+
+}
+static DEVICE_ATTR(flags, S_IRUGO, show_flags, NULL);
+
+/*
+ * write_undock - write method for "undock" file in sysfs
+ */
+static ssize_t write_undock(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ if (!count)
+ return -EINVAL;
+
+ begin_undock(dock_station);
+ ret = handle_eject_request(dock_station, ACPI_NOTIFY_EJECT_REQUEST);
+ return ret ? ret: count;
+}
+static DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock);
+
+/*
+ * show_dock_uid - read method for "uid" file in sysfs
+ */
+static ssize_t show_dock_uid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned long lbuf;
+ acpi_status status = acpi_evaluate_integer(dock_station->handle,
+ "_UID", NULL, &lbuf);
+ if (ACPI_FAILURE(status))
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%lx\n", lbuf);
+}
+static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL);
+
/**
* dock_add - add a new dock station
* @handle: the dock station handle
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);
+ mutex_init(&dock_station->hp_lock);
+ ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);
+
+ /* initialize platform device stuff */
+ dock_device =
+ platform_device_register_simple(dock_device_name, 0, NULL, 0);
+ if (IS_ERR(dock_device)) {
+ kfree(dock_station);
+ dock_station = NULL;
+ return PTR_ERR(dock_device);
+ }
+
+ /* we want the dock device to send uevents */
+ dock_device->dev.uevent_suppress = 0;
+
+ ret = device_create_file(&dock_device->dev, &dev_attr_docked);
+ if (ret) {
+ printk("Error %d adding sysfs file\n", ret);
+ platform_device_unregister(dock_device);
+ kfree(dock_station);
+ dock_station = NULL;
+ return ret;
+ }
+ ret = device_create_file(&dock_device->dev, &dev_attr_undock);
+ if (ret) {
+ printk("Error %d adding sysfs file\n", ret);
+ device_remove_file(&dock_device->dev, &dev_attr_docked);
+ platform_device_unregister(dock_device);
+ kfree(dock_station);
+ dock_station = NULL;
+ return ret;
+ }
+ ret = device_create_file(&dock_device->dev, &dev_attr_uid);
+ if (ret) {
+ printk("Error %d adding sysfs file\n", ret);
+ device_remove_file(&dock_device->dev, &dev_attr_docked);
+ device_remove_file(&dock_device->dev, &dev_attr_undock);
+ platform_device_unregister(dock_device);
+ kfree(dock_station);
+ dock_station = NULL;
+ return ret;
+ }
+ ret = device_create_file(&dock_device->dev, &dev_attr_flags);
+ if (ret) {
+ printk("Error %d adding sysfs file\n", ret);
+ device_remove_file(&dock_device->dev, &dev_attr_docked);
+ device_remove_file(&dock_device->dev, &dev_attr_undock);
+ device_remove_file(&dock_device->dev, &dev_attr_uid);
+ platform_device_unregister(dock_device);
+ kfree(dock_station);
+ dock_station = NULL;
+ return ret;
+ }
/* Find dependent devices */
acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
dd = alloc_dock_dependent_device(handle);
if (!dd) {
kfree(dock_station);
- return -ENOMEM;
+ dock_station = NULL;
+ ret = -ENOMEM;
+ goto dock_add_err_unregister;
}
add_dock_dependent_device(dock_station, dd);
goto dock_add_err;
}
- printk(KERN_INFO PREFIX "%s \n", ACPI_DOCK_DRIVER_NAME);
+ printk(KERN_INFO PREFIX "%s \n", ACPI_DOCK_DRIVER_DESCRIPTION);
return 0;
dock_add_err:
- kfree(dock_station);
kfree(dd);
+dock_add_err_unregister:
+ device_remove_file(&dock_device->dev, &dev_attr_docked);
+ device_remove_file(&dock_device->dev, &dev_attr_undock);
+ device_remove_file(&dock_device->dev, &dev_attr_uid);
+ device_remove_file(&dock_device->dev, &dev_attr_flags);
+ platform_device_unregister(dock_device);
+ kfree(dock_station);
+ dock_station = NULL;
return ret;
}
if (ACPI_FAILURE(status))
printk(KERN_ERR "Error removing notify handler\n");
+ /* cleanup sysfs */
+ device_remove_file(&dock_device->dev, &dev_attr_docked);
+ device_remove_file(&dock_device->dev, &dev_attr_undock);
+ device_remove_file(&dock_device->dev, &dev_attr_uid);
+ device_remove_file(&dock_device->dev, &dev_attr_flags);
+ platform_device_unregister(dock_device);
+
/* free dock station memory */
kfree(dock_station);
+ dock_station = NULL;
return 0;
}
static acpi_status
find_dock(acpi_handle handle, u32 lvl, void *context, void **rv)
{
- int *count = (int *)context;
+ int *count = context;
acpi_status status = AE_OK;
if (is_dock(handle)) {
ACPI_UINT32_MAX, find_dock, &num, NULL);
if (!num)
- return -ENODEV;
+ printk(KERN_INFO "No dock devices found.\n");
return 0;
}