/* * drivers/base/core.c - core driver model code (device registration, etc) * * Copyright (c) 2002 Patrick Mochel * 2002 Open Source Development Lab */ #define DEBUG 0 #include <linux/device.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/err.h> #include <linux/init.h> #include "base.h" LIST_HEAD(global_device_list); int (*platform_notify)(struct device * dev) = NULL; int (*platform_notify_remove)(struct device * dev) = NULL; spinlock_t device_lock = SPIN_LOCK_UNLOCKED; #define to_dev(node) container_of(node,struct device,driver_list) static int probe(struct device * dev, struct device_driver * drv) { dev->driver = drv; return drv->probe ? drv->probe(dev) : 0; } static void attach(struct device * dev) { spin_lock(&device_lock); list_add_tail(&dev->driver_list,&dev->driver->devices); spin_unlock(&device_lock); devclass_add_device(dev); } /** * found_match - do actual binding of device to driver * @dev: device * @drv: driver * * We're here because the bus's match callback returned success for this * pair. We call the driver's probe callback to verify they're really a * match made in heaven. * * In the future, we may want to notify userspace of the binding. (But, * we might not want to do it here). * * We may also want to create a symlink in the driver's directory to the * device's physical directory. */ static int found_match(struct device * dev, struct device_driver * drv) { int error = 0; if (!(error = probe(dev,get_driver(drv)))) { pr_debug("bound device '%s' to driver '%s'\n", dev->bus_id,drv->name); attach(dev); } else { put_driver(drv); dev->driver = NULL; } return error; } /** * device_attach - try to associated device with a driver * @drv: current driver to try * @data: device in disguise * * This function is used as a callback to bus_for_each_drv. * It calls the bus's match callback to check if the driver supports * the device. If so, it calls the found_match() function above to * take care of all the details. */ static int do_device_attach(struct device_driver * drv, void * data) { struct device * dev = (struct device *)data; int error = 0; if (drv->bus->match && drv->bus->match(dev,drv)) error = found_match(dev,drv); return error; } static int device_attach(struct device * dev) { int error = 0; if (!dev->driver) { if (dev->bus) error = bus_for_each_drv(dev->bus,dev,do_device_attach); } else attach(dev); return error; } static void device_detach(struct device * dev) { struct device_driver * drv = dev->driver; if (drv) { devclass_remove_device(dev); if (drv && drv->remove) drv->remove(dev); dev->driver = NULL; } } static int do_driver_attach(struct device * dev, void * data) { struct device_driver * drv = (struct device_driver *)data; int error = 0; if (!dev->driver) { if (dev->bus->match && dev->bus->match(dev,drv)) error = found_match(dev,drv); } return error; } int driver_attach(struct device_driver * drv) { return bus_for_each_dev(drv->bus,drv,do_driver_attach); } void driver_detach(struct device_driver * drv) { struct list_head * node; struct device * prev = NULL; spin_lock(&device_lock); list_for_each(node,&drv->devices) { struct device * dev = get_device_locked(to_dev(node)); if (dev) { if (prev) list_del_init(&prev->driver_list); spin_unlock(&device_lock); device_detach(dev); if (prev) put_device(prev); prev = dev; spin_lock(&device_lock); } } spin_unlock(&device_lock); } /** * device_register - register a device * @dev: pointer to the device structure * * First, make sure that the device has a parent, create * a directory for it, then add it to the parent's list of * children. * * Maintains a global list of all devices, in depth-first ordering. * The head for that list is device_root.g_list. */ int device_register(struct device *dev) { int error; if (!dev || !strlen(dev->bus_id)) return -EINVAL; INIT_LIST_HEAD(&dev->node); INIT_LIST_HEAD(&dev->children); INIT_LIST_HEAD(&dev->g_list); INIT_LIST_HEAD(&dev->driver_list); INIT_LIST_HEAD(&dev->bus_list); INIT_LIST_HEAD(&dev->intf_list); spin_lock_init(&dev->lock); atomic_set(&dev->refcount,2); spin_lock(&device_lock); if (dev->parent) { get_device_locked(dev->parent); list_add_tail(&dev->g_list,&dev->parent->g_list); list_add_tail(&dev->node,&dev->parent->children); } else list_add_tail(&dev->g_list,&global_device_list); spin_unlock(&device_lock); pr_debug("DEV: registering device: ID = '%s', name = %s\n", dev->bus_id, dev->name); if ((error = device_make_dir(dev))) goto register_done; bus_add_device(dev); /* bind to driver */ device_attach(dev); /* notify platform of device entry */ if (platform_notify) platform_notify(dev); /* notify userspace of device entry */ dev_hotplug(dev, "add"); register_done: if (error) { spin_lock(&device_lock); list_del_init(&dev->g_list); list_del_init(&dev->node); spin_unlock(&device_lock); if (dev->parent) put_device(dev->parent); } put_device(dev); return error; } struct device * get_device_locked(struct device * dev) { struct device * ret = dev; if (dev && atomic_read(&dev->refcount) > 0) atomic_inc(&dev->refcount); else ret = NULL; return ret; } struct device * get_device(struct device * dev) { struct device * ret; spin_lock(&device_lock); ret = get_device_locked(dev); spin_unlock(&device_lock); return ret; } /** * put_device - decrement reference count, and clean up when it hits 0 * @dev: device in question */ void put_device(struct device * dev) { if (!atomic_dec_and_lock(&dev->refcount,&device_lock)) return; list_del_init(&dev->node); list_del_init(&dev->g_list); list_del_init(&dev->bus_list); list_del_init(&dev->driver_list); spin_unlock(&device_lock); pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", dev->bus_id,dev->name); /* Notify the platform of the removal, in case they * need to do anything... */ if (platform_notify_remove) platform_notify_remove(dev); /* notify userspace that this device is about to disappear */ dev_hotplug (dev, "remove"); device_detach(dev); bus_remove_device(dev); /* remove the driverfs directory */ device_remove_dir(dev); if (dev->release) dev->release(dev); if (dev->parent) put_device(dev->parent); } static int __init device_init(void) { int error; error = init_driverfs_fs(); if (error) panic("DEV: could not initialize driverfs"); return 0; } core_initcall(device_init); EXPORT_SYMBOL(device_register); EXPORT_SYMBOL(get_device); EXPORT_SYMBOL(put_device);