diff options
Diffstat (limited to 'drivers/base/bus.c')
| -rw-r--r-- | drivers/base/bus.c | 291 | 
1 files changed, 273 insertions, 18 deletions
diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 000e7b2006f..99dc5921e1d 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -16,9 +16,14 @@  #include <linux/slab.h>  #include <linux/init.h>  #include <linux/string.h> +#include <linux/mutex.h>  #include "base.h"  #include "power/power.h" +/* /sys/devices/system */ +/* FIXME: make static after drivers/base/sys.c is deleted */ +struct kset *system_kset; +  #define to_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr)  /* @@ -360,6 +365,47 @@ struct device *bus_find_device_by_name(struct bus_type *bus,  }  EXPORT_SYMBOL_GPL(bus_find_device_by_name); +/** + * subsys_find_device_by_id - find a device with a specific enumeration number + * @subsys: subsystem + * @id: index 'id' in struct device + * @hint: device to check first + * + * Check the hint's next object and if it is a match return it directly, + * otherwise, fall back to a full list search. Either way a reference for + * the returned object is taken. + */ +struct device *subsys_find_device_by_id(struct bus_type *subsys, unsigned int id, +					struct device *hint) +{ +	struct klist_iter i; +	struct device *dev; + +	if (!subsys) +		return NULL; + +	if (hint) { +		klist_iter_init_node(&subsys->p->klist_devices, &i, &hint->p->knode_bus); +		dev = next_device(&i); +		if (dev && dev->id == id && get_device(dev)) { +			klist_iter_exit(&i); +			return dev; +		} +		klist_iter_exit(&i); +	} + +	klist_iter_init_node(&subsys->p->klist_devices, &i, NULL); +	while ((dev = next_device(&i))) { +		if (dev->id == id && get_device(dev)) { +			klist_iter_exit(&i); +			return dev; +		} +	} +	klist_iter_exit(&i); +	return NULL; +} +EXPORT_SYMBOL_GPL(subsys_find_device_by_id); +  static struct device_driver *next_driver(struct klist_iter *i)  {  	struct klist_node *n = klist_next(i); @@ -487,38 +533,59 @@ out_put:  void bus_probe_device(struct device *dev)  {  	struct bus_type *bus = dev->bus; +	struct subsys_interface *sif;  	int ret; -	if (bus && bus->p->drivers_autoprobe) { +	if (!bus) +		return; + +	if (bus->p->drivers_autoprobe) {  		ret = device_attach(dev);  		WARN_ON(ret < 0);  	} + +	mutex_lock(&bus->p->mutex); +	list_for_each_entry(sif, &bus->p->interfaces, node) +		if (sif->add_dev) +			sif->add_dev(dev, sif); +	mutex_unlock(&bus->p->mutex);  }  /**   * bus_remove_device - remove device from bus   * @dev: device to be removed   * - * - Remove symlink from bus's directory. + * - Remove device from all interfaces. + * - Remove symlink from bus' directory.   * - Delete device from bus's list.   * - Detach from its driver.   * - Drop reference taken in bus_add_device().   */  void bus_remove_device(struct device *dev)  { -	if (dev->bus) { -		sysfs_remove_link(&dev->kobj, "subsystem"); -		sysfs_remove_link(&dev->bus->p->devices_kset->kobj, -				  dev_name(dev)); -		device_remove_attrs(dev->bus, dev); -		if (klist_node_attached(&dev->p->knode_bus)) -			klist_del(&dev->p->knode_bus); +	struct bus_type *bus = dev->bus; +	struct subsys_interface *sif; -		pr_debug("bus: '%s': remove device %s\n", -			 dev->bus->name, dev_name(dev)); -		device_release_driver(dev); -		bus_put(dev->bus); -	} +	if (!bus) +		return; + +	mutex_lock(&bus->p->mutex); +	list_for_each_entry(sif, &bus->p->interfaces, node) +		if (sif->remove_dev) +			sif->remove_dev(dev, sif); +	mutex_unlock(&bus->p->mutex); + +	sysfs_remove_link(&dev->kobj, "subsystem"); +	sysfs_remove_link(&dev->bus->p->devices_kset->kobj, +			  dev_name(dev)); +	device_remove_attrs(dev->bus, dev); +	if (klist_node_attached(&dev->p->knode_bus)) +		klist_del(&dev->p->knode_bus); + +	pr_debug("bus: '%s': remove device %s\n", +		 dev->bus->name, dev_name(dev)); +	device_release_driver(dev); +	bus_put(dev->bus);  }  static int driver_add_attrs(struct bus_type *bus, struct device_driver *drv) @@ -847,14 +914,14 @@ static ssize_t bus_uevent_store(struct bus_type *bus,  static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);  /** - * bus_register - register a bus with the system. + * __bus_register - register a driver-core subsystem   * @bus: bus.   *   * Once we have that, we registered the bus with the kobject   * infrastructure, then register the children subsystems it has: - * the devices and drivers that belong to the bus. + * the devices and drivers that belong to the subsystem.   */ -int bus_register(struct bus_type *bus) +int __bus_register(struct bus_type *bus, struct lock_class_key *key)  {  	int retval;  	struct subsys_private *priv; @@ -898,6 +965,8 @@ int bus_register(struct bus_type *bus)  		goto bus_drivers_fail;  	} +	INIT_LIST_HEAD(&priv->interfaces); +	__mutex_init(&priv->mutex, "subsys mutex", key);  	klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);  	klist_init(&priv->klist_drivers, NULL, NULL); @@ -927,7 +996,7 @@ out:  	bus->p = NULL;  	return retval;  } -EXPORT_SYMBOL_GPL(bus_register); +EXPORT_SYMBOL_GPL(__bus_register);  /**   * bus_unregister - remove a bus from the system @@ -939,6 +1008,8 @@ EXPORT_SYMBOL_GPL(bus_register);  void bus_unregister(struct bus_type *bus)  {  	pr_debug("bus: '%s': unregistering\n", bus->name); +	if (bus->dev_root) +		device_unregister(bus->dev_root);  	bus_remove_attrs(bus);  	remove_probe_files(bus);  	kset_unregister(bus->p->drivers_kset); @@ -1028,10 +1099,194 @@ void bus_sort_breadthfirst(struct bus_type *bus,  }  EXPORT_SYMBOL_GPL(bus_sort_breadthfirst); +/** + * subsys_dev_iter_init - initialize subsys device iterator + * @iter: subsys iterator to initialize + * @subsys: the subsys we wanna iterate over + * @start: the device to start iterating from, if any + * @type: device_type of the devices to iterate over, NULL for all + * + * Initialize subsys iterator @iter such that it iterates over devices + * of @subsys.  If @start is set, the list iteration will start there, + * otherwise if it is NULL, the iteration starts at the beginning of + * the list. + */ +void subsys_dev_iter_init(struct subsys_dev_iter *iter, struct bus_type *subsys, +			  struct device *start, const struct device_type *type) +{ +	struct klist_node *start_knode = NULL; + +	if (start) +		start_knode = &start->p->knode_bus; +	klist_iter_init_node(&subsys->p->klist_devices, &iter->ki, start_knode); +	iter->type = type; +} +EXPORT_SYMBOL_GPL(subsys_dev_iter_init); + +/** + * subsys_dev_iter_next - iterate to the next device + * @iter: subsys iterator to proceed + * + * Proceed @iter to the next device and return it.  Returns NULL if + * iteration is complete. + * + * The returned device is referenced and won't be released till + * iterator is proceed to the next device or exited.  The caller is + * free to do whatever it wants to do with the device including + * calling back into subsys code. + */ +struct device *subsys_dev_iter_next(struct subsys_dev_iter *iter) +{ +	struct klist_node *knode; +	struct device *dev; + +	for (;;) { +		knode = klist_next(&iter->ki); +		if (!knode) +			return NULL; +		dev = container_of(knode, struct device_private, knode_bus)->device; +		if (!iter->type || iter->type == dev->type) +			return dev; +	} +} +EXPORT_SYMBOL_GPL(subsys_dev_iter_next); + +/** + * subsys_dev_iter_exit - finish iteration + * @iter: subsys iterator to finish + * + * Finish an iteration.  Always call this function after iteration is + * complete whether the iteration ran till the end or not. + */ +void subsys_dev_iter_exit(struct subsys_dev_iter *iter) +{ +	klist_iter_exit(&iter->ki); +} +EXPORT_SYMBOL_GPL(subsys_dev_iter_exit); + +int subsys_interface_register(struct subsys_interface *sif) +{ +	struct bus_type *subsys; +	struct subsys_dev_iter iter; +	struct device *dev; + +	if (!sif || !sif->subsys) +		return -ENODEV; + +	subsys = bus_get(sif->subsys); +	if (!subsys) +		return -EINVAL; + +	mutex_lock(&subsys->p->mutex); +	list_add_tail(&sif->node, &subsys->p->interfaces); +	if (sif->add_dev) { +		subsys_dev_iter_init(&iter, subsys, NULL, NULL); +		while ((dev = subsys_dev_iter_next(&iter))) +			sif->add_dev(dev, sif); +		subsys_dev_iter_exit(&iter); +	} +	mutex_unlock(&subsys->p->mutex); + +	return 0; +} +EXPORT_SYMBOL_GPL(subsys_interface_register); + +void subsys_interface_unregister(struct subsys_interface *sif) +{ +	struct bus_type *subsys = sif->subsys; +	struct subsys_dev_iter iter; +	struct device *dev; + +	if (!sif) +		return; + +	mutex_lock(&subsys->p->mutex); +	list_del_init(&sif->node); +	if (sif->remove_dev) { +		subsys_dev_iter_init(&iter, subsys, NULL, NULL); +		while ((dev = subsys_dev_iter_next(&iter))) +			sif->remove_dev(dev, sif); +		subsys_dev_iter_exit(&iter); +	} +	mutex_unlock(&subsys->p->mutex); + +	bus_put(subsys); +} +EXPORT_SYMBOL_GPL(subsys_interface_unregister); + +static void system_root_device_release(struct device *dev) +{ +	kfree(dev); +} +/** + * subsys_system_register - register a subsystem at /sys/devices/system/ + * @subsys - system subsystem + * @groups - default attributes for the root device + * + * All 'system' subsystems have a /sys/devices/system/<name> root device + * with the name of the subsystem. The root device can carry subsystem- + * wide attributes. All registered devices are below this single root + * device and are named after the subsystem with a simple enumeration + * number appended. The registered devices are not explicitely named; + * only 'id' in the device needs to be set. + * + * Do not use this interface for anything new, it exists for compatibility + * with bad ideas only. New subsystems should use plain subsystems; and + * add the subsystem-wide attributes should be added to the subsystem + * directory itself and not some create fake root-device placed in + * /sys/devices/system/<name>. + */ +int subsys_system_register(struct bus_type *subsys, +			   const struct attribute_group **groups) +{ +	struct device *dev; +	int err; + +	err = bus_register(subsys); +	if (err < 0) +		return err; + +	dev = kzalloc(sizeof(struct device), GFP_KERNEL); +	if (!dev) { +		err = -ENOMEM; +		goto err_dev; +	} + +	err = dev_set_name(dev, "%s", subsys->name); +	if (err < 0) +		goto err_name; + +	dev->kobj.parent = &system_kset->kobj; +	dev->groups = groups; +	dev->release = system_root_device_release; + +	err = device_register(dev); +	if (err < 0) +		goto err_dev_reg; + +	subsys->dev_root = dev; +	return 0; + +err_dev_reg: +	put_device(dev); +	dev = NULL; +err_name: +	kfree(dev); +err_dev: +	bus_unregister(subsys); +	return err; +} +EXPORT_SYMBOL_GPL(subsys_system_register); +  int __init buses_init(void)  {  	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);  	if (!bus_kset)  		return -ENOMEM; + +	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj); +	if (!system_kset) +		return -ENOMEM; +  	return 0;  }  |