diff options
Diffstat (limited to 'drivers/base')
28 files changed, 5835 insertions, 0 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig new file mode 100644 index 000000000000..934149c1512b --- /dev/null +++ b/drivers/base/Kconfig @@ -0,0 +1,40 @@ +menu "Generic Driver Options" + +config STANDALONE + bool "Select only drivers that don't need compile-time external firmware" if EXPERIMENTAL + default y + help + Select this option if you don't have magic firmware for drivers that + need it. + + If unsure, say Y. + +config PREVENT_FIRMWARE_BUILD + bool "Prevent firmware from being built" + default y + help + Say yes to avoid building firmware. Firmware is usually shipped + with the driver, and only when updating the firmware a rebuild + should be made. + If unsure say Y here. + +config FW_LOADER + tristate "Hotplug firmware loading support" + select HOTPLUG + ---help--- + This option is provided for the case where no in-kernel-tree modules + require hotplug firmware loading support, but a module built outside + the kernel tree does. + +config DEBUG_DRIVER + bool "Driver Core verbose debug messages" + depends on DEBUG_KERNEL + help + Say Y here if you want the Driver core to produce a bunch of + debug messages to the system log. Select this if you are having a + problem with the driver core and want to see more of what is + going on. + + If you are unsure about this, say N here. + +endmenu diff --git a/drivers/base/Makefile b/drivers/base/Makefile new file mode 100644 index 000000000000..6662b545e0a9 --- /dev/null +++ b/drivers/base/Makefile @@ -0,0 +1,14 @@ +# Makefile for the Linux device tree + +obj-y := core.o sys.o interface.o bus.o \ + driver.o class.o class_simple.o platform.o \ + cpu.o firmware.o init.o map.o dmapool.o \ + attribute_container.o transport_class.o +obj-y += power/ +obj-$(CONFIG_FW_LOADER) += firmware_class.o +obj-$(CONFIG_NUMA) += node.o + +ifeq ($(CONFIG_DEBUG_DRIVER),y) +EXTRA_CFLAGS += -DDEBUG +endif + diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c new file mode 100644 index 000000000000..ec615d854be9 --- /dev/null +++ b/drivers/base/attribute_container.c @@ -0,0 +1,376 @@ +/* + * attribute_container.c - implementation of a simple container for classes + * + * Copyright (c) 2005 - James Bottomley <James.Bottomley@steeleye.com> + * + * This file is licensed under GPLv2 + * + * The basic idea here is to enable a device to be attached to an + * aritrary numer of classes without having to allocate storage for them. + * Instead, the contained classes select the devices they need to attach + * to via a matching function. + */ + +#include <linux/attribute_container.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/module.h> + +/* This is a private structure used to tie the classdev and the + * container .. it should never be visible outside this file */ +struct internal_container { + struct list_head node; + struct attribute_container *cont; + struct class_device classdev; +}; + +/** + * attribute_container_classdev_to_container - given a classdev, return the container + * + * @classdev: the class device created by attribute_container_add_device. + * + * Returns the container associated with this classdev. + */ +struct attribute_container * +attribute_container_classdev_to_container(struct class_device *classdev) +{ + struct internal_container *ic = + container_of(classdev, struct internal_container, classdev); + return ic->cont; +} +EXPORT_SYMBOL_GPL(attribute_container_classdev_to_container); + +static struct list_head attribute_container_list; + +static DECLARE_MUTEX(attribute_container_mutex); + +/** + * attribute_container_register - register an attribute container + * + * @cont: The container to register. This must be allocated by the + * callee and should also be zeroed by it. + */ +int +attribute_container_register(struct attribute_container *cont) +{ + INIT_LIST_HEAD(&cont->node); + INIT_LIST_HEAD(&cont->containers); + + down(&attribute_container_mutex); + list_add_tail(&cont->node, &attribute_container_list); + up(&attribute_container_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(attribute_container_register); + +/** + * attribute_container_unregister - remove a container registration + * + * @cont: previously registered container to remove + */ +int +attribute_container_unregister(struct attribute_container *cont) +{ + int retval = -EBUSY; + down(&attribute_container_mutex); + if (!list_empty(&cont->containers)) + goto out; + retval = 0; + list_del(&cont->node); + out: + up(&attribute_container_mutex); + return retval; + +} +EXPORT_SYMBOL_GPL(attribute_container_unregister); + +/* private function used as class release */ +static void attribute_container_release(struct class_device *classdev) +{ + struct internal_container *ic + = container_of(classdev, struct internal_container, classdev); + struct device *dev = classdev->dev; + + kfree(ic); + put_device(dev); +} + +/** + * attribute_container_add_device - see if any container is interested in dev + * + * @dev: device to add attributes to + * @fn: function to trigger addition of class device. + * + * This function allocates storage for the class device(s) to be + * attached to dev (one for each matching attribute_container). If no + * fn is provided, the code will simply register the class device via + * class_device_add. If a function is provided, it is expected to add + * the class device at the appropriate time. One of the things that + * might be necessary is to allocate and initialise the classdev and + * then add it a later time. To do this, call this routine for + * allocation and initialisation and then use + * attribute_container_device_trigger() to call class_device_add() on + * it. Note: after this, the class device contains a reference to dev + * which is not relinquished until the release of the classdev. + */ +void +attribute_container_add_device(struct device *dev, + int (*fn)(struct attribute_container *, + struct device *, + struct class_device *)) +{ + struct attribute_container *cont; + + down(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + struct internal_container *ic; + + if (attribute_container_no_classdevs(cont)) + continue; + + if (!cont->match(cont, dev)) + continue; + ic = kmalloc(sizeof(struct internal_container), GFP_KERNEL); + if (!ic) { + dev_printk(KERN_ERR, dev, "failed to allocate class container\n"); + continue; + } + memset(ic, 0, sizeof(struct internal_container)); + INIT_LIST_HEAD(&ic->node); + ic->cont = cont; + class_device_initialize(&ic->classdev); + ic->classdev.dev = get_device(dev); + ic->classdev.class = cont->class; + cont->class->release = attribute_container_release; + strcpy(ic->classdev.class_id, dev->bus_id); + if (fn) + fn(cont, dev, &ic->classdev); + else + attribute_container_add_class_device(&ic->classdev); + list_add_tail(&ic->node, &cont->containers); + } + up(&attribute_container_mutex); +} + +/** + * attribute_container_remove_device - make device eligible for removal. + * + * @dev: The generic device + * @fn: A function to call to remove the device + * + * This routine triggers device removal. If fn is NULL, then it is + * simply done via class_device_unregister (note that if something + * still has a reference to the classdev, then the memory occupied + * will not be freed until the classdev is released). If you want a + * two phase release: remove from visibility and then delete the + * device, then you should use this routine with a fn that calls + * class_device_del() and then use + * attribute_container_device_trigger() to do the final put on the + * classdev. + */ +void +attribute_container_remove_device(struct device *dev, + void (*fn)(struct attribute_container *, + struct device *, + struct class_device *)) +{ + struct attribute_container *cont; + + down(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + struct internal_container *ic, *tmp; + + if (attribute_container_no_classdevs(cont)) + continue; + + if (!cont->match(cont, dev)) + continue; + list_for_each_entry_safe(ic, tmp, &cont->containers, node) { + if (dev != ic->classdev.dev) + continue; + list_del(&ic->node); + if (fn) + fn(cont, dev, &ic->classdev); + else { + attribute_container_remove_attrs(&ic->classdev); + class_device_unregister(&ic->classdev); + } + } + } + up(&attribute_container_mutex); +} +EXPORT_SYMBOL_GPL(attribute_container_remove_device); + +/** + * attribute_container_device_trigger - execute a trigger for each matching classdev + * + * @dev: The generic device to run the trigger for + * @fn the function to execute for each classdev. + * + * This funcion is for executing a trigger when you need to know both + * the container and the classdev. If you only care about the + * container, then use attribute_container_trigger() instead. + */ +void +attribute_container_device_trigger(struct device *dev, + int (*fn)(struct attribute_container *, + struct device *, + struct class_device *)) +{ + struct attribute_container *cont; + + down(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + struct internal_container *ic, *tmp; + + if (!cont->match(cont, dev)) + continue; + + list_for_each_entry_safe(ic, tmp, &cont->containers, node) { + if (dev == ic->classdev.dev) + fn(cont, dev, &ic->classdev); + } + } + up(&attribute_container_mutex); +} +EXPORT_SYMBOL_GPL(attribute_container_device_trigger); + +/** + * attribute_container_trigger - trigger a function for each matching container + * + * @dev: The generic device to activate the trigger for + * @fn: the function to trigger + * + * This routine triggers a function that only needs to know the + * matching containers (not the classdev) associated with a device. + * It is more lightweight than attribute_container_device_trigger, so + * should be used in preference unless the triggering function + * actually needs to know the classdev. + */ +void +attribute_container_trigger(struct device *dev, + int (*fn)(struct attribute_container *, + struct device *)) +{ + struct attribute_container *cont; + + down(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + if (cont->match(cont, dev)) + fn(cont, dev); + } + up(&attribute_container_mutex); +} +EXPORT_SYMBOL_GPL(attribute_container_trigger); + +/** + * attribute_container_add_attrs - add attributes + * + * @classdev: The class device + * + * This simply creates all the class device sysfs files from the + * attributes listed in the container + */ +int +attribute_container_add_attrs(struct class_device *classdev) +{ + struct attribute_container *cont = + attribute_container_classdev_to_container(classdev); + struct class_device_attribute **attrs = cont->attrs; + int i, error; + + if (!attrs) + return 0; + + for (i = 0; attrs[i]; i++) { + error = class_device_create_file(classdev, attrs[i]); + if (error) + return error; + } + + return 0; +} +EXPORT_SYMBOL_GPL(attribute_container_add_attrs); + +/** + * attribute_container_add_class_device - same function as class_device_add + * + * @classdev: the class device to add + * + * This performs essentially the same function as class_device_add except for + * attribute containers, namely add the classdev to the system and then + * create the attribute files + */ +int +attribute_container_add_class_device(struct class_device *classdev) +{ + int error = class_device_add(classdev); + if (error) + return error; + return attribute_container_add_attrs(classdev); +} +EXPORT_SYMBOL_GPL(attribute_container_add_class_device); + +/** + * attribute_container_add_class_device_adapter - simple adapter for triggers + * + * This function is identical to attribute_container_add_class_device except + * that it is designed to be called from the triggers + */ +int +attribute_container_add_class_device_adapter(struct attribute_container *cont, + struct device *dev, + struct class_device *classdev) +{ + return attribute_container_add_class_device(classdev); +} +EXPORT_SYMBOL_GPL(attribute_container_add_class_device_adapter); + +/** + * attribute_container_remove_attrs - remove any attribute files + * + * @classdev: The class device to remove the files from + * + */ +void +attribute_container_remove_attrs(struct class_device *classdev) +{ + struct attribute_container *cont = + attribute_container_classdev_to_container(classdev); + struct class_device_attribute **attrs = cont->attrs; + int i; + + if (!attrs) + return; + + for (i = 0; attrs[i]; i++) + class_device_remove_file(classdev, attrs[i]); +} +EXPORT_SYMBOL_GPL(attribute_container_remove_attrs); + +/** + * attribute_container_class_device_del - equivalent of class_device_del + * + * @classdev: the class device + * + * This function simply removes all the attribute files and then calls + * class_device_del. + */ +void +attribute_container_class_device_del(struct class_device *classdev) +{ + attribute_container_remove_attrs(classdev); + class_device_del(classdev); +} +EXPORT_SYMBOL_GPL(attribute_container_class_device_del); + +int __init +attribute_container_init(void) +{ + INIT_LIST_HEAD(&attribute_container_list); + return 0; +} diff --git a/drivers/base/base.h b/drivers/base/base.h new file mode 100644 index 000000000000..8d1e8bd48632 --- /dev/null +++ b/drivers/base/base.h @@ -0,0 +1,18 @@ +extern int bus_add_device(struct device * dev); +extern void bus_remove_device(struct device * dev); + +extern int bus_add_driver(struct device_driver *); +extern void bus_remove_driver(struct device_driver *); + +static inline struct class_device *to_class_dev(struct kobject *obj) +{ + return container_of(obj, struct class_device, kobj); +} + +static inline +struct class_device_attribute *to_class_dev_attr(struct attribute *_attr) +{ + return container_of(_attr, struct class_device_attribute, attr); +} + + diff --git a/drivers/base/bus.c b/drivers/base/bus.c new file mode 100644 index 000000000000..f4fa27315fb4 --- /dev/null +++ b/drivers/base/bus.c @@ -0,0 +1,770 @@ +/* + * bus.c - bus driver management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/string.h> +#include "base.h" +#include "power/power.h" + +#define to_dev(node) container_of(node, struct device, bus_list) +#define to_drv(node) container_of(node, struct device_driver, kobj.entry) + +#define to_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr) +#define to_bus(obj) container_of(obj, struct bus_type, subsys.kset.kobj) + +/* + * sysfs bindings for drivers + */ + +#define to_drv_attr(_attr) container_of(_attr, struct driver_attribute, attr) +#define to_driver(obj) container_of(obj, struct device_driver, kobj) + + +static ssize_t +drv_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct driver_attribute * drv_attr = to_drv_attr(attr); + struct device_driver * drv = to_driver(kobj); + ssize_t ret = 0; + + if (drv_attr->show) + ret = drv_attr->show(drv, buf); + return ret; +} + +static ssize_t +drv_attr_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + struct driver_attribute * drv_attr = to_drv_attr(attr); + struct device_driver * drv = to_driver(kobj); + ssize_t ret = 0; + + if (drv_attr->store) + ret = drv_attr->store(drv, buf, count); + return ret; +} + +static struct sysfs_ops driver_sysfs_ops = { + .show = drv_attr_show, + .store = drv_attr_store, +}; + + +static void driver_release(struct kobject * kobj) +{ + struct device_driver * drv = to_driver(kobj); + complete(&drv->unloaded); +} + +static struct kobj_type ktype_driver = { + .sysfs_ops = &driver_sysfs_ops, + .release = driver_release, +}; + + +/* + * sysfs bindings for buses + */ + + +static ssize_t +bus_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct bus_attribute * bus_attr = to_bus_attr(attr); + struct bus_type * bus = to_bus(kobj); + ssize_t ret = 0; + + if (bus_attr->show) + ret = bus_attr->show(bus, buf); + return ret; +} + +static ssize_t +bus_attr_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + struct bus_attribute * bus_attr = to_bus_attr(attr); + struct bus_type * bus = to_bus(kobj); + ssize_t ret = 0; + + if (bus_attr->store) + ret = bus_attr->store(bus, buf, count); + return ret; +} + +static struct sysfs_ops bus_sysfs_ops = { + .show = bus_attr_show, + .store = bus_attr_store, +}; + +int bus_create_file(struct bus_type * bus, struct bus_attribute * attr) +{ + int error; + if (get_bus(bus)) { + error = sysfs_create_file(&bus->subsys.kset.kobj, &attr->attr); + put_bus(bus); + } else + error = -EINVAL; + return error; +} + +void bus_remove_file(struct bus_type * bus, struct bus_attribute * attr) +{ + if (get_bus(bus)) { + sysfs_remove_file(&bus->subsys.kset.kobj, &attr->attr); + put_bus(bus); + } +} + +static struct kobj_type ktype_bus = { + .sysfs_ops = &bus_sysfs_ops, + +}; + +decl_subsys(bus, &ktype_bus, NULL); + +static int __bus_for_each_dev(struct bus_type *bus, struct device *start, + void *data, int (*fn)(struct device *, void *)) +{ + struct list_head *head; + struct device *dev; + int error = 0; + + if (!(bus = get_bus(bus))) + return -EINVAL; + + head = &bus->devices.list; + dev = list_prepare_entry(start, head, bus_list); + list_for_each_entry_continue(dev, head, bus_list) { + get_device(dev); + error = fn(dev, data); + put_device(dev); + if (error) + break; + } + put_bus(bus); + return error; +} + +static int __bus_for_each_drv(struct bus_type *bus, struct device_driver *start, + void * data, int (*fn)(struct device_driver *, void *)) +{ + struct list_head *head; + struct device_driver *drv; + int error = 0; + + if (!(bus = get_bus(bus))) + return -EINVAL; + + head = &bus->drivers.list; + drv = list_prepare_entry(start, head, kobj.entry); + list_for_each_entry_continue(drv, head, kobj.entry) { + get_driver(drv); + error = fn(drv, data); + put_driver(drv); + if (error) + break; + } + put_bus(bus); + return error; +} + +/** + * bus_for_each_dev - device iterator. + * @bus: bus type. + * @start: device to start iterating from. + * @data: data for the callback. + * @fn: function to be called for each device. + * + * Iterate over @bus's list of devices, and call @fn for each, + * passing it @data. If @start is not NULL, we use that device to + * begin iterating from. + * + * We check the return of @fn each time. If it returns anything + * other than 0, we break out and return that value. + * + * NOTE: The device that returns a non-zero value is not retained + * in any way, nor is its refcount incremented. If the caller needs + * to retain this data, it should do, and increment the reference + * count in the supplied callback. + */ + +int bus_for_each_dev(struct bus_type * bus, struct device * start, + void * data, int (*fn)(struct device *, void *)) +{ + int ret; + + down_read(&bus->subsys.rwsem); + ret = __bus_for_each_dev(bus, start, data, fn); + up_read(&bus->subsys.rwsem); + return ret; +} + +/** + * bus_for_each_drv - driver iterator + * @bus: bus we're dealing with. + * @start: driver to start iterating on. + * @data: data to pass to the callback. + * @fn: function to call for each driver. + * + * This is nearly identical to the device iterator above. + * We iterate over each driver that belongs to @bus, and call + * @fn for each. If @fn returns anything but 0, we break out + * and return it. If @start is not NULL, we use it as the head + * of the list. + * + * NOTE: we don't return the driver that returns a non-zero + * value, nor do we leave the reference count incremented for that + * driver. If the caller needs to know that info, it must set it + * in the callback. It must also be sure to increment the refcount + * so it doesn't disappear before returning to the caller. + */ + +int bus_for_each_drv(struct bus_type * bus, struct device_driver * start, + void * data, int (*fn)(struct device_driver *, void *)) +{ + int ret; + + down_read(&bus->subsys.rwsem); + ret = __bus_for_each_drv(bus, start, data, fn); + up_read(&bus->subsys.rwsem); + return ret; +} + +/** + * device_bind_driver - bind a driver to one device. + * @dev: device. + * + * Allow manual attachment of a driver to a device. + * Caller must have already set @dev->driver. + * + * Note that this does not modify the bus reference count + * nor take the bus's rwsem. Please verify those are accounted + * for before calling this. (It is ok to call with no other effort + * from a driver's probe() method.) + */ + +void device_bind_driver(struct device * dev) +{ + pr_debug("bound device '%s' to driver '%s'\n", + dev->bus_id, dev->driver->name); + list_add_tail(&dev->driver_list, &dev->driver->devices); + sysfs_create_link(&dev->driver->kobj, &dev->kobj, + kobject_name(&dev->kobj)); + sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver"); +} + + +/** + * driver_probe_device - attempt to bind device & driver. + * @drv: driver. + * @dev: device. + * + * First, we call the bus's match function, if one present, which + * should compare the device IDs the driver supports with the + * device IDs of the device. Note we don't do this ourselves + * because we don't know the format of the ID structures, nor what + * is to be considered a match and what is not. + * + * If we find a match, we call @drv->probe(@dev) if it exists, and + * call device_bind_driver() above. + */ +int driver_probe_device(struct device_driver * drv, struct device * dev) +{ + if (drv->bus->match && !drv->bus->match(dev, drv)) + return -ENODEV; + + dev->driver = drv; + if (drv->probe) { + int error = drv->probe(dev); + if (error) { + dev->driver = NULL; + return error; + } + } + + device_bind_driver(dev); + return 0; +} + + +/** + * device_attach - try to attach device to a driver. + * @dev: device. + * + * Walk the list of drivers that the bus has and call + * driver_probe_device() for each pair. If a compatible + * pair is found, break out and return. + */ +int device_attach(struct device * dev) +{ + struct bus_type * bus = dev->bus; + struct list_head * entry; + int error; + + if (dev->driver) { + device_bind_driver(dev); + return 1; + } + + if (bus->match) { + list_for_each(entry, &bus->drivers.list) { + struct device_driver * drv = to_drv(entry); + error = driver_probe_device(drv, dev); + if (!error) + /* success, driver matched */ + return 1; + if (error != -ENODEV && error != -ENXIO) + /* driver matched but the probe failed */ + printk(KERN_WARNING + "%s: probe of %s failed with error %d\n", + drv->name, dev->bus_id, error); + } + } + + return 0; +} + + +/** + * driver_attach - try to bind driver to devices. + * @drv: driver. + * + * Walk the list of devices that the bus has on it and try to + * match the driver with each one. If driver_probe_device() + * returns 0 and the @dev->driver is set, we've found a + * compatible pair. + * + * Note that we ignore the -ENODEV error from driver_probe_device(), + * since it's perfectly valid for a driver not to bind to any devices. + */ +void driver_attach(struct device_driver * drv) +{ + struct bus_type * bus = drv->bus; + struct list_head * entry; + int error; + + if (!bus->match) + return; + + list_for_each(entry, &bus->devices.list) { + struct device * dev = container_of(entry, struct device, bus_list); + if (!dev->driver) { + error = driver_probe_device(drv, dev); + if (error && (error != -ENODEV)) + /* driver matched but the probe failed */ + printk(KERN_WARNING + "%s: probe of %s failed with error %d\n", + drv->name, dev->bus_id, error); + } + } +} + + +/** + * device_release_driver - manually detach device from driver. + * @dev: device. + * + * Manually detach device from driver. + * Note that this is called without incrementing the bus + * reference count nor taking the bus's rwsem. Be sure that + * those are accounted for before calling this function. + */ + +void device_release_driver(struct device * dev) +{ + struct device_driver * drv = dev->driver; + if (drv) { + sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj)); + sysfs_remove_link(&dev->kobj, "driver"); + list_del_init(&dev->driver_list); + device_detach_shutdown(dev); + if (drv->remove) + drv->remove(dev); + dev->driver = NULL; + } +} + + +/** + * driver_detach - detach driver from all devices it controls. + * @drv: driver. + */ + +static void driver_detach(struct device_driver * drv) +{ + struct list_head * entry, * next; + list_for_each_safe(entry, next, &drv->devices) { + struct device * dev = container_of(entry, struct device, driver_list); + device_release_driver(dev); + } +} + +static int device_add_attrs(struct bus_type * bus, struct device * dev) +{ + int error = 0; + int i; + + if (bus->dev_attrs) { + for (i = 0; attr_name(bus->dev_attrs[i]); i++) { + error = device_create_file(dev,&bus->dev_attrs[i]); + if (error) + goto Err; + } + } + Done: + return error; + Err: + while (--i >= 0) + device_remove_file(dev,&bus->dev_attrs[i]); + goto Done; +} + + +static void device_remove_attrs(struct bus_type * bus, struct device * dev) +{ + int i; + + if (bus->dev_attrs) { + for (i = 0; attr_name(bus->dev_attrs[i]); i++) + device_remove_file(dev,&bus->dev_attrs[i]); + } +} + + +/** + * bus_add_device - add device to bus + * @dev: device being added + * + * - Add the device to its bus's list of devices. + * - Try to attach to driver. + * - Create link to device's physical location. + */ +int bus_add_device(struct device * dev) +{ + struct bus_type * bus = get_bus(dev->bus); + int error = 0; + + if (bus) { + down_write(&dev->bus->subsys.rwsem); + pr_debug("bus %s: add device %s\n", bus->name, dev->bus_id); + list_add_tail(&dev->bus_list, &dev->bus->devices.list); + device_attach(dev); + up_write(&dev->bus->subsys.rwsem); + device_add_attrs(bus, dev); + sysfs_create_link(&bus->devices.kobj, &dev->kobj, dev->bus_id); + sysfs_create_link(&dev->kobj, &dev->bus->subsys.kset.kobj, "bus"); + } + return error; +} + +/** + * bus_remove_device - remove device from bus + * @dev: device to be removed + * + * - Remove symlink from bus's 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, "bus"); + sysfs_remove_link(&dev->bus->devices.kobj, dev->bus_id); + device_remove_attrs(dev->bus, dev); + down_write(&dev->bus->subsys.rwsem); + pr_debug("bus %s: remove device %s\n", dev->bus->name, dev->bus_id); + device_release_driver(dev); + list_del_init(&dev->bus_list); + up_write(&dev->bus->subsys.rwsem); + put_bus(dev->bus); + } +} + +static int driver_add_attrs(struct bus_type * bus, struct device_driver * drv) +{ + int error = 0; + int i; + + if (bus->drv_attrs) { + for (i = 0; attr_name(bus->drv_attrs[i]); i++) { + error = driver_create_file(drv, &bus->drv_attrs[i]); + if (error) + goto Err; + } + } + Done: + return error; + Err: + while (--i >= 0) + driver_remove_file(drv, &bus->drv_attrs[i]); + goto Done; +} + + +static void driver_remove_attrs(struct bus_type * bus, struct device_driver * drv) +{ + int i; + + if (bus->drv_attrs) { + for (i = 0; attr_name(bus->drv_attrs[i]); i++) + driver_remove_file(drv, &bus->drv_attrs[i]); + } +} + + +/** + * bus_add_driver - Add a driver to the bus. + * @drv: driver. + * + */ +int bus_add_driver(struct device_driver * drv) +{ + struct bus_type * bus = get_bus(drv->bus); + int error = 0; + + if (bus) { + pr_debug("bus %s: add driver %s\n", bus->name, drv->name); + error = kobject_set_name(&drv->kobj, "%s", drv->name); + if (error) { + put_bus(bus); + return error; + } + drv->kobj.kset = &bus->drivers; + if ((error = kobject_register(&drv->kobj))) { + put_bus(bus); + return error; + } + + down_write(&bus->subsys.rwsem); + driver_attach(drv); + up_write(&bus->subsys.rwsem); + module_add_driver(drv->owner, drv); + + driver_add_attrs(bus, drv); + } + return error; +} + + +/** + * bus_remove_driver - delete driver from bus's knowledge. + * @drv: driver. + * + * Detach the driver from the devices it controls, and remove + * it from its bus's list of drivers. Finally, we drop the reference + * to the bus we took in bus_add_driver(). + */ + +void bus_remove_driver(struct device_driver * drv) +{ + if (drv->bus) { + driver_remove_attrs(drv->bus, drv); + down_write(&drv->bus->subsys.rwsem); + pr_debug("bus %s: remove driver %s\n", drv->bus->name, drv->name); + driver_detach(drv); + up_write(&drv->bus->subsys.rwsem); + module_remove_driver(drv); + kobject_unregister(&drv->kobj); + put_bus(drv->bus); + } +} + + +/* Helper for bus_rescan_devices's iter */ +static int bus_rescan_devices_helper(struct device *dev, void *data) +{ + int *count = data; + + if (!dev->driver && device_attach(dev)) + (*count)++; + + return 0; +} + + +/** + * bus_rescan_devices - rescan devices on the bus for possible drivers + * @bus: the bus to scan. + * + * This function will look for devices on the bus with no driver + * attached and rescan it against existing drivers to see if it + * matches any. Calls device_attach(). Returns the number of devices + * that were sucessfully bound to a driver. + */ +int bus_rescan_devices(struct bus_type * bus) +{ + int count = 0; + + down_write(&bus->subsys.rwsem); + __bus_for_each_dev(bus, NULL, &count, bus_rescan_devices_helper); + up_write(&bus->subsys.rwsem); + + return count; +} + + +struct bus_type * get_bus(struct bus_type * bus) +{ + return bus ? container_of(subsys_get(&bus->subsys), struct bus_type, subsys) : NULL; +} + +void put_bus(struct bus_type * bus) +{ + subsys_put(&bus->subsys); +} + + +/** + * find_bus - locate bus by name. + * @name: name of bus. + * + * Call kset_find_obj() to iterate over list of buses to + * find a bus by name. Return bus if found. + * + * Note that kset_find_obj increments bus' reference count. + */ + +struct bus_type * find_bus(char * name) +{ + struct kobject * k = kset_find_obj(&bus_subsys.kset, name); + return k ? to_bus(k) : NULL; +} + + +/** + * bus_add_attrs - Add default attributes for this bus. + * @bus: Bus that has just been registered. + */ + +static int bus_add_attrs(struct bus_type * bus) +{ + int error = 0; + int i; + + if (bus->bus_attrs) { + for (i = 0; attr_name(bus->bus_attrs[i]); i++) { + if ((error = bus_create_file(bus,&bus->bus_attrs[i]))) + goto Err; + } + } + Done: + return error; + Err: + while (--i >= 0) + bus_remove_file(bus,&bus->bus_attrs[i]); + goto Done; +} + +static void bus_remove_attrs(struct bus_type * bus) +{ + int i; + + if (bus->bus_attrs) { + for (i = 0; attr_name(bus->bus_attrs[i]); i++) + bus_remove_file(bus,&bus->bus_attrs[i]); + } +} + +/** + * bus_register - register a bus with the system. + * @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. + */ +int bus_register(struct bus_type * bus) +{ + int retval; + + retval = kobject_set_name(&bus->subsys.kset.kobj, "%s", bus->name); + if (retval) + goto out; + + subsys_set_kset(bus, bus_subsys); + retval = subsystem_register(&bus->subsys); + if (retval) + goto out; + + kobject_set_name(&bus->devices.kobj, "devices"); + bus->devices.subsys = &bus->subsys; + retval = kset_register(&bus->devices); + if (retval) + goto bus_devices_fail; + + kobject_set_name(&bus->drivers.kobj, "drivers"); + bus->drivers.subsys = &bus->subsys; + bus->drivers.ktype = &ktype_driver; + retval = kset_register(&bus->drivers); + if (retval) + goto bus_drivers_fail; + bus_add_attrs(bus); + + pr_debug("bus type '%s' registered\n", bus->name); + return 0; + +bus_drivers_fail: + kset_unregister(&bus->devices); +bus_devices_fail: + subsystem_unregister(&bus->subsys); +out: + return retval; +} + + +/** + * bus_unregister - remove a bus from the system + * @bus: bus. + * + * Unregister the child subsystems and the bus itself. + * Finally, we call put_bus() to release the refcount + */ +void bus_unregister(struct bus_type * bus) +{ + pr_debug("bus %s: unregistering\n", bus->name); + bus_remove_attrs(bus); + kset_unregister(&bus->drivers); + kset_unregister(&bus->devices); + subsystem_unregister(&bus->subsys); +} + +int __init buses_init(void) +{ + return subsystem_register(&bus_subsys); +} + + +EXPORT_SYMBOL_GPL(bus_for_each_dev); +EXPORT_SYMBOL_GPL(bus_for_each_drv); + +EXPORT_SYMBOL_GPL(driver_probe_device); +EXPORT_SYMBOL_GPL(device_bind_driver); +EXPORT_SYMBOL_GPL(device_release_driver); +EXPORT_SYMBOL_GPL(device_attach); +EXPORT_SYMBOL_GPL(driver_attach); + +EXPORT_SYMBOL_GPL(bus_add_device); +EXPORT_SYMBOL_GPL(bus_remove_device); +EXPORT_SYMBOL_GPL(bus_register); +EXPORT_SYMBOL_GPL(bus_unregister); +EXPORT_SYMBOL_GPL(bus_rescan_devices); +EXPORT_SYMBOL_GPL(get_bus); +EXPORT_SYMBOL_GPL(put_bus); +EXPORT_SYMBOL_GPL(find_bus); + +EXPORT_SYMBOL_GPL(bus_create_file); +EXPORT_SYMBOL_GPL(bus_remove_file); diff --git a/drivers/base/class.c b/drivers/base/class.c new file mode 100644 index 000000000000..6bf650fce78c --- /dev/null +++ b/drivers/base/class.c @@ -0,0 +1,591 @@ +/* + * class.c - basic device class management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2003-2004 Greg Kroah-Hartman + * Copyright (c) 2003-2004 IBM Corp. + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/kdev_t.h> +#include "base.h" + +#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr) +#define to_class(obj) container_of(obj, struct class, subsys.kset.kobj) + +static ssize_t +class_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct class_attribute * class_attr = to_class_attr(attr); + struct class * dc = to_class(kobj); + ssize_t ret = 0; + + if (class_attr->show) + ret = class_attr->show(dc, buf); + return ret; +} + +static ssize_t +class_attr_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + struct class_attribute * class_attr = to_class_attr(attr); + struct class * dc = to_class(kobj); + ssize_t ret = 0; + + if (class_attr->store) + ret = class_attr->store(dc, buf, count); + return ret; +} + +static void class_release(struct kobject * kobj) +{ + struct class *class = to_class(kobj); + + pr_debug("class '%s': release.\n", class->name); + + if (class->class_release) + class->class_release(class); + else + pr_debug("class '%s' does not have a release() function, " + "be careful\n", class->name); +} + +static struct sysfs_ops class_sysfs_ops = { + .show = class_attr_show, + .store = class_attr_store, +}; + +static struct kobj_type ktype_class = { + .sysfs_ops = &class_sysfs_ops, + .release = class_release, +}; + +/* Hotplug events for classes go to the class_obj subsys */ +static decl_subsys(class, &ktype_class, NULL); + + +int class_create_file(struct class * cls, const struct class_attribute * attr) +{ + int error; + if (cls) { + error = sysfs_create_file(&cls->subsys.kset.kobj, &attr->attr); + } else + error = -EINVAL; + return error; +} + +void class_remove_file(struct class * cls, const struct class_attribute * attr) +{ + if (cls) + sysfs_remove_file(&cls->subsys.kset.kobj, &attr->attr); +} + +struct class * class_get(struct class * cls) +{ + if (cls) + return container_of(subsys_get(&cls->subsys), struct class, subsys); + return NULL; +} + +void class_put(struct class * cls) +{ + subsys_put(&cls->subsys); +} + + +static int add_class_attrs(struct class * cls) +{ + int i; + int error = 0; + + if (cls->class_attrs) { + for (i = 0; attr_name(cls->class_attrs[i]); i++) { + error = class_create_file(cls,&cls->class_attrs[i]); + if (error) + goto Err; + } + } + Done: + return error; + Err: + while (--i >= 0) + class_remove_file(cls,&cls->class_attrs[i]); + goto Done; +} + +static void remove_class_attrs(struct class * cls) +{ + int i; + + if (cls->class_attrs) { + for (i = 0; attr_name(cls->class_attrs[i]); i++) + class_remove_file(cls,&cls->class_attrs[i]); + } +} + +int class_register(struct class * cls) +{ + int error; + + pr_debug("device class '%s': registering\n", cls->name); + + INIT_LIST_HEAD(&cls->children); + INIT_LIST_HEAD(&cls->interfaces); + init_MUTEX(&cls->sem); + error = kobject_set_name(&cls->subsys.kset.kobj, "%s", cls->name); + if (error) + return error; + + subsys_set_kset(cls, class_subsys); + + error = subsystem_register(&cls->subsys); + if (!error) { + error = add_class_attrs(class_get(cls)); + class_put(cls); + } + return error; +} + +void class_unregister(struct class * cls) +{ + pr_debug("device class '%s': unregistering\n", cls->name); + remove_class_attrs(cls); + subsystem_unregister(&cls->subsys); +} + + +/* Class Device Stuff */ + +int class_device_create_file(struct class_device * class_dev, + const struct class_device_attribute * attr) +{ + int error = -EINVAL; + if (class_dev) + error = sysfs_create_file(&class_dev->kobj, &attr->attr); + return error; +} + +void class_device_remove_file(struct class_device * class_dev, + const struct class_device_attribute * attr) +{ + if (class_dev) + sysfs_remove_file(&class_dev->kobj, &attr->attr); +} + +int class_device_create_bin_file(struct class_device *class_dev, + struct bin_attribute *attr) +{ + int error = -EINVAL; + if (class_dev) + error = sysfs_create_bin_file(&class_dev->kobj, attr); + return error; +} + +void class_device_remove_bin_file(struct class_device *class_dev, + struct bin_attribute *attr) +{ + if (class_dev) + sysfs_remove_bin_file(&class_dev->kobj, attr); +} + +static ssize_t +class_device_attr_show(struct kobject * kobj, struct attribute * attr, + char * buf) +{ + struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr); + struct class_device * cd = to_class_dev(kobj); + ssize_t ret = 0; + + if (class_dev_attr->show) + ret = class_dev_attr->show(cd, buf); + return ret; +} + +static ssize_t +class_device_attr_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr); + struct class_device * cd = to_class_dev(kobj); + ssize_t ret = 0; + + if (class_dev_attr->store) + ret = class_dev_attr->store(cd, buf, count); + return ret; +} + +static struct sysfs_ops class_dev_sysfs_ops = { + .show = class_device_attr_show, + .store = class_device_attr_store, +}; + +static void class_dev_release(struct kobject * kobj) +{ + struct class_device *cd = to_class_dev(kobj); + struct class * cls = cd->class; + + pr_debug("device class '%s': release.\n", cd->class_id); + + if (cls->release) + cls->release(cd); + else { + printk(KERN_ERR "Device class '%s' does not have a release() function, " + "it is broken and must be fixed.\n", + cd->class_id); + WARN_ON(1); + } +} + +static struct kobj_type ktype_class_device = { + .sysfs_ops = &class_dev_sysfs_ops, + .release = class_dev_release, +}; + +static int class_hotplug_filter(struct kset *kset, struct kobject *kobj) +{ + struct kobj_type *ktype = get_ktype(kobj); + + if (ktype == &ktype_class_device) { + struct class_device *class_dev = to_class_dev(kobj); + if (class_dev->class) + return 1; + } + return 0; +} + +static char *class_hotplug_name(struct kset *kset, struct kobject *kobj) +{ + struct class_device *class_dev = to_class_dev(kobj); + + return class_dev->class->name; +} + +static int class_hotplug(struct kset *kset, struct kobject *kobj, char **envp, + int num_envp, char *buffer, int buffer_size) +{ + struct class_device *class_dev = to_class_dev(kobj); + int i = 0; + int length = 0; + int retval = 0; + + pr_debug("%s - name = %s\n", __FUNCTION__, class_dev->class_id); + + if (class_dev->dev) { + /* add physical device, backing this device */ + struct device *dev = class_dev->dev; + char *path = kobject_get_path(&dev->kobj, GFP_KERNEL); + + add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, + &length, "PHYSDEVPATH=%s", path); + kfree(path); + + if (dev->bus) + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVBUS=%s", dev->bus->name); + + if (dev->driver) + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVDRIVER=%s", dev->driver->name); + } + + if (MAJOR(class_dev->devt)) { + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "MAJOR=%u", MAJOR(class_dev->devt)); + + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "MINOR=%u", MINOR(class_dev->devt)); + } + + /* terminate, set to next free slot, shrink available space */ + envp[i] = NULL; + envp = &envp[i]; + num_envp -= i; + buffer = &buffer[length]; + buffer_size -= length; + + if (class_dev->class->hotplug) { + /* have the bus specific function add its stuff */ + retval = class_dev->class->hotplug (class_dev, envp, num_envp, + buffer, buffer_size); + if (retval) { + pr_debug ("%s - hotplug() returned %d\n", + __FUNCTION__, retval); + } + } + + return retval; +} + +static struct kset_hotplug_ops class_hotplug_ops = { + .filter = class_hotplug_filter, + .name = class_hotplug_name, + .hotplug = class_hotplug, +}; + +static decl_subsys(class_obj, &ktype_class_device, &class_hotplug_ops); + + +static int class_device_add_attrs(struct class_device * cd) +{ + int i; + int error = 0; + struct class * cls = cd->class; + + if (cls->class_dev_attrs) { + for (i = 0; attr_name(cls->class_dev_attrs[i]); i++) { + error = class_device_create_file(cd, + &cls->class_dev_attrs[i]); + if (error) + goto Err; + } + } + Done: + return error; + Err: + while (--i >= 0) + class_device_remove_file(cd,&cls->class_dev_attrs[i]); + goto Done; +} + +static void class_device_remove_attrs(struct class_device * cd) +{ + int i; + struct class * cls = cd->class; + + if (cls->class_dev_attrs) { + for (i = 0; attr_name(cls->class_dev_attrs[i]); i++) + class_device_remove_file(cd,&cls->class_dev_attrs[i]); + } +} + +static ssize_t show_dev(struct class_device *class_dev, char *buf) +{ + return print_dev_t(buf, class_dev->devt); +} +static CLASS_DEVICE_ATTR(dev, S_IRUGO, show_dev, NULL); + +void class_device_initialize(struct class_device *class_dev) +{ + kobj_set_kset_s(class_dev, class_obj_subsys); + kobject_init(&class_dev->kobj); + INIT_LIST_HEAD(&class_dev->node); +} + +int class_device_add(struct class_device *class_dev) +{ + struct class * parent = NULL; + struct class_interface * class_intf; + int error; + + class_dev = class_device_get(class_dev); + if (!class_dev) + return -EINVAL; + + if (!strlen(class_dev->class_id)) { + error = -EINVAL; + goto register_done; + } + + parent = class_get(class_dev->class); + + pr_debug("CLASS: registering class device: ID = '%s'\n", + class_dev->class_id); + + /* first, register with generic layer. */ + kobject_set_name(&class_dev->kobj, "%s", class_dev->class_id); + if (parent) + class_dev->kobj.parent = &parent->subsys.kset.kobj; + + if ((error = kobject_add(&class_dev->kobj))) + goto register_done; + + /* now take care of our own registration */ + if (parent) { + down(&parent->sem); + list_add_tail(&class_dev->node, &parent->children); + list_for_each_entry(class_intf, &parent->interfaces, node) + if (class_intf->add) + class_intf->add(class_dev); + up(&parent->sem); + } + + if (MAJOR(class_dev->devt)) + class_device_create_file(class_dev, &class_device_attr_dev); + + class_device_add_attrs(class_dev); + if (class_dev->dev) + sysfs_create_link(&class_dev->kobj, + &class_dev->dev->kobj, "device"); + + register_done: + if (error && parent) + class_put(parent); + class_device_put(class_dev); + return error; +} + +int class_device_register(struct class_device *class_dev) +{ + class_device_initialize(class_dev); + return class_device_add(class_dev); +} + +void class_device_del(struct class_device *class_dev) +{ + struct class * parent = class_dev->class; + struct class_interface * class_intf; + + if (parent) { + down(&parent->sem); + list_del_init(&class_dev->node); + list_for_each_entry(class_intf, &parent->interfaces, node) + if (class_intf->remove) + class_intf->remove(class_dev); + up(&parent->sem); + } + + if (class_dev->dev) + sysfs_remove_link(&class_dev->kobj, "device"); + class_device_remove_attrs(class_dev); + + kobject_del(&class_dev->kobj); + + if (parent) + class_put(parent); +} + +void class_device_unregister(struct class_device *class_dev) +{ + pr_debug("CLASS: Unregistering class device. ID = '%s'\n", + class_dev->class_id); + class_device_del(class_dev); + class_device_put(class_dev); +} + +int class_device_rename(struct class_device *class_dev, char *new_name) +{ + int error = 0; + + class_dev = class_device_get(class_dev); + if (!class_dev) + return -EINVAL; + + pr_debug("CLASS: renaming '%s' to '%s'\n", class_dev->class_id, + new_name); + + strlcpy(class_dev->class_id, new_name, KOBJ_NAME_LEN); + + error = kobject_rename(&class_dev->kobj, new_name); + + class_device_put(class_dev); + + return error; +} + +struct class_device * class_device_get(struct class_device *class_dev) +{ + if (class_dev) + return to_class_dev(kobject_get(&class_dev->kobj)); + return NULL; +} + +void class_device_put(struct class_device *class_dev) +{ + kobject_put(&class_dev->kobj); +} + + +int class_interface_register(struct class_interface *class_intf) +{ + struct class *parent; + struct class_device *class_dev; + + if (!class_intf || !class_intf->class) + return -ENODEV; + + parent = class_get(class_intf->class); + if (!parent) + return -EINVAL; + + down(&parent->sem); + list_add_tail(&class_intf->node, &parent->interfaces); + if (class_intf->add) { + list_for_each_entry(class_dev, &parent->children, node) + class_intf->add(class_dev); + } + up(&parent->sem); + + return 0; +} + +void class_interface_unregister(struct class_interface *class_intf) +{ + struct class * parent = class_intf->class; + struct class_device *class_dev; + + if (!parent) + return; + + down(&parent->sem); + list_del_init(&class_intf->node); + if (class_intf->remove) { + list_for_each_entry(class_dev, &parent->children, node) + class_intf->remove(class_dev); + } + up(&parent->sem); + + class_put(parent); +} + + + +int __init classes_init(void) +{ + int retval; + + retval = subsystem_register(&class_subsys); + if (retval) + return retval; + + /* ick, this is ugly, the things we go through to keep from showing up + * in sysfs... */ + subsystem_init(&class_obj_subsys); + if (!class_obj_subsys.kset.subsys) + class_obj_subsys.kset.subsys = &class_obj_subsys; + return 0; +} + +EXPORT_SYMBOL_GPL(class_create_file); +EXPORT_SYMBOL_GPL(class_remove_file); +EXPORT_SYMBOL_GPL(class_register); +EXPORT_SYMBOL_GPL(class_unregister); +EXPORT_SYMBOL_GPL(class_get); +EXPORT_SYMBOL_GPL(class_put); + +EXPORT_SYMBOL_GPL(class_device_register); +EXPORT_SYMBOL_GPL(class_device_unregister); +EXPORT_SYMBOL_GPL(class_device_initialize); +EXPORT_SYMBOL_GPL(class_device_add); +EXPORT_SYMBOL_GPL(class_device_del); +EXPORT_SYMBOL_GPL(class_device_get); +EXPORT_SYMBOL_GPL(class_device_put); +EXPORT_SYMBOL_GPL(class_device_create_file); +EXPORT_SYMBOL_GPL(class_device_remove_file); +EXPORT_SYMBOL_GPL(class_device_create_bin_file); +EXPORT_SYMBOL_GPL(class_device_remove_bin_file); + +EXPORT_SYMBOL_GPL(class_interface_register); +EXPORT_SYMBOL_GPL(class_interface_unregister); diff --git a/drivers/base/class_simple.c b/drivers/base/class_simple.c new file mode 100644 index 000000000000..27699eb20a37 --- /dev/null +++ b/drivers/base/class_simple.c @@ -0,0 +1,199 @@ +/* + * class_simple.c - a "simple" interface for classes for simple char devices. + * + * Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (c) 2003-2004 IBM Corp. + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <linux/err.h> + +struct class_simple { + struct class class; +}; +#define to_class_simple(d) container_of(d, struct class_simple, class) + +struct simple_dev { + struct list_head node; + struct class_device class_dev; +}; +#define to_simple_dev(d) container_of(d, struct simple_dev, class_dev) + +static LIST_HEAD(simple_dev_list); +static DEFINE_SPINLOCK(simple_dev_list_lock); + +static void release_simple_dev(struct class_device *class_dev) +{ + struct simple_dev *s_dev = to_simple_dev(class_dev); + kfree(s_dev); +} + +static void class_simple_release(struct class *class) +{ + struct class_simple *cs = to_class_simple(class); + kfree(cs); +} + +/** + * class_simple_create - create a struct class_simple structure + * @owner: pointer to the module that is to "own" this struct class_simple + * @name: pointer to a string for the name of this class. + * + * This is used to create a struct class_simple pointer that can then be used + * in calls to class_simple_device_add(). This is used when you do not wish to + * create a full blown class support for a type of char devices. + * + * Note, the pointer created here is to be destroyed when finished by making a + * call to class_simple_destroy(). + */ +struct class_simple *class_simple_create(struct module *owner, char *name) +{ + struct class_simple *cs; + int retval; + + cs = kmalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) { + retval = -ENOMEM; + goto error; + } + memset(cs, 0x00, sizeof(*cs)); + + cs->class.name = name; + cs->class.class_release = class_simple_release; + cs->class.release = release_simple_dev; + + retval = class_register(&cs->class); + if (retval) + goto error; + + return cs; + +error: + kfree(cs); + return ERR_PTR(retval); +} +EXPORT_SYMBOL(class_simple_create); + +/** + * class_simple_destroy - destroys a struct class_simple structure + * @cs: pointer to the struct class_simple that is to be destroyed + * + * Note, the pointer to be destroyed must have been created with a call to + * class_simple_create(). + */ +void class_simple_destroy(struct class_simple *cs) +{ + if ((cs == NULL) || (IS_ERR(cs))) + return; + + class_unregister(&cs->class); +} +EXPORT_SYMBOL(class_simple_destroy); + +/** + * class_simple_device_add - adds a class device to sysfs for a character driver + * @cs: pointer to the struct class_simple that this device should be registered to. + * @dev: the dev_t for the device to be added. + * @device: a pointer to a struct device that is assiociated with this class device. + * @fmt: string for the class device's name + * + * This function can be used by simple char device classes that do not + * implement their own class device registration. A struct class_device will + * be created in sysfs, registered to the specified class. A "dev" file will + * be created, showing the dev_t for the device. The pointer to the struct + * class_device will be returned from the call. Any further sysfs files that + * might be required can be created using this pointer. + * Note: the struct class_simple passed to this function must have previously been + * created with a call to class_simple_create(). + */ +struct class_device *class_simple_device_add(struct class_simple *cs, dev_t dev, struct device *device, const char *fmt, ...) +{ + va_list args; + struct simple_dev *s_dev = NULL; + int retval; + + if ((cs == NULL) || (IS_ERR(cs))) { + retval = -ENODEV; + goto error; + } + + s_dev = kmalloc(sizeof(*s_dev), GFP_KERNEL); + if (!s_dev) { + retval = -ENOMEM; + goto error; + } + memset(s_dev, 0x00, sizeof(*s_dev)); + + s_dev->class_dev.devt = dev; + s_dev->class_dev.dev = device; + s_dev->class_dev.class = &cs->class; + + va_start(args, fmt); + vsnprintf(s_dev->class_dev.class_id, BUS_ID_SIZE, fmt, args); + va_end(args); + retval = class_device_register(&s_dev->class_dev); + if (retval) + goto error; + + spin_lock(&simple_dev_list_lock); + list_add(&s_dev->node, &simple_dev_list); + spin_unlock(&simple_dev_list_lock); + + return &s_dev->class_dev; + +error: + kfree(s_dev); + return ERR_PTR(retval); +} +EXPORT_SYMBOL(class_simple_device_add); + +/** + * class_simple_set_hotplug - set the hotplug callback in the embedded struct class + * @cs: pointer to the struct class_simple to hold the pointer + * @hotplug: function pointer to the hotplug function + * + * Implement and set a hotplug function to add environment variables specific to this + * class on the hotplug event. + */ +int class_simple_set_hotplug(struct class_simple *cs, + int (*hotplug)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size)) +{ + if ((cs == NULL) || (IS_ERR(cs))) + return -ENODEV; + cs->class.hotplug = hotplug; + return 0; +} +EXPORT_SYMBOL(class_simple_set_hotplug); + +/** + * class_simple_device_remove - removes a class device that was created with class_simple_device_add() + * @dev: the dev_t of the device that was previously registered. + * + * This call unregisters and cleans up a class device that was created with a + * call to class_device_simple_add() + */ +void class_simple_device_remove(dev_t dev) +{ + struct simple_dev *s_dev = NULL; + int found = 0; + + spin_lock(&simple_dev_list_lock); + list_for_each_entry(s_dev, &simple_dev_list, node) { + if (s_dev->class_dev.devt == dev) { + found = 1; + break; + } + } + if (found) { + list_del(&s_dev->node); + spin_unlock(&simple_dev_list_lock); + class_device_unregister(&s_dev->class_dev); + } else { + spin_unlock(&simple_dev_list_lock); + } +} +EXPORT_SYMBOL(class_simple_device_remove); diff --git a/drivers/base/core.c b/drivers/base/core.c new file mode 100644 index 000000000000..4e6cce8e6d35 --- /dev/null +++ b/drivers/base/core.c @@ -0,0 +1,439 @@ +/* + * drivers/base/core.c - core driver model code (device registration, etc) + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <asm/semaphore.h> + +#include "base.h" +#include "power/power.h" + +int (*platform_notify)(struct device * dev) = NULL; +int (*platform_notify_remove)(struct device * dev) = NULL; + +/* + * sysfs bindings for devices. + */ + +#define to_dev(obj) container_of(obj, struct device, kobj) +#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) + +extern struct attribute * dev_default_attrs[]; + +static ssize_t +dev_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct device_attribute * dev_attr = to_dev_attr(attr); + struct device * dev = to_dev(kobj); + ssize_t ret = 0; + + if (dev_attr->show) + ret = dev_attr->show(dev, buf); + return ret; +} + +static ssize_t +dev_attr_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + struct device_attribute * dev_attr = to_dev_attr(attr); + struct device * dev = to_dev(kobj); + ssize_t ret = 0; + + if (dev_attr->store) + ret = dev_attr->store(dev, buf, count); + return ret; +} + +static struct sysfs_ops dev_sysfs_ops = { + .show = dev_attr_show, + .store = dev_attr_store, +}; + + +/** + * device_release - free device structure. + * @kobj: device's kobject. + * + * This is called once the reference count for the object + * reaches 0. We forward the call to the device's release + * method, which should handle actually freeing the structure. + */ +static void device_release(struct kobject * kobj) +{ + struct device * dev = to_dev(kobj); + + if (dev->release) + dev->release(dev); + else { + printk(KERN_ERR "Device '%s' does not have a release() function, " + "it is broken and must be fixed.\n", + dev->bus_id); + WARN_ON(1); + } +} + +static struct kobj_type ktype_device = { + .release = device_release, + .sysfs_ops = &dev_sysfs_ops, + .default_attrs = dev_default_attrs, +}; + + +static int dev_hotplug_filter(struct kset *kset, struct kobject *kobj) +{ + struct kobj_type *ktype = get_ktype(kobj); + + if (ktype == &ktype_device) { + struct device *dev = to_dev(kobj); + if (dev->bus) + return 1; + } + return 0; +} + +static char *dev_hotplug_name(struct kset *kset, struct kobject *kobj) +{ + struct device *dev = to_dev(kobj); + + return dev->bus->name; +} + +static int dev_hotplug(struct kset *kset, struct kobject *kobj, char **envp, + int num_envp, char *buffer, int buffer_size) +{ + struct device *dev = to_dev(kobj); + int i = 0; + int length = 0; + int retval = 0; + + /* add bus name of physical device */ + if (dev->bus) + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVBUS=%s", dev->bus->name); + + /* add driver name of physical device */ + if (dev->driver) + add_hotplug_env_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVDRIVER=%s", dev->driver->name); + + /* terminate, set to next free slot, shrink available space */ + envp[i] = NULL; + envp = &envp[i]; + num_envp -= i; + buffer = &buffer[length]; + buffer_size -= length; + + if (dev->bus->hotplug) { + /* have the bus specific function add its stuff */ + retval = dev->bus->hotplug (dev, envp, num_envp, buffer, buffer_size); + if (retval) { + pr_debug ("%s - hotplug() returned %d\n", + __FUNCTION__, retval); + } + } + + return retval; +} + +static struct kset_hotplug_ops device_hotplug_ops = { + .filter = dev_hotplug_filter, + .name = dev_hotplug_name, + .hotplug = dev_hotplug, +}; + +/** + * device_subsys - structure to be registered with kobject core. + */ + +decl_subsys(devices, &ktype_device, &device_hotplug_ops); + + +/** + * device_create_file - create sysfs attribute file for device. + * @dev: device. + * @attr: device attribute descriptor. + */ + +int device_create_file(struct device * dev, struct device_attribute * attr) +{ + int error = 0; + if (get_device(dev)) { + error = sysfs_create_file(&dev->kobj, &attr->attr); + put_device(dev); + } + return error; +} + +/** + * device_remove_file - remove sysfs attribute file. + * @dev: device. + * @attr: device attribute descriptor. + */ + +void device_remove_file(struct device * dev, struct device_attribute * attr) +{ + if (get_device(dev)) { + sysfs_remove_file(&dev->kobj, &attr->attr); + put_device(dev); + } +} + + +/** + * device_initialize - init device structure. + * @dev: device. + * + * This prepares the device for use by other layers, + * including adding it to the device hierarchy. + * It is the first half of device_register(), if called by + * that, though it can also be called separately, so one + * may use @dev's fields (e.g. the refcount). + */ + +void device_initialize(struct device *dev) +{ + kobj_set_kset_s(dev, devices_subsys); + kobject_init(&dev->kobj); + INIT_LIST_HEAD(&dev->node); + INIT_LIST_HEAD(&dev->children); + INIT_LIST_HEAD(&dev->driver_list); + INIT_LIST_HEAD(&dev->bus_list); + INIT_LIST_HEAD(&dev->dma_pools); +} + +/** + * device_add - add device to device hierarchy. + * @dev: device. + * + * This is part 2 of device_register(), though may be called + * separately _iff_ device_initialize() has been called separately. + * + * This adds it to the kobject hierarchy via kobject_add(), adds it + * to the global and sibling lists for the device, then + * adds it to the other relevant subsystems of the driver model. + */ +int device_add(struct device *dev) +{ + struct device *parent = NULL; + int error = -EINVAL; + + dev = get_device(dev); + if (!dev || !strlen(dev->bus_id)) + goto Error; + + parent = get_device(dev->parent); + + pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); + + /* first, register with generic layer. */ + kobject_set_name(&dev->kobj, "%s", dev->bus_id); + if (parent) + dev->kobj.parent = &parent->kobj; + + if ((error = kobject_add(&dev->kobj))) + goto Error; + if ((error = device_pm_add(dev))) + goto PMError; + if ((error = bus_add_device(dev))) + goto BusError; + down_write(&devices_subsys.rwsem); + if (parent) + list_add_tail(&dev->node, &parent->children); + up_write(&devices_subsys.rwsem); + + /* notify platform of device entry */ + if (platform_notify) + platform_notify(dev); + Done: + put_device(dev); + return error; + BusError: + device_pm_remove(dev); + PMError: + kobject_del(&dev->kobj); + Error: + if (parent) + put_device(parent); + goto Done; +} + + +/** + * device_register - register a device with the system. + * @dev: pointer to the device structure + * + * This happens in two clean steps - initialize the device + * and add it to the system. The two steps can be called + * separately, but this is the easiest and most common. + * I.e. you should only call the two helpers separately if + * have a clearly defined need to use and refcount the device + * before it is added to the hierarchy. + */ + +int device_register(struct device *dev) +{ + device_initialize(dev); + return device_add(dev); +} + + +/** + * get_device - increment reference count for device. + * @dev: device. + * + * This simply forwards the call to kobject_get(), though + * we do take care to provide for the case that we get a NULL + * pointer passed in. + */ + +struct device * get_device(struct device * dev) +{ + return dev ? to_dev(kobject_get(&dev->kobj)) : NULL; +} + + +/** + * put_device - decrement reference count. + * @dev: device in question. + */ +void put_device(struct device * dev) +{ + if (dev) + kobject_put(&dev->kobj); +} + + +/** + * device_del - delete device from system. + * @dev: device. + * + * This is the first part of the device unregistration + * sequence. This removes the device from the lists we control + * from here, has it removed from the other driver model + * subsystems it was added to in device_add(), and removes it + * from the kobject hierarchy. + * + * NOTE: this should be called manually _iff_ device_add() was + * also called manually. + */ + +void device_del(struct device * dev) +{ + struct device * parent = dev->parent; + + down_write(&devices_subsys.rwsem); + if (parent) + list_del_init(&dev->node); + up_write(&devices_subsys.rwsem); + + /* Notify the platform of the removal, in case they + * need to do anything... + */ + if (platform_notify_remove) + platform_notify_remove(dev); + bus_remove_device(dev); + device_pm_remove(dev); + kobject_del(&dev->kobj); + if (parent) + put_device(parent); +} + +/** + * device_unregister - unregister device from system. + * @dev: device going away. + * + * We do this in two parts, like we do device_register(). First, + * we remove it from all the subsystems with device_del(), then + * we decrement the reference count via put_device(). If that + * is the final reference count, the device will be cleaned up + * via device_release() above. Otherwise, the structure will + * stick around until the final reference to the device is dropped. + */ +void device_unregister(struct device * dev) +{ + pr_debug("DEV: Unregistering device. ID = '%s'\n", dev->bus_id); + device_del(dev); + put_device(dev); +} + + +/** + * device_for_each_child - device child iterator. + * @dev: parent struct device. + * @data: data for the callback. + * @fn: function to be called for each device. + * + * Iterate over @dev's child devices, and call @fn for each, + * passing it @data. + * + * We check the return of @fn each time. If it returns anything + * other than 0, we break out and return that value. + */ +int device_for_each_child(struct device * dev, void * data, + int (*fn)(struct device *, void *)) +{ + struct device * child; + int error = 0; + + down_read(&devices_subsys.rwsem); + list_for_each_entry(child, &dev->children, node) { + if((error = fn(child, data))) + break; + } + up_read(&devices_subsys.rwsem); + return error; +} + +/** + * device_find - locate device on a bus by name. + * @name: name of the device. + * @bus: bus to scan for the device. + * + * Call kset_find_obj() to iterate over list of devices on + * a bus to find device by name. Return device if found. + * + * Note that kset_find_obj increments device's reference count. + */ +struct device *device_find(const char *name, struct bus_type *bus) +{ + struct kobject *k = kset_find_obj(&bus->devices, name); + if (k) + return to_dev(k); + return NULL; +} + +int __init devices_init(void) +{ + return subsystem_register(&devices_subsys); +} + +EXPORT_SYMBOL_GPL(device_for_each_child); + +EXPORT_SYMBOL_GPL(device_initialize); +EXPORT_SYMBOL_GPL(device_add); +EXPORT_SYMBOL_GPL(device_register); + +EXPORT_SYMBOL_GPL(device_del); +EXPORT_SYMBOL_GPL(device_unregister); +EXPORT_SYMBOL_GPL(get_device); +EXPORT_SYMBOL_GPL(put_device); +EXPORT_SYMBOL_GPL(device_find); + +EXPORT_SYMBOL_GPL(device_create_file); +EXPORT_SYMBOL_GPL(device_remove_file); diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c new file mode 100644 index 000000000000..6ef3069b5710 --- /dev/null +++ b/drivers/base/cpu.c @@ -0,0 +1,104 @@ +/* + * drivers/base/cpu.c - basic CPU class support + */ + +#include <linux/sysdev.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/cpu.h> +#include <linux/topology.h> +#include <linux/device.h> + + +struct sysdev_class cpu_sysdev_class = { + set_kset_name("cpu"), +}; +EXPORT_SYMBOL(cpu_sysdev_class); + +#ifdef CONFIG_HOTPLUG_CPU +static ssize_t show_online(struct sys_device *dev, char *buf) +{ + struct cpu *cpu = container_of(dev, struct cpu, sysdev); + + return sprintf(buf, "%u\n", !!cpu_online(cpu->sysdev.id)); +} + +static ssize_t store_online(struct sys_device *dev, const char *buf, + size_t count) +{ + struct cpu *cpu = container_of(dev, struct cpu, sysdev); + ssize_t ret; + + switch (buf[0]) { + case '0': + ret = cpu_down(cpu->sysdev.id); + if (!ret) + kobject_hotplug(&dev->kobj, KOBJ_OFFLINE); + break; + case '1': + ret = cpu_up(cpu->sysdev.id); + break; + default: + ret = -EINVAL; + } + + if (ret >= 0) + ret = count; + return ret; +} +static SYSDEV_ATTR(online, 0600, show_online, store_online); + +static void __devinit register_cpu_control(struct cpu *cpu) +{ + sysdev_create_file(&cpu->sysdev, &attr_online); +} +void unregister_cpu(struct cpu *cpu, struct node *root) +{ + + if (root) + sysfs_remove_link(&root->sysdev.kobj, + kobject_name(&cpu->sysdev.kobj)); + sysdev_remove_file(&cpu->sysdev, &attr_online); + + sysdev_unregister(&cpu->sysdev); + + return; +} +#else /* ... !CONFIG_HOTPLUG_CPU */ +static inline void register_cpu_control(struct cpu *cpu) +{ +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/* + * register_cpu - Setup a driverfs device for a CPU. + * @cpu - Callers can set the cpu->no_control field to 1, to indicate not to + * generate a control file in sysfs for this CPU. + * @num - CPU number to use when creating the device. + * + * Initialize and register the CPU device. + */ +int __devinit register_cpu(struct cpu *cpu, int num, struct node *root) +{ + int error; + + cpu->node_id = cpu_to_node(num); + cpu->sysdev.id = num; + cpu->sysdev.cls = &cpu_sysdev_class; + + error = sysdev_register(&cpu->sysdev); + if (!error && root) + error = sysfs_create_link(&root->sysdev.kobj, + &cpu->sysdev.kobj, + kobject_name(&cpu->sysdev.kobj)); + if (!error && !cpu->no_control) + register_cpu_control(cpu); + return error; +} + + + +int __init cpu_dev_init(void) +{ + return sysdev_class_register(&cpu_sysdev_class); +} diff --git a/drivers/base/dmapool.c b/drivers/base/dmapool.c new file mode 100644 index 000000000000..f48833df61a2 --- /dev/null +++ b/drivers/base/dmapool.c @@ -0,0 +1,414 @@ + +#include <linux/device.h> +#include <linux/mm.h> +#include <asm/io.h> /* Needed for i386 to build */ +#include <asm/scatterlist.h> /* Needed for i386 to build */ +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/slab.h> +#include <linux/module.h> + +/* + * Pool allocator ... wraps the dma_alloc_coherent page allocator, so + * small blocks are easily used by drivers for bus mastering controllers. + * This should probably be sharing the guts of the slab allocator. + */ + +struct dma_pool { /* the pool */ + struct list_head page_list; + spinlock_t lock; + size_t blocks_per_page; + size_t size; + struct device *dev; + size_t allocation; + char name [32]; + wait_queue_head_t waitq; + struct list_head pools; +}; + +struct dma_page { /* cacheable header for 'allocation' bytes */ + struct list_head page_list; + void *vaddr; + dma_addr_t dma; + unsigned in_use; + unsigned long bitmap [0]; +}; + +#define POOL_TIMEOUT_JIFFIES ((100 /* msec */ * HZ) / 1000) +#define POOL_POISON_FREED 0xa7 /* !inuse */ +#define POOL_POISON_ALLOCATED 0xa9 /* !initted */ + +static DECLARE_MUTEX (pools_lock); + +static ssize_t +show_pools (struct device *dev, char *buf) +{ + unsigned temp; + unsigned size; + char *next; + struct dma_page *page; + struct dma_pool *pool; + + next = buf; + size = PAGE_SIZE; + + temp = scnprintf(next, size, "poolinfo - 0.1\n"); + size -= temp; + next += temp; + + down (&pools_lock); + list_for_each_entry(pool, &dev->dma_pools, pools) { + unsigned pages = 0; + unsigned blocks = 0; + + list_for_each_entry(page, &pool->page_list, page_list) { + pages++; + blocks += page->in_use; + } + + /* per-pool info, no real statistics yet */ + temp = scnprintf(next, size, "%-16s %4u %4Zu %4Zu %2u\n", + pool->name, + blocks, pages * pool->blocks_per_page, + pool->size, pages); + size -= temp; + next += temp; + } + up (&pools_lock); + + return PAGE_SIZE - size; +} +static DEVICE_ATTR (pools, S_IRUGO, show_pools, NULL); + +/** + * dma_pool_create - Creates a pool of consistent memory blocks, for dma. + * @name: name of pool, for diagnostics + * @dev: device that will be doing the DMA + * @size: size of the blocks in this pool. + * @align: alignment requirement for blocks; must be a power of two + * @allocation: returned blocks won't cross this boundary (or zero) + * Context: !in_interrupt() + * + * Returns a dma allocation pool with the requested characteristics, or + * null if one can't be created. Given one of these pools, dma_pool_alloc() + * may be used to allocate memory. Such memory will all have "consistent" + * DMA mappings, accessible by the device and its driver without using + * cache flushing primitives. The actual size of blocks allocated may be + * larger than requested because of alignment. + * + * If allocation is nonzero, objects returned from dma_pool_alloc() won't + * cross that size boundary. This is useful for devices which have + * addressing restrictions on individual DMA transfers, such as not crossing + * boundaries of 4KBytes. + */ +struct dma_pool * +dma_pool_create (const char *name, struct device *dev, + size_t size, size_t align, size_t allocation) +{ + struct dma_pool *retval; + + if (align == 0) + align = 1; + if (size == 0) + return NULL; + else if (size < align) + size = align; + else if ((size % align) != 0) { + size += align + 1; + size &= ~(align - 1); + } + + if (allocation == 0) { + if (PAGE_SIZE < size) + allocation = size; + else + allocation = PAGE_SIZE; + // FIXME: round up for less fragmentation + } else if (allocation < size) + return NULL; + + if (!(retval = kmalloc (sizeof *retval, SLAB_KERNEL))) + return retval; + + strlcpy (retval->name, name, sizeof retval->name); + + retval->dev = dev; + + INIT_LIST_HEAD (&retval->page_list); + spin_lock_init (&retval->lock); + retval->size = size; + retval->allocation = allocation; + retval->blocks_per_page = allocation / size; + init_waitqueue_head (&retval->waitq); + + if (dev) { + down (&pools_lock); + if (list_empty (&dev->dma_pools)) + device_create_file (dev, &dev_attr_pools); + /* note: not currently insisting "name" be unique */ + list_add (&retval->pools, &dev->dma_pools); + up (&pools_lock); + } else + INIT_LIST_HEAD (&retval->pools); + + return retval; +} + + +static struct dma_page * +pool_alloc_page (struct dma_pool *pool, unsigned int __nocast mem_flags) +{ + struct dma_page *page; + int mapsize; + + mapsize = pool->blocks_per_page; + mapsize = (mapsize + BITS_PER_LONG - 1) / BITS_PER_LONG; + mapsize *= sizeof (long); + + page = (struct dma_page *) kmalloc (mapsize + sizeof *page, mem_flags); + if (!page) + return NULL; + page->vaddr = dma_alloc_coherent (pool->dev, + pool->allocation, + &page->dma, + mem_flags); + if (page->vaddr) { + memset (page->bitmap, 0xff, mapsize); // bit set == free +#ifdef CONFIG_DEBUG_SLAB + memset (page->vaddr, POOL_POISON_FREED, pool->allocation); +#endif + list_add (&page->page_list, &pool->page_list); + page->in_use = 0; + } else { + kfree (page); + page = NULL; + } + return page; +} + + +static inline int +is_page_busy (int blocks, unsigned long *bitmap) +{ + while (blocks > 0) { + if (*bitmap++ != ~0UL) + return 1; + blocks -= BITS_PER_LONG; + } + return 0; +} + +static void +pool_free_page (struct dma_pool *pool, struct dma_page *page) +{ + dma_addr_t dma = page->dma; + +#ifdef CONFIG_DEBUG_SLAB + memset (page->vaddr, POOL_POISON_FREED, pool->allocation); +#endif + dma_free_coherent (pool->dev, pool->allocation, page->vaddr, dma); + list_del (&page->page_list); + kfree (page); +} + + +/** + * dma_pool_destroy - destroys a pool of dma memory blocks. + * @pool: dma pool that will be destroyed + * Context: !in_interrupt() + * + * Caller guarantees that no more memory from the pool is in use, + * and that nothing will try to use the pool after this call. + */ +void +dma_pool_destroy (struct dma_pool *pool) +{ + down (&pools_lock); + list_del (&pool->pools); + if (pool->dev && list_empty (&pool->dev->dma_pools)) + device_remove_file (pool->dev, &dev_attr_pools); + up (&pools_lock); + + while (!list_empty (&pool->page_list)) { + struct dma_page *page; + page = list_entry (pool->page_list.next, + struct dma_page, page_list); + if (is_page_busy (pool->blocks_per_page, page->bitmap)) { + if (pool->dev) + dev_err(pool->dev, "dma_pool_destroy %s, %p busy\n", + pool->name, page->vaddr); + else + printk (KERN_ERR "dma_pool_destroy %s, %p busy\n", + pool->name, page->vaddr); + /* leak the still-in-use consistent memory */ + list_del (&page->page_list); + kfree (page); + } else + pool_free_page (pool, page); + } + + kfree (pool); +} + + +/** + * dma_pool_alloc - get a block of consistent memory + * @pool: dma pool that will produce the block + * @mem_flags: GFP_* bitmask + * @handle: pointer to dma address of block + * + * This returns the kernel virtual address of a currently unused block, + * and reports its dma address through the handle. + * If such a memory block can't be allocated, null is returned. + */ +void * +dma_pool_alloc (struct dma_pool *pool, int mem_flags, dma_addr_t *handle) +{ + unsigned long flags; + struct dma_page *page; + int map, block; + size_t offset; + void *retval; + +restart: + spin_lock_irqsave (&pool->lock, flags); + list_for_each_entry(page, &pool->page_list, page_list) { + int i; + /* only cachable accesses here ... */ + for (map = 0, i = 0; + i < pool->blocks_per_page; + i += BITS_PER_LONG, map++) { + if (page->bitmap [map] == 0) + continue; + block = ffz (~ page->bitmap [map]); + if ((i + block) < pool->blocks_per_page) { + clear_bit (block, &page->bitmap [map]); + offset = (BITS_PER_LONG * map) + block; + offset *= pool->size; + goto ready; + } + } + } + if (!(page = pool_alloc_page (pool, SLAB_ATOMIC))) { + if (mem_flags & __GFP_WAIT) { + DECLARE_WAITQUEUE (wait, current); + + current->state = TASK_INTERRUPTIBLE; + add_wait_queue (&pool->waitq, &wait); + spin_unlock_irqrestore (&pool->lock, flags); + + schedule_timeout (POOL_TIMEOUT_JIFFIES); + + remove_wait_queue (&pool->waitq, &wait); + goto restart; + } + retval = NULL; + goto done; + } + + clear_bit (0, &page->bitmap [0]); + offset = 0; +ready: + page->in_use++; + retval = offset + page->vaddr; + *handle = offset + page->dma; +#ifdef CONFIG_DEBUG_SLAB + memset (retval, POOL_POISON_ALLOCATED, pool->size); +#endif +done: + spin_unlock_irqrestore (&pool->lock, flags); + return retval; +} + + +static struct dma_page * +pool_find_page (struct dma_pool *pool, dma_addr_t dma) +{ + unsigned long flags; + struct dma_page *page; + + spin_lock_irqsave (&pool->lock, flags); + list_for_each_entry(page, &pool->page_list, page_list) { + if (dma < page->dma) + continue; + if (dma < (page->dma + pool->allocation)) + goto done; + } + page = NULL; +done: + spin_unlock_irqrestore (&pool->lock, flags); + return page; +} + + +/** + * dma_pool_free - put block back into dma pool + * @pool: the dma pool holding the block + * @vaddr: virtual address of block + * @dma: dma address of block + * + * Caller promises neither device nor driver will again touch this block + * unless it is first re-allocated. + */ +void +dma_pool_free (struct dma_pool *pool, void *vaddr, dma_addr_t dma) +{ + struct dma_page *page; + unsigned long flags; + int map, block; + + if ((page = pool_find_page (pool, dma)) == 0) { + if (pool->dev) + dev_err(pool->dev, "dma_pool_free %s, %p/%lx (bad dma)\n", + pool->name, vaddr, (unsigned long) dma); + else + printk (KERN_ERR "dma_pool_free %s, %p/%lx (bad dma)\n", + pool->name, vaddr, (unsigned long) dma); + return; + } + + block = dma - page->dma; + block /= pool->size; + map = block / BITS_PER_LONG; + block %= BITS_PER_LONG; + +#ifdef CONFIG_DEBUG_SLAB + if (((dma - page->dma) + (void *)page->vaddr) != vaddr) { + if (pool->dev) + dev_err(pool->dev, "dma_pool_free %s, %p (bad vaddr)/%Lx\n", + pool->name, vaddr, (unsigned long long) dma); + else + printk (KERN_ERR "dma_pool_free %s, %p (bad vaddr)/%Lx\n", + pool->name, vaddr, (unsigned long long) dma); + return; + } + if (page->bitmap [map] & (1UL << block)) { + if (pool->dev) + dev_err(pool->dev, "dma_pool_free %s, dma %Lx already free\n", + pool->name, (unsigned long long)dma); + else + printk (KERN_ERR "dma_pool_free %s, dma %Lx already free\n", + pool->name, (unsigned long long)dma); + return; + } + memset (vaddr, POOL_POISON_FREED, pool->size); +#endif + + spin_lock_irqsave (&pool->lock, flags); + page->in_use--; + set_bit (block, &page->bitmap [map]); + if (waitqueue_active (&pool->waitq)) + wake_up (&pool->waitq); + /* + * Resist a temptation to do + * if (!is_page_busy(bpp, page->bitmap)) pool_free_page(pool, page); + * Better have a few empty pages hang around. + */ + spin_unlock_irqrestore (&pool->lock, flags); +} + + +EXPORT_SYMBOL (dma_pool_create); +EXPORT_SYMBOL (dma_pool_destroy); +EXPORT_SYMBOL (dma_pool_alloc); +EXPORT_SYMBOL (dma_pool_free); diff --git a/drivers/base/driver.c b/drivers/base/driver.c new file mode 100644 index 000000000000..3b269f7e5213 --- /dev/null +++ b/drivers/base/driver.c @@ -0,0 +1,138 @@ +/* + * driver.c - centralized device driver management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/string.h> +#include "base.h" + +#define to_dev(node) container_of(node, struct device, driver_list) +#define to_drv(obj) container_of(obj, struct device_driver, kobj) + +/** + * driver_create_file - create sysfs file for driver. + * @drv: driver. + * @attr: driver attribute descriptor. + */ + +int driver_create_file(struct device_driver * drv, struct driver_attribute * attr) +{ + int error; + if (get_driver(drv)) { + error = sysfs_create_file(&drv->kobj, &attr->attr); + put_driver(drv); + } else + error = -EINVAL; + return error; +} + + +/** + * driver_remove_file - remove sysfs file for driver. + * @drv: driver. + * @attr: driver attribute descriptor. + */ + +void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr) +{ + if (get_driver(drv)) { + sysfs_remove_file(&drv->kobj, &attr->attr); + put_driver(drv); + } +} + + +/** + * get_driver - increment driver reference count. + * @drv: driver. + */ +struct device_driver * get_driver(struct device_driver * drv) +{ + return drv ? to_drv(kobject_get(&drv->kobj)) : NULL; +} + + +/** + * put_driver - decrement driver's refcount. + * @drv: driver. + */ +void put_driver(struct device_driver * drv) +{ + kobject_put(&drv->kobj); +} + + +/** + * driver_register - register driver with bus + * @drv: driver to register + * + * We pass off most of the work to the bus_add_driver() call, + * since most of the things we have to do deal with the bus + * structures. + * + * The one interesting aspect is that we setup @drv->unloaded + * as a completion that gets complete when the driver reference + * count reaches 0. + */ +int driver_register(struct device_driver * drv) +{ + INIT_LIST_HEAD(&drv->devices); + init_completion(&drv->unloaded); + return bus_add_driver(drv); +} + + +/** + * driver_unregister - remove driver from system. + * @drv: driver. + * + * Again, we pass off most of the work to the bus-level call. + * + * Though, once that is done, we wait until @drv->unloaded is completed. + * This will block until the driver refcount reaches 0, and it is + * released. Only modular drivers will call this function, and we + * have to guarantee that it won't complete, letting the driver + * unload until all references are gone. + */ + +void driver_unregister(struct device_driver * drv) +{ + bus_remove_driver(drv); + wait_for_completion(&drv->unloaded); +} + +/** + * driver_find - locate driver on a bus by its name. + * @name: name of the driver. + * @bus: bus to scan for the driver. + * + * Call kset_find_obj() to iterate over list of drivers on + * a bus to find driver by name. Return driver if found. + * + * Note that kset_find_obj increments driver's reference count. + */ +struct device_driver *driver_find(const char *name, struct bus_type *bus) +{ + struct kobject *k = kset_find_obj(&bus->drivers, name); + if (k) + return to_drv(k); + return NULL; +} + +EXPORT_SYMBOL_GPL(driver_register); +EXPORT_SYMBOL_GPL(driver_unregister); +EXPORT_SYMBOL_GPL(get_driver); +EXPORT_SYMBOL_GPL(put_driver); +EXPORT_SYMBOL_GPL(driver_find); + +EXPORT_SYMBOL_GPL(driver_create_file); +EXPORT_SYMBOL_GPL(driver_remove_file); diff --git a/drivers/base/firmware.c b/drivers/base/firmware.c new file mode 100644 index 000000000000..88ab044932f2 --- /dev/null +++ b/drivers/base/firmware.c @@ -0,0 +1,34 @@ +/* + * firmware.c - firmware subsystem hoohaw. + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/init.h> + +static decl_subsys(firmware, NULL, NULL); + +int firmware_register(struct subsystem * s) +{ + kset_set_kset_s(s, firmware_subsys); + return subsystem_register(s); +} + +void firmware_unregister(struct subsystem * s) +{ + subsystem_unregister(s); +} + +int __init firmware_init(void) +{ + return subsystem_register(&firmware_subsys); +} + +EXPORT_SYMBOL_GPL(firmware_register); +EXPORT_SYMBOL_GPL(firmware_unregister); diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c new file mode 100644 index 000000000000..26c9464af80a --- /dev/null +++ b/drivers/base/firmware_class.c @@ -0,0 +1,583 @@ +/* + * firmware_class.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org> + * + * Please see Documentation/firmware_class/ for more information. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <asm/semaphore.h> + +#include <linux/firmware.h> +#include "base.h" + +MODULE_AUTHOR("Manuel Estrada Sainz <ranty@debian.org>"); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +enum { + FW_STATUS_LOADING, + FW_STATUS_DONE, + FW_STATUS_ABORT, + FW_STATUS_READY, +}; + +static int loading_timeout = 10; /* In seconds */ + +/* fw_lock could be moved to 'struct firmware_priv' but since it is just + * guarding for corner cases a global lock should be OK */ +static DECLARE_MUTEX(fw_lock); + +struct firmware_priv { + char fw_id[FIRMWARE_NAME_MAX]; + struct completion completion; + struct bin_attribute attr_data; + struct firmware *fw; + unsigned long status; + int alloc_size; + struct timer_list timeout; +}; + +static inline void +fw_load_abort(struct firmware_priv *fw_priv) +{ + set_bit(FW_STATUS_ABORT, &fw_priv->status); + wmb(); + complete(&fw_priv->completion); +} + +static ssize_t +firmware_timeout_show(struct class *class, char *buf) +{ + return sprintf(buf, "%d\n", loading_timeout); +} + +/** + * firmware_timeout_store: + * Description: + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be return to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait for ever' + * + **/ +static ssize_t +firmware_timeout_store(struct class *class, const char *buf, size_t count) +{ + loading_timeout = simple_strtol(buf, NULL, 10); + return count; +} + +static CLASS_ATTR(timeout, 0644, firmware_timeout_show, firmware_timeout_store); + +static void fw_class_dev_release(struct class_device *class_dev); +int firmware_class_hotplug(struct class_device *dev, char **envp, + int num_envp, char *buffer, int buffer_size); + +static struct class firmware_class = { + .name = "firmware", + .hotplug = firmware_class_hotplug, + .release = fw_class_dev_release, +}; + +int +firmware_class_hotplug(struct class_device *class_dev, char **envp, + int num_envp, char *buffer, int buffer_size) +{ + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + int i = 0, len = 0; + + if (!test_bit(FW_STATUS_READY, &fw_priv->status)) + return -ENODEV; + + if (add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, &len, + "FIRMWARE=%s", fw_priv->fw_id)) + return -ENOMEM; + + envp[i] = NULL; + + return 0; +} + +static ssize_t +firmware_loading_show(struct class_device *class_dev, char *buf) +{ + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status); + return sprintf(buf, "%d\n", loading); +} + +/** + * firmware_loading_store: - loading control file + * Description: + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and handle the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static ssize_t +firmware_loading_store(struct class_device *class_dev, + const char *buf, size_t count) +{ + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + int loading = simple_strtol(buf, NULL, 10); + + switch (loading) { + case 1: + down(&fw_lock); + vfree(fw_priv->fw->data); + fw_priv->fw->data = NULL; + fw_priv->fw->size = 0; + fw_priv->alloc_size = 0; + set_bit(FW_STATUS_LOADING, &fw_priv->status); + up(&fw_lock); + break; + case 0: + if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { + complete(&fw_priv->completion); + clear_bit(FW_STATUS_LOADING, &fw_priv->status); + break; + } + /* fallthrough */ + default: + printk(KERN_ERR "%s: unexpected value (%d)\n", __FUNCTION__, + loading); + /* fallthrough */ + case -1: + fw_load_abort(fw_priv); + break; + } + + return count; +} + +static CLASS_DEVICE_ATTR(loading, 0644, + firmware_loading_show, firmware_loading_store); + +static ssize_t +firmware_data_read(struct kobject *kobj, + char *buffer, loff_t offset, size_t count) +{ + struct class_device *class_dev = to_class_dev(kobj); + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + struct firmware *fw; + ssize_t ret_count = count; + + down(&fw_lock); + fw = fw_priv->fw; + if (test_bit(FW_STATUS_DONE, &fw_priv->status)) { + ret_count = -ENODEV; + goto out; + } + if (offset > fw->size) { + ret_count = 0; + goto out; + } + if (offset + ret_count > fw->size) + ret_count = fw->size - offset; + + memcpy(buffer, fw->data + offset, ret_count); +out: + up(&fw_lock); + return ret_count; +} +static int +fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) +{ + u8 *new_data; + + if (min_size <= fw_priv->alloc_size) + return 0; + + new_data = vmalloc(fw_priv->alloc_size + PAGE_SIZE); + if (!new_data) { + printk(KERN_ERR "%s: unable to alloc buffer\n", __FUNCTION__); + /* Make sure that we don't keep incomplete data */ + fw_load_abort(fw_priv); + return -ENOMEM; + } + fw_priv->alloc_size += PAGE_SIZE; + if (fw_priv->fw->data) { + memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); + vfree(fw_priv->fw->data); + } + fw_priv->fw->data = new_data; + BUG_ON(min_size > fw_priv->alloc_size); + return 0; +} + +/** + * firmware_data_write: + * + * Description: + * + * Data written to the 'data' attribute will be later handled to + * the driver as a firmware image. + **/ +static ssize_t +firmware_data_write(struct kobject *kobj, + char *buffer, loff_t offset, size_t count) +{ + struct class_device *class_dev = to_class_dev(kobj); + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + struct firmware *fw; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + down(&fw_lock); + fw = fw_priv->fw; + if (test_bit(FW_STATUS_DONE, &fw_priv->status)) { + retval = -ENODEV; + goto out; + } + retval = fw_realloc_buffer(fw_priv, offset + count); + if (retval) + goto out; + + memcpy(fw->data + offset, buffer, count); + + fw->size = max_t(size_t, offset + count, fw->size); + retval = count; +out: + up(&fw_lock); + return retval; +} +static struct bin_attribute firmware_attr_data_tmpl = { + .attr = {.name = "data", .mode = 0644, .owner = THIS_MODULE}, + .size = 0, + .read = firmware_data_read, + .write = firmware_data_write, +}; + +static void +fw_class_dev_release(struct class_device *class_dev) +{ + struct firmware_priv *fw_priv = class_get_devdata(class_dev); + + kfree(fw_priv); + kfree(class_dev); + + module_put(THIS_MODULE); +} + +static void +firmware_class_timeout(u_long data) +{ + struct firmware_priv *fw_priv = (struct firmware_priv *) data; + fw_load_abort(fw_priv); +} + +static inline void +fw_setup_class_device_id(struct class_device *class_dev, struct device *dev) +{ + /* XXX warning we should watch out for name collisions */ + strlcpy(class_dev->class_id, dev->bus_id, BUS_ID_SIZE); +} + +static int +fw_register_class_device(struct class_device **class_dev_p, + const char *fw_name, struct device *device) +{ + int retval; + struct firmware_priv *fw_priv = kmalloc(sizeof (struct firmware_priv), + GFP_KERNEL); + struct class_device *class_dev = kmalloc(sizeof (struct class_device), + GFP_KERNEL); + + *class_dev_p = NULL; + + if (!fw_priv || !class_dev) { + printk(KERN_ERR "%s: kmalloc failed\n", __FUNCTION__); + retval = -ENOMEM; + goto error_kfree; + } + memset(fw_priv, 0, sizeof (*fw_priv)); + memset(class_dev, 0, sizeof (*class_dev)); + + init_completion(&fw_priv->completion); + fw_priv->attr_data = firmware_attr_data_tmpl; + strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX); + + fw_priv->timeout.function = firmware_class_timeout; + fw_priv->timeout.data = (u_long) fw_priv; + init_timer(&fw_priv->timeout); + + fw_setup_class_device_id(class_dev, device); + class_dev->dev = device; + class_dev->class = &firmware_class; + class_set_devdata(class_dev, fw_priv); + retval = class_device_register(class_dev); + if (retval) { + printk(KERN_ERR "%s: class_device_register failed\n", + __FUNCTION__); + goto error_kfree; + } + *class_dev_p = class_dev; + return 0; + +error_kfree: + kfree(fw_priv); + kfree(class_dev); + return retval; +} + +static int +fw_setup_class_device(struct firmware *fw, struct class_device **class_dev_p, + const char *fw_name, struct device *device) +{ + struct class_device *class_dev; + struct firmware_priv *fw_priv; + int retval; + + *class_dev_p = NULL; + retval = fw_register_class_device(&class_dev, fw_name, device); + if (retval) + goto out; + + /* Need to pin this module until class device is destroyed */ + __module_get(THIS_MODULE); + + fw_priv = class_get_devdata(class_dev); + + fw_priv->fw = fw; + retval = sysfs_create_bin_file(&class_dev->kobj, &fw_priv->attr_data); + if (retval) { + printk(KERN_ERR "%s: sysfs_create_bin_file failed\n", + __FUNCTION__); + goto error_unreg; + } + + retval = class_device_create_file(class_dev, + &class_device_attr_loading); + if (retval) { + printk(KERN_ERR "%s: class_device_create_file failed\n", + __FUNCTION__); + goto error_unreg; + } + + set_bit(FW_STATUS_READY, &fw_priv->status); + *class_dev_p = class_dev; + goto out; + +error_unreg: + class_device_unregister(class_dev); +out: + return retval; +} + +/** + * request_firmware: - request firmware to hotplug and wait for it + * Description: + * @firmware will be used to return a firmware image by the name + * of @name for device @device. + * + * Should be called from user context where sleeping is allowed. + * + * @name will be use as $FIRMWARE in the hotplug environment and + * should be distinctive enough not to be confused with any other + * firmware image for this or any other device. + **/ +int +request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device) +{ + struct class_device *class_dev; + struct firmware_priv *fw_priv; + struct firmware *firmware; + int retval; + + if (!firmware_p) + return -EINVAL; + + *firmware_p = firmware = kmalloc(sizeof (struct firmware), GFP_KERNEL); + if (!firmware) { + printk(KERN_ERR "%s: kmalloc(struct firmware) failed\n", + __FUNCTION__); + retval = -ENOMEM; + goto out; + } + memset(firmware, 0, sizeof (*firmware)); + + retval = fw_setup_class_device(firmware, &class_dev, name, device); + if (retval) + goto error_kfree_fw; + + fw_priv = class_get_devdata(class_dev); + + if (loading_timeout) { + fw_priv->timeout.expires = jiffies + loading_timeout * HZ; + add_timer(&fw_priv->timeout); + } + + kobject_hotplug(&class_dev->kobj, KOBJ_ADD); + wait_for_completion(&fw_priv->completion); + set_bit(FW_STATUS_DONE, &fw_priv->status); + + del_timer_sync(&fw_priv->timeout); + + down(&fw_lock); + if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) { + retval = -ENOENT; + release_firmware(fw_priv->fw); + *firmware_p = NULL; + } + fw_priv->fw = NULL; + up(&fw_lock); + class_device_unregister(class_dev); + goto out; + +error_kfree_fw: + kfree(firmware); + *firmware_p = NULL; +out: + return retval; +} + +/** + * release_firmware: - release the resource associated with a firmware image + **/ +void +release_firmware(const struct firmware *fw) +{ + if (fw) { + vfree(fw->data); + kfree(fw); + } +} + +/** + * register_firmware: - provide a firmware image for later usage + * + * Description: + * Make sure that @data will be available by requesting firmware @name. + * + * Note: This will not be possible until some kind of persistence + * is available. + **/ +void +register_firmware(const char *name, const u8 *data, size_t size) +{ + /* This is meaningless without firmware caching, so until we + * decide if firmware caching is reasonable just leave it as a + * noop */ +} + +/* Async support */ +struct firmware_work { + struct work_struct work; + struct module *module; + const char *name; + struct device *device; + void *context; + void (*cont)(const struct firmware *fw, void *context); +}; + +static int +request_firmware_work_func(void *arg) +{ + struct firmware_work *fw_work = arg; + const struct firmware *fw; + if (!arg) { + WARN_ON(1); + return 0; + } + daemonize("%s/%s", "firmware", fw_work->name); + request_firmware(&fw, fw_work->name, fw_work->device); + fw_work->cont(fw, fw_work->context); + release_firmware(fw); + module_put(fw_work->module); + kfree(fw_work); + return 0; +} + +/** + * request_firmware_nowait: + * + * Description: + * Asynchronous variant of request_firmware() for contexts where + * it is not possible to sleep. + * + * @cont will be called asynchronously when the firmware request is over. + * + * @context will be passed over to @cont. + * + * @fw may be %NULL if firmware request fails. + * + **/ +int +request_firmware_nowait( + struct module *module, + const char *name, struct device *device, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work), + GFP_ATOMIC); + int ret; + + if (!fw_work) + return -ENOMEM; + if (!try_module_get(module)) { + kfree(fw_work); + return -EFAULT; + } + + *fw_work = (struct firmware_work) { + .module = module, + .name = name, + .device = device, + .context = context, + .cont = cont, + }; + + ret = kernel_thread(request_firmware_work_func, fw_work, + CLONE_FS | CLONE_FILES); + + if (ret < 0) { + fw_work->cont(NULL, fw_work->context); + return ret; + } + return 0; +} + +static int __init +firmware_class_init(void) +{ + int error; + error = class_register(&firmware_class); + if (error) { + printk(KERN_ERR "%s: class_register failed\n", __FUNCTION__); + return error; + } + error = class_create_file(&firmware_class, &class_attr_timeout); + if (error) { + printk(KERN_ERR "%s: class_create_file failed\n", + __FUNCTION__); + class_unregister(&firmware_class); + } + return error; + +} +static void __exit +firmware_class_exit(void) +{ + class_unregister(&firmware_class); +} + +module_init(firmware_class_init); +module_exit(firmware_class_exit); + +EXPORT_SYMBOL(release_firmware); +EXPORT_SYMBOL(request_firmware); +EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL(register_firmware); diff --git a/drivers/base/init.c b/drivers/base/init.c new file mode 100644 index 000000000000..a76ae5a221f3 --- /dev/null +++ b/drivers/base/init.c @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include <linux/init.h> + +extern int devices_init(void); +extern int buses_init(void); +extern int classes_init(void); +extern int firmware_init(void); +extern int platform_bus_init(void); +extern int system_bus_init(void); +extern int cpu_dev_init(void); +extern int attribute_container_init(void); +/** + * driver_init - initialize driver model. + * + * Call the driver model init functions to initialize their + * subsystems. Called early from init/main.c. + */ + +void __init driver_init(void) +{ + /* These are the core pieces */ + devices_init(); + buses_init(); + classes_init(); + firmware_init(); + + /* These are also core pieces, but must come after the + * core core pieces. + */ + platform_bus_init(); + system_bus_init(); + cpu_dev_init(); + attribute_container_init(); +} diff --git a/drivers/base/interface.c b/drivers/base/interface.c new file mode 100644 index 000000000000..bd515843a0cb --- /dev/null +++ b/drivers/base/interface.c @@ -0,0 +1,51 @@ +/* + * drivers/base/interface.c - common driverfs interface that's exported to + * the world for all devices. + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/stat.h> +#include <linux/string.h> + +/** + * detach_state - control the default power state for the device. + * + * This is the state the device enters when it's driver module is + * unloaded. The value is an unsigned integer, in the range of 0-4. + * '0' indicates 'On', so no action will be taken when the driver is + * unloaded. This is the default behavior. + * '4' indicates 'Off', meaning the driver core will call the driver's + * shutdown method to quiesce the device. + * 1-3 indicate a low-power state for the device to enter via the + * driver's suspend method. + */ + +static ssize_t detach_show(struct device * dev, char * buf) +{ + return sprintf(buf, "%u\n", dev->detach_state); +} + +static ssize_t detach_store(struct device * dev, const char * buf, size_t n) +{ + u32 state; + state = simple_strtoul(buf, NULL, 10); + if (state > 4) + return -EINVAL; + dev->detach_state = state; + return n; +} + +static DEVICE_ATTR(detach_state, 0644, detach_show, detach_store); + + +struct attribute * dev_default_attrs[] = { + &dev_attr_detach_state.attr, + NULL, +}; diff --git a/drivers/base/map.c b/drivers/base/map.c new file mode 100644 index 000000000000..2f455d86793c --- /dev/null +++ b/drivers/base/map.c @@ -0,0 +1,155 @@ +/* + * linux/drivers/base/map.c + * + * (C) Copyright Al Viro 2002,2003 + * Released under GPL v2. + * + * NOTE: data structure needs to be changed. It works, but for large dev_t + * it will be too slow. It is isolated, though, so these changes will be + * local to that file. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kdev_t.h> +#include <linux/kobject.h> +#include <linux/kobj_map.h> + +struct kobj_map { + struct probe { + struct probe *next; + dev_t dev; + unsigned long range; + struct module *owner; + kobj_probe_t *get; + int (*lock)(dev_t, void *); + void *data; + } *probes[255]; + struct semaphore *sem; +}; + +int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, + struct module *module, kobj_probe_t *probe, + int (*lock)(dev_t, void *), void *data) +{ + unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; + unsigned index = MAJOR(dev); + unsigned i; + struct probe *p; + + if (n > 255) + n = 255; + + p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); + + if (p == NULL) + return -ENOMEM; + + for (i = 0; i < n; i++, p++) { + p->owner = module; + p->get = probe; + p->lock = lock; + p->dev = dev; + p->range = range; + p->data = data; + } + down(domain->sem); + for (i = 0, p -= n; i < n; i++, p++, index++) { + struct probe **s = &domain->probes[index % 255]; + while (*s && (*s)->range < range) + s = &(*s)->next; + p->next = *s; + *s = p; + } + up(domain->sem); + return 0; +} + +void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range) +{ + unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; + unsigned index = MAJOR(dev); + unsigned i; + struct probe *found = NULL; + + if (n > 255) + n = 255; + + down(domain->sem); + for (i = 0; i < n; i++, index++) { + struct probe **s; + for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) { + struct probe *p = *s; + if (p->dev == dev && p->range == range) { + *s = p->next; + if (!found) + found = p; + break; + } + } + } + up(domain->sem); + kfree(found); +} + +struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) +{ + struct kobject *kobj; + struct probe *p; + unsigned long best = ~0UL; + +retry: + down(domain->sem); + for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { + struct kobject *(*probe)(dev_t, int *, void *); + struct module *owner; + void *data; + + if (p->dev > dev || p->dev + p->range - 1 < dev) + continue; + if (p->range - 1 >= best) + break; + if (!try_module_get(p->owner)) + continue; + owner = p->owner; + data = p->data; + probe = p->get; + best = p->range - 1; + *index = dev - p->dev; + if (p->lock && p->lock(dev, data) < 0) { + module_put(owner); + continue; + } + up(domain->sem); + kobj = probe(dev, index, data); + /* Currently ->owner protects _only_ ->probe() itself. */ + module_put(owner); + if (kobj) + return kobj; + goto retry; + } + up(domain->sem); + return NULL; +} + +struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct semaphore *sem) +{ + struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL); + struct probe *base = kmalloc(sizeof(struct probe), GFP_KERNEL); + int i; + + if ((p == NULL) || (base == NULL)) { + kfree(p); + kfree(base); + return NULL; + } + + memset(base, 0, sizeof(struct probe)); + base->dev = 1; + base->range = ~0; + base->get = base_probe; + for (i = 0; i < 255; i++) + p->probes[i] = base; + p->sem = sem; + return p; +} diff --git a/drivers/base/node.c b/drivers/base/node.c new file mode 100644 index 000000000000..583d57ec49a8 --- /dev/null +++ b/drivers/base/node.c @@ -0,0 +1,161 @@ +/* + * drivers/base/node.c - basic Node class support + */ + +#include <linux/sysdev.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/node.h> +#include <linux/hugetlb.h> +#include <linux/cpumask.h> +#include <linux/topology.h> +#include <linux/nodemask.h> + +static struct sysdev_class node_class = { + set_kset_name("node"), +}; + + +static ssize_t node_read_cpumap(struct sys_device * dev, char * buf) +{ + struct node *node_dev = to_node(dev); + cpumask_t mask = node_to_cpumask(node_dev->sysdev.id); + int len; + + /* 2004/06/03: buf currently PAGE_SIZE, need > 1 char per 4 bits. */ + BUILD_BUG_ON(MAX_NUMNODES/4 > PAGE_SIZE/2); + + len = cpumask_scnprintf(buf, PAGE_SIZE-1, mask); + len += sprintf(buf + len, "\n"); + return len; +} + +static SYSDEV_ATTR(cpumap, S_IRUGO, node_read_cpumap, NULL); + +#define K(x) ((x) << (PAGE_SHIFT - 10)) +static ssize_t node_read_meminfo(struct sys_device * dev, char * buf) +{ + int n; + int nid = dev->id; + struct sysinfo i; + unsigned long inactive; + unsigned long active; + unsigned long free; + + si_meminfo_node(&i, nid); + __get_zone_counts(&active, &inactive, &free, NODE_DATA(nid)); + + n = sprintf(buf, "\n" + "Node %d MemTotal: %8lu kB\n" + "Node %d MemFree: %8lu kB\n" + "Node %d MemUsed: %8lu kB\n" + "Node %d Active: %8lu kB\n" + "Node %d Inactive: %8lu kB\n" + "Node %d HighTotal: %8lu kB\n" + "Node %d HighFree: %8lu kB\n" + "Node %d LowTotal: %8lu kB\n" + "Node %d LowFree: %8lu kB\n", + nid, K(i.totalram), + nid, K(i.freeram), + nid, K(i.totalram - i.freeram), + nid, K(active), + nid, K(inactive), + nid, K(i.totalhigh), + nid, K(i.freehigh), + nid, K(i.totalram - i.totalhigh), + nid, K(i.freeram - i.freehigh)); + n += hugetlb_report_node_meminfo(nid, buf + n); + return n; +} + +#undef K +static SYSDEV_ATTR(meminfo, S_IRUGO, node_read_meminfo, NULL); + +static ssize_t node_read_numastat(struct sys_device * dev, char * buf) +{ + unsigned long numa_hit, numa_miss, interleave_hit, numa_foreign; + unsigned long local_node, other_node; + int i, cpu; + pg_data_t *pg = NODE_DATA(dev->id); + numa_hit = 0; + numa_miss = 0; + interleave_hit = 0; + numa_foreign = 0; + local_node = 0; + other_node = 0; + for (i = 0; i < MAX_NR_ZONES; i++) { + struct zone *z = &pg->node_zones[i]; + for (cpu = 0; cpu < NR_CPUS; cpu++) { + struct per_cpu_pageset *ps = &z->pageset[cpu]; + numa_hit += ps->numa_hit; + numa_miss += ps->numa_miss; + numa_foreign += ps->numa_foreign; + interleave_hit += ps->interleave_hit; + local_node += ps->local_node; + other_node += ps->other_node; + } + } + return sprintf(buf, + "numa_hit %lu\n" + "numa_miss %lu\n" + "numa_foreign %lu\n" + "interleave_hit %lu\n" + "local_node %lu\n" + "other_node %lu\n", + numa_hit, + numa_miss, + numa_foreign, + interleave_hit, + local_node, + other_node); +} +static SYSDEV_ATTR(numastat, S_IRUGO, node_read_numastat, NULL); + +static ssize_t node_read_distance(struct sys_device * dev, char * buf) +{ + int nid = dev->id; + int len = 0; + int i; + + /* buf currently PAGE_SIZE, need ~4 chars per node */ + BUILD_BUG_ON(MAX_NUMNODES*4 > PAGE_SIZE/2); + + for_each_online_node(i) + len += sprintf(buf + len, "%s%d", i ? " " : "", node_distance(nid, i)); + + len += sprintf(buf + len, "\n"); + return len; +} +static SYSDEV_ATTR(distance, S_IRUGO, node_read_distance, NULL); + + +/* + * register_node - Setup a driverfs device for a node. + * @num - Node number to use when creating the device. + * + * Initialize and register the node device. + */ +int __init register_node(struct node *node, int num, struct node *parent) +{ + int error; + + node->sysdev.id = num; + node->sysdev.cls = &node_class; + error = sysdev_register(&node->sysdev); + + if (!error){ + sysdev_create_file(&node->sysdev, &attr_cpumap); + sysdev_create_file(&node->sysdev, &attr_meminfo); + sysdev_create_file(&node->sysdev, &attr_numastat); + sysdev_create_file(&node->sysdev, &attr_distance); + } + return error; +} + + +int __init register_node_type(void) +{ + return sysdev_class_register(&node_class); +} +postcore_initcall(register_node_type); diff --git a/drivers/base/platform.c b/drivers/base/platform.c new file mode 100644 index 000000000000..996cbb4d5087 --- /dev/null +++ b/drivers/base/platform.c @@ -0,0 +1,350 @@ +/* + * platform.c - platform 'pseudo' bus for legacy devices + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * This file is released under the GPLv2 + * + * Please see Documentation/driver-model/platform.txt for more + * information. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/bootmem.h> +#include <linux/err.h> + +struct device platform_bus = { + .bus_id = "platform", +}; + +/** + * platform_get_resource - get a resource for a device + * @dev: platform device + * @type: resource type + * @num: resource index + */ +struct resource * +platform_get_resource(struct platform_device *dev, unsigned int type, + unsigned int num) +{ + int i; + + for (i = 0; i < dev->num_resources; i++) { + struct resource *r = &dev->resource[i]; + + if ((r->flags & (IORESOURCE_IO|IORESOURCE_MEM| + IORESOURCE_IRQ|IORESOURCE_DMA)) + == type) + if (num-- == 0) + return r; + } + return NULL; +} + +/** + * platform_get_irq - get an IRQ for a device + * @dev: platform device + * @num: IRQ number index + */ +int platform_get_irq(struct platform_device *dev, unsigned int num) +{ + struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num); + + return r ? r->start : 0; +} + +/** + * platform_get_resource_byname - get a resource for a device by name + * @dev: platform device + * @type: resource type + * @name: resource name + */ +struct resource * +platform_get_resource_byname(struct platform_device *dev, unsigned int type, + char *name) +{ + int i; + + for (i = 0; i < dev->num_resources; i++) { + struct resource *r = &dev->resource[i]; + + if ((r->flags & (IORESOURCE_IO|IORESOURCE_MEM| + IORESOURCE_IRQ|IORESOURCE_DMA)) == type) + if (!strcmp(r->name, name)) + return r; + } + return NULL; +} + +/** + * platform_get_irq - get an IRQ for a device + * @dev: platform device + * @name: IRQ name + */ +int platform_get_irq_byname(struct platform_device *dev, char *name) +{ + struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name); + + return r ? r->start : 0; +} + +/** + * platform_add_devices - add a numbers of platform devices + * @devs: array of platform devices to add + * @num: number of platform devices in array + */ +int platform_add_devices(struct platform_device **devs, int num) +{ + int i, ret = 0; + + for (i = 0; i < num; i++) { + ret = platform_device_register(devs[i]); + if (ret) { + while (--i >= 0) + platform_device_unregister(devs[i]); + break; + } + } + + return ret; +} + +/** + * platform_device_register - add a platform-level device + * @dev: platform device we're adding + * + */ +int platform_device_register(struct platform_device * pdev) +{ + int i, ret = 0; + + if (!pdev) + return -EINVAL; + + if (!pdev->dev.parent) + pdev->dev.parent = &platform_bus; + + pdev->dev.bus = &platform_bus_type; + + if (pdev->id != -1) + snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id); + else + strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE); + + for (i = 0; i < pdev->num_resources; i++) { + struct resource *p, *r = &pdev->resource[i]; + + if (r->name == NULL) + r->name = pdev->dev.bus_id; + + p = r->parent; + if (!p) { + if (r->flags & IORESOURCE_MEM) + p = &iomem_resource; + else if (r->flags & IORESOURCE_IO) + p = &ioport_resource; + } + + if (p && request_resource(p, r)) { + printk(KERN_ERR + "%s: failed to claim resource %d\n", + pdev->dev.bus_id, i); + ret = -EBUSY; + goto failed; + } + } + + pr_debug("Registering platform device '%s'. Parent at %s\n", + pdev->dev.bus_id, pdev->dev.parent->bus_id); + + ret = device_register(&pdev->dev); + if (ret == 0) + return ret; + + failed: + while (--i >= 0) + if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO)) + release_resource(&pdev->resource[i]); + return ret; +} + +/** + * platform_device_unregister - remove a platform-level device + * @dev: platform device we're removing + * + * Note that this function will also release all memory- and port-based + * resources owned by the device (@dev->resource). + */ +void platform_device_unregister(struct platform_device * pdev) +{ + int i; + + if (pdev) { + for (i = 0; i < pdev->num_resources; i++) { + struct resource *r = &pdev->resource[i]; + if (r->flags & (IORESOURCE_MEM|IORESOURCE_IO)) + release_resource(r); + } + + device_unregister(&pdev->dev); + } +} + +struct platform_object { + struct platform_device pdev; + struct resource resources[0]; +}; + +static void platform_device_release_simple(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + kfree(container_of(pdev, struct platform_object, pdev)); +} + +/** + * platform_device_register_simple + * @name: base name of the device we're adding + * @id: instance id + * @res: set of resources that needs to be allocated for the device + * @num: number of resources + * + * This function creates a simple platform device that requires minimal + * resource and memory management. Canned release function freeing + * memory allocated for the device allows drivers using such devices + * to be unloaded iwithout waiting for the last reference to the device + * to be dropped. + */ +struct platform_device *platform_device_register_simple(char *name, unsigned int id, + struct resource *res, unsigned int num) +{ + struct platform_object *pobj; + int retval; + + pobj = kmalloc(sizeof(struct platform_object) + sizeof(struct resource) * num, GFP_KERNEL); + if (!pobj) { + retval = -ENOMEM; + goto error; + } + + memset(pobj, 0, sizeof(*pobj)); + pobj->pdev.name = name; + pobj->pdev.id = id; + pobj->pdev.dev.release = platform_device_release_simple; + + if (num) { + memcpy(pobj->resources, res, sizeof(struct resource) * num); + pobj->pdev.resource = pobj->resources; + pobj->pdev.num_resources = num; + } + + retval = platform_device_register(&pobj->pdev); + if (retval) + goto error; + + return &pobj->pdev; + +error: + kfree(pobj); + return ERR_PTR(retval); +} + + +/** + * platform_match - bind platform device to platform driver. + * @dev: device. + * @drv: driver. + * + * Platform device IDs are assumed to be encoded like this: + * "<name><instance>", where <name> is a short description of the + * type of device, like "pci" or "floppy", and <instance> is the + * enumerated instance of the device, like '0' or '42'. + * Driver IDs are simply "<name>". + * So, extract the <name> from the platform_device structure, + * and compare it against the name of the driver. Return whether + * they match or not. + */ + +static int platform_match(struct device * dev, struct device_driver * drv) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + + return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); +} + +static int platform_suspend(struct device * dev, pm_message_t state) +{ + int ret = 0; + + if (dev->driver && dev->driver->suspend) { + ret = dev->driver->suspend(dev, state, SUSPEND_DISABLE); + if (ret == 0) + ret = dev->driver->suspend(dev, state, SUSPEND_SAVE_STATE); + if (ret == 0) + ret = dev->driver->suspend(dev, state, SUSPEND_POWER_DOWN); + } + return ret; +} + +static int platform_resume(struct device * dev) +{ + int ret = 0; + + if (dev->driver && dev->driver->resume) { + ret = dev->driver->resume(dev, RESUME_POWER_ON); + if (ret == 0) + ret = dev->driver->resume(dev, RESUME_RESTORE_STATE); + if (ret == 0) + ret = dev->driver->resume(dev, RESUME_ENABLE); + } + return ret; +} + +struct bus_type platform_bus_type = { + .name = "platform", + .match = platform_match, + .suspend = platform_suspend, + .resume = platform_resume, +}; + +int __init platform_bus_init(void) +{ + device_register(&platform_bus); + return bus_register(&platform_bus_type); +} + +#ifndef ARCH_HAS_DMA_GET_REQUIRED_MASK +u64 dma_get_required_mask(struct device *dev) +{ + u32 low_totalram = ((max_pfn - 1) << PAGE_SHIFT); + u32 high_totalram = ((max_pfn - 1) >> (32 - PAGE_SHIFT)); + u64 mask; + + if (!high_totalram) { + /* convert to mask just covering totalram */ + low_totalram = (1 << (fls(low_totalram) - 1)); + low_totalram += low_totalram - 1; + mask = low_totalram; + } else { + high_totalram = (1 << (fls(high_totalram) - 1)); + high_totalram += high_totalram - 1; + mask = (((u64)high_totalram) << 32) + 0xffffffff; + } + return mask & *dev->dma_mask; +} +EXPORT_SYMBOL_GPL(dma_get_required_mask); +#endif + +EXPORT_SYMBOL_GPL(platform_bus); +EXPORT_SYMBOL_GPL(platform_bus_type); +EXPORT_SYMBOL_GPL(platform_device_register); +EXPORT_SYMBOL_GPL(platform_device_register_simple); +EXPORT_SYMBOL_GPL(platform_device_unregister); +EXPORT_SYMBOL_GPL(platform_get_irq); +EXPORT_SYMBOL_GPL(platform_get_resource); +EXPORT_SYMBOL_GPL(platform_get_irq_byname); +EXPORT_SYMBOL_GPL(platform_get_resource_byname); diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile new file mode 100644 index 000000000000..c0219ad94aca --- /dev/null +++ b/drivers/base/power/Makefile @@ -0,0 +1,6 @@ +obj-y := shutdown.o +obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o + +ifeq ($(CONFIG_DEBUG_DRIVER),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c new file mode 100644 index 000000000000..15e6a8f951f1 --- /dev/null +++ b/drivers/base/power/main.c @@ -0,0 +1,99 @@ +/* + * drivers/base/power/main.c - Where the driver meets power management. + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Open Source Development Lab + * + * This file is released under the GPLv2 + * + * + * The driver model core calls device_pm_add() when a device is registered. + * This will intialize the embedded device_pm_info object in the device + * and add it to the list of power-controlled devices. sysfs entries for + * controlling device power management will also be added. + * + * A different set of lists than the global subsystem list are used to + * keep track of power info because we use different lists to hold + * devices based on what stage of the power management process they + * are in. The power domain dependencies may also differ from the + * ancestral dependencies that the subsystem list maintains. + */ + +#include <linux/config.h> +#include <linux/device.h> +#include "power.h" + +LIST_HEAD(dpm_active); +LIST_HEAD(dpm_off); +LIST_HEAD(dpm_off_irq); + +DECLARE_MUTEX(dpm_sem); +DECLARE_MUTEX(dpm_list_sem); + +/* + * PM Reference Counting. + */ + +static inline void device_pm_hold(struct device * dev) +{ + if (dev) + atomic_inc(&dev->power.pm_users); +} + +static inline void device_pm_release(struct device * dev) +{ + if (dev) + atomic_dec(&dev->power.pm_users); +} + + +/** + * device_pm_set_parent - Specify power dependency. + * @dev: Device who needs power. + * @parent: Device that supplies power. + * + * This function is used to manually describe a power-dependency + * relationship. It may be used to specify a transversal relationship + * (where the power supplier is not the physical (or electrical) + * ancestor of a specific device. + * The effect of this is that the supplier will not be powered down + * before the power dependent. + */ + +void device_pm_set_parent(struct device * dev, struct device * parent) +{ + struct device * old_parent = dev->power.pm_parent; + device_pm_release(old_parent); + dev->power.pm_parent = parent; + device_pm_hold(parent); +} +EXPORT_SYMBOL_GPL(device_pm_set_parent); + +int device_pm_add(struct device * dev) +{ + int error; + + pr_debug("PM: Adding info for %s:%s\n", + dev->bus ? dev->bus->name : "No Bus", dev->kobj.name); + atomic_set(&dev->power.pm_users, 0); + down(&dpm_list_sem); + list_add_tail(&dev->power.entry, &dpm_active); + device_pm_set_parent(dev, dev->parent); + if ((error = dpm_sysfs_add(dev))) + list_del(&dev->power.entry); + up(&dpm_list_sem); + return error; +} + +void device_pm_remove(struct device * dev) +{ + pr_debug("PM: Removing info for %s:%s\n", + dev->bus ? dev->bus->name : "No Bus", dev->kobj.name); + down(&dpm_list_sem); + dpm_sysfs_remove(dev); + device_pm_release(dev->power.pm_parent); + list_del_init(&dev->power.entry); + up(&dpm_list_sem); +} + + diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h new file mode 100644 index 000000000000..e5eda746f2a6 --- /dev/null +++ b/drivers/base/power/power.h @@ -0,0 +1,106 @@ + + +enum { + DEVICE_PM_ON, + DEVICE_PM1, + DEVICE_PM2, + DEVICE_PM3, + DEVICE_PM_OFF, +}; + +/* + * shutdown.c + */ + +extern int device_detach_shutdown(struct device *); +extern void device_shutdown(void); + + +#ifdef CONFIG_PM + +/* + * main.c + */ + +/* + * Used to synchronize global power management operations. + */ +extern struct semaphore dpm_sem; + +/* + * Used to serialize changes to the dpm_* lists. + */ +extern struct semaphore dpm_list_sem; + +/* + * The PM lists. + */ +extern struct list_head dpm_active; +extern struct list_head dpm_off; +extern struct list_head dpm_off_irq; + + +static inline struct dev_pm_info * to_pm_info(struct list_head * entry) +{ + return container_of(entry, struct dev_pm_info, entry); +} + +static inline struct device * to_device(struct list_head * entry) +{ + return container_of(to_pm_info(entry), struct device, power); +} + +extern int device_pm_add(struct device *); +extern void device_pm_remove(struct device *); + +/* + * sysfs.c + */ + +extern int dpm_sysfs_add(struct device *); +extern void dpm_sysfs_remove(struct device *); + +/* + * resume.c + */ + +extern void dpm_resume(void); +extern void dpm_power_up(void); +extern int resume_device(struct device *); + +/* + * suspend.c + */ +extern int suspend_device(struct device *, pm_message_t); + + +/* + * runtime.c + */ + +extern int dpm_runtime_suspend(struct device *, pm_message_t); +extern void dpm_runtime_resume(struct device *); + +#else /* CONFIG_PM */ + + +static inline int device_pm_add(struct device * dev) +{ + return 0; +} +static inline void device_pm_remove(struct device * dev) +{ + +} + +static inline int dpm_runtime_suspend(struct device * dev, pm_message_t state) +{ + return 0; +} + +static inline void dpm_runtime_resume(struct device * dev) +{ + +} + +#endif diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c new file mode 100644 index 000000000000..f8f5055754d6 --- /dev/null +++ b/drivers/base/power/resume.c @@ -0,0 +1,112 @@ +/* + * resume.c - Functions for waking devices up. + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include "power.h" + +extern int sysdev_resume(void); + + +/** + * resume_device - Restore state for one device. + * @dev: Device. + * + */ + +int resume_device(struct device * dev) +{ + if (dev->bus && dev->bus->resume) + return dev->bus->resume(dev); + return 0; +} + + + +void dpm_resume(void) +{ + down(&dpm_list_sem); + while(!list_empty(&dpm_off)) { + struct list_head * entry = dpm_off.next; + struct device * dev = to_device(entry); + + get_device(dev); + list_del_init(entry); + list_add_tail(entry, &dpm_active); + + up(&dpm_list_sem); + if (!dev->power.prev_state) + resume_device(dev); + down(&dpm_list_sem); + put_device(dev); + } + up(&dpm_list_sem); +} + + +/** + * device_resume - Restore state of each device in system. + * + * Walk the dpm_off list, remove each entry, resume the device, + * then add it to the dpm_active list. + */ + +void device_resume(void) +{ + down(&dpm_sem); + dpm_resume(); + up(&dpm_sem); +} + +EXPORT_SYMBOL_GPL(device_resume); + + +/** + * device_power_up_irq - Power on some devices. + * + * Walk the dpm_off_irq list and power each device up. This + * is used for devices that required they be powered down with + * interrupts disabled. As devices are powered on, they are moved to + * the dpm_suspended list. + * + * Interrupts must be disabled when calling this. + */ + +void dpm_power_up(void) +{ + while(!list_empty(&dpm_off_irq)) { + struct list_head * entry = dpm_off_irq.next; + struct device * dev = to_device(entry); + + get_device(dev); + list_del_init(entry); + list_add_tail(entry, &dpm_active); + resume_device(dev); + put_device(dev); + } +} + + +/** + * device_pm_power_up - Turn on all devices that need special attention. + * + * Power on system devices then devices that required we shut them down + * with interrupts disabled. + * Called with interrupts disabled. + */ + +void device_power_up(void) +{ + sysdev_resume(); + dpm_power_up(); +} + +EXPORT_SYMBOL_GPL(device_power_up); + + diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c new file mode 100644 index 000000000000..325962d80191 --- /dev/null +++ b/drivers/base/power/runtime.c @@ -0,0 +1,81 @@ +/* + * drivers/base/power/runtime.c - Handling dynamic device power management. + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Open Source Development Lab + * + */ + +#include <linux/device.h> +#include "power.h" + + +static void runtime_resume(struct device * dev) +{ + dev_dbg(dev, "resuming\n"); + if (!dev->power.power_state) + return; + if (!resume_device(dev)) + dev->power.power_state = 0; +} + + +/** + * dpm_runtime_resume - Power one device back on. + * @dev: Device. + * + * Bring one device back to the on state by first powering it + * on, then restoring state. We only operate on devices that aren't + * already on. + * FIXME: We need to handle devices that are in an unknown state. + */ + +void dpm_runtime_resume(struct device * dev) +{ + down(&dpm_sem); + runtime_resume(dev); + up(&dpm_sem); +} + + +/** + * dpm_runtime_suspend - Put one device in low-power state. + * @dev: Device. + * @state: State to enter. + */ + +int dpm_runtime_suspend(struct device * dev, pm_message_t state) +{ + int error = 0; + + down(&dpm_sem); + if (dev->power.power_state == state) + goto Done; + + if (dev->power.power_state) + runtime_resume(dev); + + if (!(error = suspend_device(dev, state))) + dev->power.power_state = state; + Done: + up(&dpm_sem); + return error; +} + + +/** + * dpm_set_power_state - Update power_state field. + * @dev: Device. + * @state: Power state device is in. + * + * This is an update mechanism for drivers to notify the core + * what power state a device is in. Device probing code may not + * always be able to tell, but we need accurate information to + * work reliably. + */ +void dpm_set_power_state(struct device * dev, pm_message_t state) +{ + down(&dpm_sem); + dev->power.power_state = state; + up(&dpm_sem); +} diff --git a/drivers/base/power/shutdown.c b/drivers/base/power/shutdown.c new file mode 100644 index 000000000000..d1e023fbe169 --- /dev/null +++ b/drivers/base/power/shutdown.c @@ -0,0 +1,67 @@ +/* + * shutdown.c - power management functions for the device tree. + * + * Copyright (c) 2002-3 Patrick Mochel + * 2002-3 Open Source Development Lab + * + * This file is released under the GPLv2 + * + */ + +#include <linux/config.h> +#include <linux/device.h> +#include <asm/semaphore.h> + +#include "power.h" + +#define to_dev(node) container_of(node, struct device, kobj.entry) + +extern struct subsystem devices_subsys; + + +int device_detach_shutdown(struct device * dev) +{ + if (!dev->detach_state) + return 0; + + if (dev->detach_state == DEVICE_PM_OFF) { + if (dev->driver && dev->driver->shutdown) + dev->driver->shutdown(dev); + return 0; + } + return dpm_runtime_suspend(dev, dev->detach_state); +} + + +/** + * We handle system devices differently - we suspend and shut them + * down last and resume them first. That way, we don't do anything stupid like + * shutting down the interrupt controller before any devices.. + * + * Note that there are not different stages for power management calls - + * they only get one called once when interrupts are disabled. + */ + +extern int sysdev_shutdown(void); + +/** + * device_shutdown - call ->shutdown() on each device to shutdown. + */ +void device_shutdown(void) +{ + struct device * dev; + + down_write(&devices_subsys.rwsem); + list_for_each_entry_reverse(dev, &devices_subsys.kset.list, kobj.entry) { + pr_debug("shutting down %s: ", dev->bus_id); + if (dev->driver && dev->driver->shutdown) { + pr_debug("Ok\n"); + dev->driver->shutdown(dev); + } else + pr_debug("Ignored.\n"); + } + up_write(&devices_subsys.rwsem); + + sysdev_shutdown(); +} + diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c new file mode 100644 index 000000000000..a0b5cf689e63 --- /dev/null +++ b/drivers/base/power/suspend.c @@ -0,0 +1,144 @@ +/* + * suspend.c - Functions for putting devices to sleep. + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include "power.h" + +extern int sysdev_suspend(pm_message_t state); + +/* + * The entries in the dpm_active list are in a depth first order, simply + * because children are guaranteed to be discovered after parents, and + * are inserted at the back of the list on discovery. + * + * All list on the suspend path are done in reverse order, so we operate + * on the leaves of the device tree (or forests, depending on how you want + * to look at it ;) first. As nodes are removed from the back of the list, + * they are inserted into the front of their destintation lists. + * + * Things are the reverse on the resume path - iterations are done in + * forward order, and nodes are inserted at the back of their destination + * lists. This way, the ancestors will be accessed before their descendents. + */ + + +/** + * suspend_device - Save state of one device. + * @dev: Device. + * @state: Power state device is entering. + */ + +int suspend_device(struct device * dev, pm_message_t state) +{ + int error = 0; + + dev_dbg(dev, "suspending\n"); + + dev->power.prev_state = dev->power.power_state; + + if (dev->bus && dev->bus->suspend && !dev->power.power_state) + error = dev->bus->suspend(dev, state); + + return error; +} + + +/** + * device_suspend - Save state and stop all devices in system. + * @state: Power state to put each device in. + * + * Walk the dpm_active list, call ->suspend() for each device, and move + * it to dpm_off. + * Check the return value for each. If it returns 0, then we move the + * the device to the dpm_off list. If it returns -EAGAIN, we move it to + * the dpm_off_irq list. If we get a different error, try and back out. + * + * If we hit a failure with any of the devices, call device_resume() + * above to bring the suspended devices back to life. + * + */ + +int device_suspend(pm_message_t state) +{ + int error = 0; + + down(&dpm_sem); + down(&dpm_list_sem); + while (!list_empty(&dpm_active) && error == 0) { + struct list_head * entry = dpm_active.prev; + struct device * dev = to_device(entry); + + get_device(dev); + up(&dpm_list_sem); + + error = suspend_device(dev, state); + + down(&dpm_list_sem); + + /* Check if the device got removed */ + if (!list_empty(&dev->power.entry)) { + /* Move it to the dpm_off or dpm_off_irq list */ + if (!error) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off); + } else if (error == -EAGAIN) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off_irq); + error = 0; + } + } + if (error) + printk(KERN_ERR "Could not suspend device %s: " + "error %d\n", kobject_name(&dev->kobj), error); + put_device(dev); + } + up(&dpm_list_sem); + if (error) + dpm_resume(); + up(&dpm_sem); + return error; +} + +EXPORT_SYMBOL_GPL(device_suspend); + + +/** + * device_power_down - Shut down special devices. + * @state: Power state to enter. + * + * Walk the dpm_off_irq list, calling ->power_down() for each device that + * couldn't power down the device with interrupts enabled. When we're + * done, power down system devices. + */ + +int device_power_down(pm_message_t state) +{ + int error = 0; + struct device * dev; + + list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { + if ((error = suspend_device(dev, state))) + break; + } + if (error) + goto Error; + if ((error = sysdev_suspend(state))) + goto Error; + Done: + return error; + Error: + printk(KERN_ERR "Could not power down device %s: " + "error %d\n", kobject_name(&dev->kobj), error); + dpm_power_up(); + goto Done; +} + +EXPORT_SYMBOL_GPL(device_power_down); + diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c new file mode 100644 index 000000000000..6ac96349a8e8 --- /dev/null +++ b/drivers/base/power/sysfs.c @@ -0,0 +1,68 @@ +/* + * drivers/base/power/sysfs.c - sysfs entries for device PM + */ + +#include <linux/device.h> +#include "power.h" + + +/** + * state - Control current power state of device + * + * show() returns the current power state of the device. '0' indicates + * the device is on. Other values (1-3) indicate the device is in a low + * power state. + * + * store() sets the current power state, which is an integer value + * between 0-3. If the device is on ('0'), and the value written is + * greater than 0, then the device is placed directly into the low-power + * state (via its driver's ->suspend() method). + * If the device is currently in a low-power state, and the value is 0, + * the device is powered back on (via the ->resume() method). + * If the device is in a low-power state, and a different low-power state + * is requested, the device is first resumed, then suspended into the new + * low-power state. + */ + +static ssize_t state_show(struct device * dev, char * buf) +{ + return sprintf(buf, "%u\n", dev->power.power_state); +} + +static ssize_t state_store(struct device * dev, const char * buf, size_t n) +{ + u32 state; + char * rest; + int error = 0; + + state = simple_strtoul(buf, &rest, 10); + if (*rest) + return -EINVAL; + if (state) + error = dpm_runtime_suspend(dev, state); + else + dpm_runtime_resume(dev); + return error ? error : n; +} + +static DEVICE_ATTR(state, 0644, state_show, state_store); + + +static struct attribute * power_attrs[] = { + &dev_attr_state.attr, + NULL, +}; +static struct attribute_group pm_attr_group = { + .name = "power", + .attrs = power_attrs, +}; + +int dpm_sysfs_add(struct device * dev) +{ + return sysfs_create_group(&dev->kobj, &pm_attr_group); +} + +void dpm_sysfs_remove(struct device * dev) +{ + sysfs_remove_group(&dev->kobj, &pm_attr_group); +} diff --git a/drivers/base/sys.c b/drivers/base/sys.c new file mode 100644 index 000000000000..cff5a6a2c784 --- /dev/null +++ b/drivers/base/sys.c @@ -0,0 +1,397 @@ +/* + * sys.c - pseudo-bus for system 'devices' (cpus, PICs, timers, etc) + * + * Copyright (c) 2002-3 Patrick Mochel + * 2002-3 Open Source Development Lab + * + * This file is released under the GPLv2 + * + * This exports a 'system' bus type. + * By default, a 'sys' bus gets added to the root of the system. There will + * always be core system devices. Devices can use sysdev_register() to + * add themselves as children of the system bus. + */ + +#include <linux/config.h> +#include <linux/sysdev.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> + + +extern struct subsystem devices_subsys; + +#define to_sysdev(k) container_of(k, struct sys_device, kobj) +#define to_sysdev_attr(a) container_of(a, struct sysdev_attribute, attr) + + +static ssize_t +sysdev_show(struct kobject * kobj, struct attribute * attr, char * buffer) +{ + struct sys_device * sysdev = to_sysdev(kobj); + struct sysdev_attribute * sysdev_attr = to_sysdev_attr(attr); + + if (sysdev_attr->show) + return sysdev_attr->show(sysdev, buffer); + return 0; +} + + +static ssize_t +sysdev_store(struct kobject * kobj, struct attribute * attr, + const char * buffer, size_t count) +{ + struct sys_device * sysdev = to_sysdev(kobj); + struct sysdev_attribute * sysdev_attr = to_sysdev_attr(attr); + + if (sysdev_attr->store) + return sysdev_attr->store(sysdev, buffer, count); + return 0; +} + +static struct sysfs_ops sysfs_ops = { + .show = sysdev_show, + .store = sysdev_store, +}; + +static struct kobj_type ktype_sysdev = { + .sysfs_ops = &sysfs_ops, +}; + + +int sysdev_create_file(struct sys_device * s, struct sysdev_attribute * a) +{ + return sysfs_create_file(&s->kobj, &a->attr); +} + + +void sysdev_remove_file(struct sys_device * s, struct sysdev_attribute * a) +{ + sysfs_remove_file(&s->kobj, &a->attr); +} + +EXPORT_SYMBOL_GPL(sysdev_create_file); +EXPORT_SYMBOL_GPL(sysdev_remove_file); + +/* + * declare system_subsys + */ +static decl_subsys(system, &ktype_sysdev, NULL); + +int sysdev_class_register(struct sysdev_class * cls) +{ + pr_debug("Registering sysdev class '%s'\n", + kobject_name(&cls->kset.kobj)); + INIT_LIST_HEAD(&cls->drivers); + cls->kset.subsys = &system_subsys; + kset_set_kset_s(cls, system_subsys); + return kset_register(&cls->kset); +} + +void sysdev_class_unregister(struct sysdev_class * cls) +{ + pr_debug("Unregistering sysdev class '%s'\n", + kobject_name(&cls->kset.kobj)); + kset_unregister(&cls->kset); +} + +EXPORT_SYMBOL_GPL(sysdev_class_register); +EXPORT_SYMBOL_GPL(sysdev_class_unregister); + + +static LIST_HEAD(sysdev_drivers); +static DECLARE_MUTEX(sysdev_drivers_lock); + +/** + * sysdev_driver_register - Register auxillary driver + * @cls: Device class driver belongs to. + * @drv: Driver. + * + * If @cls is valid, then @drv is inserted into @cls->drivers to be + * called on each operation on devices of that class. The refcount + * of @cls is incremented. + * Otherwise, @drv is inserted into sysdev_drivers, and called for + * each device. + */ + +int sysdev_driver_register(struct sysdev_class * cls, + struct sysdev_driver * drv) +{ + down(&sysdev_drivers_lock); + if (cls && kset_get(&cls->kset)) { + list_add_tail(&drv->entry, &cls->drivers); + + /* If devices of this class already exist, tell the driver */ + if (drv->add) { + struct sys_device *dev; + list_for_each_entry(dev, &cls->kset.list, kobj.entry) + drv->add(dev); + } + } else + list_add_tail(&drv->entry, &sysdev_drivers); + up(&sysdev_drivers_lock); + return 0; +} + + +/** + * sysdev_driver_unregister - Remove an auxillary driver. + * @cls: Class driver belongs to. + * @drv: Driver. + */ +void sysdev_driver_unregister(struct sysdev_class * cls, + struct sysdev_driver * drv) +{ + down(&sysdev_drivers_lock); + list_del_init(&drv->entry); + if (cls) { + if (drv->remove) { + struct sys_device *dev; + list_for_each_entry(dev, &cls->kset.list, kobj.entry) + drv->remove(dev); + } + kset_put(&cls->kset); + } + up(&sysdev_drivers_lock); +} + +EXPORT_SYMBOL_GPL(sysdev_driver_register); +EXPORT_SYMBOL_GPL(sysdev_driver_unregister); + + + +/** + * sysdev_register - add a system device to the tree + * @sysdev: device in question + * + */ +int sysdev_register(struct sys_device * sysdev) +{ + int error; + struct sysdev_class * cls = sysdev->cls; + + if (!cls) + return -EINVAL; + + /* Make sure the kset is set */ + sysdev->kobj.kset = &cls->kset; + + /* But make sure we point to the right type for sysfs translation */ + sysdev->kobj.ktype = &ktype_sysdev; + error = kobject_set_name(&sysdev->kobj, "%s%d", + kobject_name(&cls->kset.kobj), sysdev->id); + if (error) + return error; + + pr_debug("Registering sys device '%s'\n", kobject_name(&sysdev->kobj)); + + /* Register the object */ + error = kobject_register(&sysdev->kobj); + + if (!error) { + struct sysdev_driver * drv; + + down(&sysdev_drivers_lock); + /* Generic notification is implicit, because it's that + * code that should have called us. + */ + + /* Notify global drivers */ + list_for_each_entry(drv, &sysdev_drivers, entry) { + if (drv->add) + drv->add(sysdev); + } + + /* Notify class auxillary drivers */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->add) + drv->add(sysdev); + } + up(&sysdev_drivers_lock); + } + return error; +} + +void sysdev_unregister(struct sys_device * sysdev) +{ + struct sysdev_driver * drv; + + down(&sysdev_drivers_lock); + list_for_each_entry(drv, &sysdev_drivers, entry) { + if (drv->remove) + drv->remove(sysdev); + } + + list_for_each_entry(drv, &sysdev->cls->drivers, entry) { + if (drv->remove) + drv->remove(sysdev); + } + up(&sysdev_drivers_lock); + + kobject_unregister(&sysdev->kobj); +} + + + +/** + * sysdev_shutdown - Shut down all system devices. + * + * Loop over each class of system devices, and the devices in each + * of those classes. For each device, we call the shutdown method for + * each driver registered for the device - the globals, the auxillaries, + * and the class driver. + * + * Note: The list is iterated in reverse order, so that we shut down + * child devices before we shut down thier parents. The list ordering + * is guaranteed by virtue of the fact that child devices are registered + * after their parents. + */ + +void sysdev_shutdown(void) +{ + struct sysdev_class * cls; + + pr_debug("Shutting Down System Devices\n"); + + down(&sysdev_drivers_lock); + list_for_each_entry_reverse(cls, &system_subsys.kset.list, + kset.kobj.entry) { + struct sys_device * sysdev; + + pr_debug("Shutting down type '%s':\n", + kobject_name(&cls->kset.kobj)); + + list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) { + struct sysdev_driver * drv; + pr_debug(" %s\n", kobject_name(&sysdev->kobj)); + + /* Call global drivers first. */ + list_for_each_entry(drv, &sysdev_drivers, entry) { + if (drv->shutdown) + drv->shutdown(sysdev); + } + + /* Call auxillary drivers next. */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->shutdown) + drv->shutdown(sysdev); + } + + /* Now call the generic one */ + if (cls->shutdown) + cls->shutdown(sysdev); + } + } + up(&sysdev_drivers_lock); +} + + +/** + * sysdev_suspend - Suspend all system devices. + * @state: Power state to enter. + * + * We perform an almost identical operation as sys_device_shutdown() + * above, though calling ->suspend() instead. Interrupts are disabled + * when this called. Devices are responsible for both saving state and + * quiescing or powering down the device. + * + * This is only called by the device PM core, so we let them handle + * all synchronization. + */ + +int sysdev_suspend(u32 state) +{ + struct sysdev_class * cls; + + pr_debug("Suspending System Devices\n"); + + list_for_each_entry_reverse(cls, &system_subsys.kset.list, + kset.kobj.entry) { + struct sys_device * sysdev; + + pr_debug("Suspending type '%s':\n", + kobject_name(&cls->kset.kobj)); + + list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) { + struct sysdev_driver * drv; + pr_debug(" %s\n", kobject_name(&sysdev->kobj)); + + /* Call global drivers first. */ + list_for_each_entry(drv, &sysdev_drivers, entry) { + if (drv->suspend) + drv->suspend(sysdev, state); + } + + /* Call auxillary drivers next. */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->suspend) + drv->suspend(sysdev, state); + } + + /* Now call the generic one */ + if (cls->suspend) + cls->suspend(sysdev, state); + } + } + return 0; +} + + +/** + * sysdev_resume - Bring system devices back to life. + * + * Similar to sys_device_suspend(), but we iterate the list forwards + * to guarantee that parent devices are resumed before their children. + * + * Note: Interrupts are disabled when called. + */ + +int sysdev_resume(void) +{ + struct sysdev_class * cls; + + pr_debug("Resuming System Devices\n"); + + list_for_each_entry(cls, &system_subsys.kset.list, kset.kobj.entry) { + struct sys_device * sysdev; + + pr_debug("Resuming type '%s':\n", + kobject_name(&cls->kset.kobj)); + + list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) { + struct sysdev_driver * drv; + pr_debug(" %s\n", kobject_name(&sysdev->kobj)); + + /* First, call the class-specific one */ + if (cls->resume) + cls->resume(sysdev); + + /* Call auxillary drivers next. */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->resume) + drv->resume(sysdev); + } + + /* Call global drivers. */ + list_for_each_entry(drv, &sysdev_drivers, entry) { + if (drv->resume) + drv->resume(sysdev); + } + + } + } + return 0; +} + + +int __init system_bus_init(void) +{ + system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj; + return subsystem_register(&system_subsys); +} + +EXPORT_SYMBOL_GPL(sysdev_register); +EXPORT_SYMBOL_GPL(sysdev_unregister); diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c new file mode 100644 index 000000000000..6c2b447a3336 --- /dev/null +++ b/drivers/base/transport_class.c @@ -0,0 +1,275 @@ +/* + * transport_class.c - implementation of generic transport classes + * using attribute_containers + * + * Copyright (c) 2005 - James Bottomley <James.Bottomley@steeleye.com> + * + * This file is licensed under GPLv2 + * + * The basic idea here is to allow any "device controller" (which + * would most often be a Host Bus Adapter" to use the services of one + * or more tranport classes for performing transport specific + * services. Transport specific services are things that the generic + * command layer doesn't want to know about (speed settings, line + * condidtioning, etc), but which the user might be interested in. + * Thus, the HBA's use the routines exported by the transport classes + * to perform these functions. The transport classes export certain + * values to the user via sysfs using attribute containers. + * + * Note: because not every HBA will care about every transport + * attribute, there's a many to one relationship that goes like this: + * + * transport class<-----attribute container<----class device + * + * Usually the attribute container is per-HBA, but the design doesn't + * mandate that. Although most of the services will be specific to + * the actual external storage connection used by the HBA, the generic + * transport class is framed entirely in terms of generic devices to + * allow it to be used by any physical HBA in the system. + */ +#include <linux/attribute_container.h> +#include <linux/transport_class.h> + +/** + * transport_class_register - register an initial transport class + * + * @tclass: a pointer to the transport class structure to be initialised + * + * The transport class contains an embedded class which is used to + * identify it. The caller should initialise this structure with + * zeros and then generic class must have been initialised with the + * actual transport class unique name. There's a macro + * DECLARE_TRANSPORT_CLASS() to do this (declared classes still must + * be registered). + * + * Returns 0 on success or error on failure. + */ +int transport_class_register(struct transport_class *tclass) +{ + return class_register(&tclass->class); +} +EXPORT_SYMBOL_GPL(transport_class_register); + +/** + * transport_class_unregister - unregister a previously registered class + * + * @tclass: The transport class to unregister + * + * Must be called prior to deallocating the memory for the transport + * class. + */ +void transport_class_unregister(struct transport_class *tclass) +{ + class_unregister(&tclass->class); +} +EXPORT_SYMBOL_GPL(transport_class_unregister); + +static int anon_transport_dummy_function(struct device *dev) +{ + /* do nothing */ + return 0; +} + +/** + * anon_transport_class_register - register an anonymous class + * + * @atc: The anon transport class to register + * + * The anonymous transport class contains both a transport class and a + * container. The idea of an anonymous class is that it never + * actually has any device attributes associated with it (and thus + * saves on container storage). So it can only be used for triggering + * events. Use prezero and then use DECLARE_ANON_TRANSPORT_CLASS() to + * initialise the anon transport class storage. + */ +int anon_transport_class_register(struct anon_transport_class *atc) +{ + int error; + atc->container.class = &atc->tclass.class; + attribute_container_set_no_classdevs(&atc->container); + error = attribute_container_register(&atc->container); + if (error) + return error; + atc->tclass.setup = anon_transport_dummy_function; + atc->tclass.remove = anon_transport_dummy_function; + return 0; +} +EXPORT_SYMBOL_GPL(anon_transport_class_register); + +/** + * anon_transport_class_unregister - unregister an anon class + * + * @atc: Pointer to the anon transport class to unregister + * + * Must be called prior to deallocating the memory for the anon + * transport class. + */ +void anon_transport_class_unregister(struct anon_transport_class *atc) +{ + attribute_container_unregister(&atc->container); +} +EXPORT_SYMBOL_GPL(anon_transport_class_unregister); + +static int transport_setup_classdev(struct attribute_container *cont, + struct device *dev, + struct class_device *classdev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + + if (tclass->setup) + tclass->setup(dev); + + return 0; +} + +/** + * transport_setup_device - declare a new dev for transport class association + * but don't make it visible yet. + * + * @dev: the generic device representing the entity being added + * + * Usually, dev represents some component in the HBA system (either + * the HBA itself or a device remote across the HBA bus). This + * routine is simply a trigger point to see if any set of transport + * classes wishes to associate with the added device. This allocates + * storage for the class device and initialises it, but does not yet + * add it to the system or add attributes to it (you do this with + * transport_add_device). If you have no need for a separate setup + * and add operations, use transport_register_device (see + * transport_class.h). + */ + +void transport_setup_device(struct device *dev) +{ + attribute_container_add_device(dev, transport_setup_classdev); +} +EXPORT_SYMBOL_GPL(transport_setup_device); + +static int transport_add_class_device(struct attribute_container *cont, + struct device *dev, + struct class_device *classdev) +{ + int error = attribute_container_add_class_device(classdev); + struct transport_container *tcont = + attribute_container_to_transport_container(cont); + + if (!error && tcont->statistics) + error = sysfs_create_group(&classdev->kobj, tcont->statistics); + + return error; +} + + +/** + * transport_add_device - declare a new dev for transport class association + * + * @dev: the generic device representing the entity being added + * + * Usually, dev represents some component in the HBA system (either + * the HBA itself or a device remote across the HBA bus). This + * routine is simply a trigger point used to add the device to the + * system and register attributes for it. + */ + +void transport_add_device(struct device *dev) +{ + attribute_container_device_trigger(dev, transport_add_class_device); +} +EXPORT_SYMBOL_GPL(transport_add_device); + +static int transport_configure(struct attribute_container *cont, + struct device *dev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + + if (tclass->configure) + tclass->configure(dev); + + return 0; +} + +/** + * transport_configure_device - configure an already set up device + * + * @dev: generic device representing device to be configured + * + * The idea of configure is simply to provide a point within the setup + * process to allow the transport class to extract information from a + * device after it has been setup. This is used in SCSI because we + * have to have a setup device to begin using the HBA, but after we + * send the initial inquiry, we use configure to extract the device + * parameters. The device need not have been added to be configured. + */ +void transport_configure_device(struct device *dev) +{ + attribute_container_trigger(dev, transport_configure); +} +EXPORT_SYMBOL_GPL(transport_configure_device); + +static int transport_remove_classdev(struct attribute_container *cont, + struct device *dev, + struct class_device *classdev) +{ + struct transport_container *tcont = + attribute_container_to_transport_container(cont); + struct transport_class *tclass = class_to_transport_class(cont->class); + + if (tclass->remove) + tclass->remove(dev); + + if (tclass->remove != anon_transport_dummy_function) { + if (tcont->statistics) + sysfs_remove_group(&classdev->kobj, tcont->statistics); + attribute_container_class_device_del(classdev); + } + + return 0; +} + + +/** + * transport_remove_device - remove the visibility of a device + * + * @dev: generic device to remove + * + * This call removes the visibility of the device (to the user from + * sysfs), but does not destroy it. To eliminate a device entirely + * you must also call transport_destroy_device. If you don't need to + * do remove and destroy as separate operations, use + * transport_unregister_device() (see transport_class.h) which will + * perform both calls for you. + */ +void transport_remove_device(struct device *dev) +{ + attribute_container_device_trigger(dev, transport_remove_classdev); +} +EXPORT_SYMBOL_GPL(transport_remove_device); + +static void transport_destroy_classdev(struct attribute_container *cont, + struct device *dev, + struct class_device *classdev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + + if (tclass->remove != anon_transport_dummy_function) + class_device_put(classdev); +} + + +/** + * transport_destroy_device - destroy a removed device + * + * @dev: device to eliminate from the transport class. + * + * This call triggers the elimination of storage associated with the + * transport classdev. Note: all it really does is relinquish a + * reference to the classdev. The memory will not be freed until the + * last reference goes to zero. Note also that the classdev retains a + * reference count on dev, so dev too will remain for as long as the + * transport class device remains around. + */ +void transport_destroy_device(struct device *dev) +{ + attribute_container_remove_device(dev, transport_destroy_classdev); +} +EXPORT_SYMBOL_GPL(transport_destroy_device); |