diff options
Diffstat (limited to 'drivers/acpi/dock.c')
| -rw-r--r-- | drivers/acpi/dock.c | 181 | 
1 files changed, 98 insertions, 83 deletions
diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 4fdea381ef21..14de9f46972e 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -66,20 +66,21 @@ struct dock_station {  	spinlock_t dd_lock;  	struct mutex 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; +static DEFINE_MUTEX(hotplug_lock);  struct dock_dependent_device {  	struct list_head list; -	struct list_head hotplug_list;  	acpi_handle handle; -	const struct acpi_dock_ops *ops; -	void *context; +	const struct acpi_dock_ops *hp_ops; +	void *hp_context; +	unsigned int hp_refcount; +	void (*hp_release)(void *);  };  #define DOCK_DOCKING	0x00000001 @@ -111,7 +112,6 @@ add_dock_dependent_device(struct dock_station *ds, acpi_handle handle)  	dd->handle = handle;  	INIT_LIST_HEAD(&dd->list); -	INIT_LIST_HEAD(&dd->hotplug_list);  	spin_lock(&ds->dd_lock);  	list_add_tail(&dd->list, &ds->dependent_devices); @@ -121,35 +121,90 @@ add_dock_dependent_device(struct dock_station *ds, acpi_handle handle)  }  /** - * 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 + * dock_init_hotplug - Initialize a hotplug device on a docking station. + * @dd: Dock-dependent device. + * @ops: Dock operations to attach to the dependent device. + * @context: Data to pass to the @ops callbacks and @release. + * @init: Optional initialization routine to run after setting up context. + * @release: Optional release routine to run on removal.   */ -static void -dock_add_hotplug_device(struct dock_station *ds, -			struct dock_dependent_device *dd) +static int dock_init_hotplug(struct dock_dependent_device *dd, +			     const struct acpi_dock_ops *ops, void *context, +			     void (*init)(void *), void (*release)(void *))  { -	mutex_lock(&ds->hp_lock); -	list_add_tail(&dd->hotplug_list, &ds->hotplug_devices); -	mutex_unlock(&ds->hp_lock); +	int ret = 0; + +	mutex_lock(&hotplug_lock); + +	if (dd->hp_context) { +		ret = -EEXIST; +	} else { +		dd->hp_refcount = 1; +		dd->hp_ops = ops; +		dd->hp_context = context; +		dd->hp_release = release; +	} + +	if (!WARN_ON(ret) && init) +		init(context); + +	mutex_unlock(&hotplug_lock); +	return ret;  }  /** - * dock_del_hotplug_device - remove a hotplug handler from the dock station - * @ds: The dock station - * @dd: the dependent device struct + * dock_release_hotplug - Decrement hotplug reference counter of dock device. + * @dd: Dock-dependent device.   * - * Delete the dependent device from the dock's hotplug device list + * Decrement the reference counter of @dd and if 0, detach its hotplug + * operations from it, reset its context pointer and run the optional release + * routine if present.   */ -static void -dock_del_hotplug_device(struct dock_station *ds, -			struct dock_dependent_device *dd) +static void dock_release_hotplug(struct dock_dependent_device *dd)  { -	mutex_lock(&ds->hp_lock); -	list_del(&dd->hotplug_list); -	mutex_unlock(&ds->hp_lock); +	void (*release)(void *) = NULL; +	void *context = NULL; + +	mutex_lock(&hotplug_lock); + +	if (dd->hp_context && !--dd->hp_refcount) { +		dd->hp_ops = NULL; +		context = dd->hp_context; +		dd->hp_context = NULL; +		release = dd->hp_release; +		dd->hp_release = NULL; +	} + +	if (release && context) +		release(context); + +	mutex_unlock(&hotplug_lock); +} + +static void dock_hotplug_event(struct dock_dependent_device *dd, u32 event, +			       bool uevent) +{ +	acpi_notify_handler cb = NULL; +	bool run = false; + +	mutex_lock(&hotplug_lock); + +	if (dd->hp_context) { +		run = true; +		dd->hp_refcount++; +		if (dd->hp_ops) +			cb = uevent ? dd->hp_ops->uevent : dd->hp_ops->handler; +	} + +	mutex_unlock(&hotplug_lock); + +	if (!run) +		return; + +	if (cb) +		cb(dd->handle, event, dd->hp_context); + +	dock_release_hotplug(dd);  }  /** @@ -360,9 +415,8 @@ static void hotplug_dock_devices(struct dock_station *ds, u32 event)  	/*  	 * First call driver specific hotplug functions  	 */ -	list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list) -		if (dd->ops && dd->ops->handler) -			dd->ops->handler(dd->handle, event, dd->context); +	list_for_each_entry(dd, &ds->dependent_devices, list) +		dock_hotplug_event(dd, event, false);  	/*  	 * Now make sure that an acpi_device is created for each @@ -398,9 +452,8 @@ static void dock_event(struct dock_station *ds, u32 event, int num)  	if (num == DOCK_EVENT)  		kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); -	list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list) -		if (dd->ops && dd->ops->uevent) -			dd->ops->uevent(dd->handle, event, dd->context); +	list_for_each_entry(dd, &ds->dependent_devices, list) +		dock_hotplug_event(dd, event, true);  	if (num != DOCK_EVENT)  		kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); @@ -570,19 +623,24 @@ EXPORT_SYMBOL_GPL(unregister_dock_notifier);   * @handle: the handle of the device   * @ops: handlers to call after docking   * @context: device specific data + * @init: Optional initialization routine to run after registration + * @release: Optional release routine to run on unregistration   *   * 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, const struct acpi_dock_ops *ops, -			     void *context) +int register_hotplug_dock_device(acpi_handle handle, +				 const struct acpi_dock_ops *ops, void *context, +				 void (*init)(void *), void (*release)(void *))  {  	struct dock_dependent_device *dd;  	struct dock_station *dock_station;  	int ret = -EINVAL; +	if (WARN_ON(!context)) +		return -EINVAL; +  	if (!dock_station_count)  		return -ENODEV; @@ -597,12 +655,8 @@ register_hotplug_dock_device(acpi_handle handle, const struct acpi_dock_ops *ops  		 * ops  		 */  		dd = find_dock_dependent_device(dock_station, handle); -		if (dd) { -			dd->ops = ops; -			dd->context = context; -			dock_add_hotplug_device(dock_station, dd); +		if (dd && !dock_init_hotplug(dd, ops, context, init, release))  			ret = 0; -		}  	}  	return ret; @@ -624,7 +678,7 @@ void unregister_hotplug_dock_device(acpi_handle handle)  	list_for_each_entry(dock_station, &dock_stations, sibling) {  		dd = find_dock_dependent_device(dock_station, handle);  		if (dd) -			dock_del_hotplug_device(dock_station, dd); +			dock_release_hotplug(dd);  	}  }  EXPORT_SYMBOL_GPL(unregister_hotplug_dock_device); @@ -868,8 +922,10 @@ static ssize_t write_undock(struct device *dev, struct device_attribute *attr,  	if (!count)  		return -EINVAL; +	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); @@ -951,7 +1007,6 @@ static int __init dock_add(acpi_handle handle)  	mutex_init(&dock_station->hp_lock);  	spin_lock_init(&dock_station->dd_lock);  	INIT_LIST_HEAD(&dock_station->sibling); -	INIT_LIST_HEAD(&dock_station->hotplug_devices);  	ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list);  	INIT_LIST_HEAD(&dock_station->dependent_devices); @@ -992,30 +1047,6 @@ err_unregister:  }  /** - * dock_remove - free up resources related to the dock station - */ -static int dock_remove(struct dock_station *ds) -{ -	struct dock_dependent_device *dd, *tmp; -	struct platform_device *dock_device = ds->dock_device; - -	if (!dock_station_count) -		return 0; - -	/* remove dependent devices */ -	list_for_each_entry_safe(dd, tmp, &ds->dependent_devices, list) -		kfree(dd); - -	list_del(&ds->sibling); - -	/* cleanup sysfs */ -	sysfs_remove_group(&dock_device->dev.kobj, &dock_attribute_group); -	platform_device_unregister(dock_device); - -	return 0; -} - -/**   * find_dock_and_bay - look for dock stations and bays   * @handle: acpi handle of a device   * @lvl: unused @@ -1033,7 +1064,7 @@ find_dock_and_bay(acpi_handle handle, u32 lvl, void *context, void **rv)  	return AE_OK;  } -static int __init dock_init(void) +int __init acpi_dock_init(void)  {  	if (acpi_disabled)  		return 0; @@ -1052,19 +1083,3 @@ static int __init dock_init(void)  		ACPI_DOCK_DRIVER_DESCRIPTION, dock_station_count);  	return 0;  } - -static void __exit dock_exit(void) -{ -	struct dock_station *tmp, *dock_station; - -	unregister_acpi_bus_notifier(&dock_acpi_notifier); -	list_for_each_entry_safe(dock_station, tmp, &dock_stations, sibling) -		dock_remove(dock_station); -} - -/* - * Must be called before drivers of devices in dock, otherwise we can't know - * which devices are in a dock - */ -subsys_initcall(dock_init); -module_exit(dock_exit);  |