diff options
Diffstat (limited to 'drivers/base')
30 files changed, 10611 insertions, 0 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig new file mode 100644 index 0000000..d8e8c49 --- /dev/null +++ b/drivers/base/Kconfig @@ -0,0 +1,129 @@ +menu "Generic Driver Options" + +config UEVENT_HELPER_PATH + string "path to uevent helper" + depends on HOTPLUG + default "/sbin/hotplug" + help + Path to uevent helper program forked by the kernel for + every uevent. + +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 "Userspace firmware loading support" if EMBEDDED + depends on HOTPLUG + default y + ---help--- + This option is provided for the case where no in-kernel-tree modules + require userspace firmware loading support, but a module built outside + the kernel tree does. + +config FIRMWARE_IN_KERNEL + bool "Include in-kernel firmware blobs in kernel binary" + depends on FW_LOADER + default y + help + The kernel source tree includes a number of firmware 'blobs' + which are used by various drivers. The recommended way to + use these is to run "make firmware_install" and to copy the + resulting binary files created in usr/lib/firmware directory + of the kernel tree to the /lib/firmware on your system so + that they can be loaded by userspace helpers on request. + + Enabling this option will build each required firmware blob + into the kernel directly, where request_firmware() will find + them without having to call out to userspace. This may be + useful if your root file system requires a device which uses + such firmware, and do not wish to use an initrd. + + This single option controls the inclusion of firmware for + every driver which uses request_firmare() and ships its + firmware in the kernel source tree, to avoid a proliferation + of 'Include firmware for xxx device' options. + + Say 'N' and let firmware be loaded from userspace. + +config EXTRA_FIRMWARE + string "External firmware blobs to build into the kernel binary" + depends on FW_LOADER + help + This option allows firmware to be built into the kernel, for the + cases where the user either cannot or doesn't want to provide it from + userspace at runtime (for example, when the firmware in question is + required for accessing the boot device, and the user doesn't want to + use an initrd). + + This option is a string, and takes the (space-separated) names of the + firmware files -- the same names which appear in MODULE_FIRMWARE() + and request_firmware() in the source. These files should exist under + the directory specified by the EXTRA_FIRMWARE_DIR option, which is + by default the firmware/ subdirectory of the kernel source tree. + + So, for example, you might set CONFIG_EXTRA_FIRMWARE="usb8388.bin", + copy the usb8388.bin file into the firmware/ directory, and build the + kernel. Then any request_firmware("usb8388.bin") will be + satisfied internally without needing to call out to userspace. + + WARNING: If you include additional firmware files into your binary + kernel image which are not available under the terms of the GPL, + then it may be a violation of the GPL to distribute the resulting + image -- since it combines both GPL and non-GPL work. You should + consult a lawyer of your own before distributing such an image. + +config EXTRA_FIRMWARE_DIR + string "Firmware blobs root directory" + depends on EXTRA_FIRMWARE != "" + default "firmware" + help + This option controls the directory in which the kernel build system + looks for the firmware files listed in the EXTRA_FIRMWARE option. + The default is the firmware/ directory in the kernel source tree, + but by changing this option you can point it elsewhere, such as + the /lib/firmware/ directory or another separate directory + containing firmware files. + +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. + +config DEBUG_DEVRES + bool "Managed device resources verbose debug messages" + depends on DEBUG_KERNEL + help + This option enables kernel parameter devres.log. If set to + non-zero, devres debug messages are printed. Select this if + you are having a problem with devres or want to debug + resource management for a managed device. devres.log can be + switched on and off from sysfs node. + + If you are unsure about this, Say N here. + +config SYS_HYPERVISOR + bool + default n + +endmenu diff --git a/drivers/base/Makefile b/drivers/base/Makefile new file mode 100644 index 0000000..c666373 --- /dev/null +++ b/drivers/base/Makefile @@ -0,0 +1,22 @@ +# Makefile for the Linux device tree + +obj-y := core.o sys.o bus.o dd.o \ + driver.o class.o platform.o \ + cpu.o firmware.o init.o map.o devres.o \ + attribute_container.o transport_class.o +obj-y += power/ +obj-$(CONFIG_HAS_DMA) += dma-mapping.o +obj-$(CONFIG_ISA) += isa.o +obj-$(CONFIG_FW_LOADER) += firmware_class.o +obj-$(CONFIG_NUMA) += node.o +obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o +obj-$(CONFIG_SMP) += topology.o +ifeq ($(CONFIG_SYSFS),y) +obj-$(CONFIG_MODULES) += module.o +endif +obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.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 0000000..f57652d --- /dev/null +++ b/drivers/base/attribute_container.c @@ -0,0 +1,440 @@ +/* + * 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> +#include <linux/mutex.h> + +#include "base.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 klist_node node; + struct attribute_container *cont; + struct device classdev; +}; + +static void internal_container_klist_get(struct klist_node *n) +{ + struct internal_container *ic = + container_of(n, struct internal_container, node); + get_device(&ic->classdev); +} + +static void internal_container_klist_put(struct klist_node *n) +{ + struct internal_container *ic = + container_of(n, struct internal_container, node); + put_device(&ic->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 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 LIST_HEAD(attribute_container_list); + +static DEFINE_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); + klist_init(&cont->containers,internal_container_klist_get, + internal_container_klist_put); + + mutex_lock(&attribute_container_mutex); + list_add_tail(&cont->node, &attribute_container_list); + mutex_unlock(&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; + mutex_lock(&attribute_container_mutex); + spin_lock(&cont->containers.k_lock); + if (!list_empty(&cont->containers.k_list)) + goto out; + retval = 0; + list_del(&cont->node); + out: + spin_unlock(&cont->containers.k_lock); + mutex_unlock(&attribute_container_mutex); + return retval; + +} +EXPORT_SYMBOL_GPL(attribute_container_unregister); + +/* private function used as class release */ +static void attribute_container_release(struct device *classdev) +{ + struct internal_container *ic + = container_of(classdev, struct internal_container, classdev); + struct device *dev = classdev->parent; + + 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 + * 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 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 device *)) +{ + struct attribute_container *cont; + + mutex_lock(&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 = kzalloc(sizeof(*ic), GFP_KERNEL); + if (!ic) { + dev_printk(KERN_ERR, dev, "failed to allocate class container\n"); + continue; + } + + ic->cont = cont; + device_initialize(&ic->classdev); + ic->classdev.parent = get_device(dev); + ic->classdev.class = cont->class; + cont->class->dev_release = attribute_container_release; + strcpy(ic->classdev.bus_id, dev->bus_id); + if (fn) + fn(cont, dev, &ic->classdev); + else + attribute_container_add_class_device(&ic->classdev); + klist_add_tail(&ic->node, &cont->containers); + } + mutex_unlock(&attribute_container_mutex); +} + +/* FIXME: can't break out of this unless klist_iter_exit is also + * called before doing the break + */ +#define klist_for_each_entry(pos, head, member, iter) \ + for (klist_iter_init(head, iter); (pos = ({ \ + struct klist_node *n = klist_next(iter); \ + n ? container_of(n, typeof(*pos), member) : \ + ({ klist_iter_exit(iter) ; NULL; }); \ + }) ) != NULL; ) + + +/** + * 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 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 + * 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 device *)) +{ + struct attribute_container *cont; + + mutex_lock(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + struct internal_container *ic; + struct klist_iter iter; + + if (attribute_container_no_classdevs(cont)) + continue; + + if (!cont->match(cont, dev)) + continue; + + klist_for_each_entry(ic, &cont->containers, node, &iter) { + if (dev != ic->classdev.parent) + continue; + klist_del(&ic->node); + if (fn) + fn(cont, dev, &ic->classdev); + else { + attribute_container_remove_attrs(&ic->classdev); + device_unregister(&ic->classdev); + } + } + } + mutex_unlock(&attribute_container_mutex); +} + +/** + * 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 device *)) +{ + struct attribute_container *cont; + + mutex_lock(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + struct internal_container *ic; + struct klist_iter iter; + + if (!cont->match(cont, dev)) + continue; + + if (attribute_container_no_classdevs(cont)) { + fn(cont, dev, NULL); + continue; + } + + klist_for_each_entry(ic, &cont->containers, node, &iter) { + if (dev == ic->classdev.parent) + fn(cont, dev, &ic->classdev); + } + } + mutex_unlock(&attribute_container_mutex); +} + +/** + * 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; + + mutex_lock(&attribute_container_mutex); + list_for_each_entry(cont, &attribute_container_list, node) { + if (cont->match(cont, dev)) + fn(cont, dev); + } + mutex_unlock(&attribute_container_mutex); +} + +/** + * 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 device *classdev) +{ + struct attribute_container *cont = + attribute_container_classdev_to_container(classdev); + struct device_attribute **attrs = cont->attrs; + int i, error; + + BUG_ON(attrs && cont->grp); + + if (!attrs && !cont->grp) + return 0; + + if (cont->grp) + return sysfs_create_group(&classdev->kobj, cont->grp); + + for (i = 0; attrs[i]; i++) { + error = device_create_file(classdev, attrs[i]); + if (error) + return error; + } + + return 0; +} + +/** + * attribute_container_add_class_device - same function as device_add + * + * @classdev: the class device to add + * + * This performs essentially the same function as 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 device *classdev) +{ + int error = device_add(classdev); + if (error) + return error; + return attribute_container_add_attrs(classdev); +} + +/** + * 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 device *classdev) +{ + return attribute_container_add_class_device(classdev); +} + +/** + * attribute_container_remove_attrs - remove any attribute files + * + * @classdev: The class device to remove the files from + * + */ +void +attribute_container_remove_attrs(struct device *classdev) +{ + struct attribute_container *cont = + attribute_container_classdev_to_container(classdev); + struct device_attribute **attrs = cont->attrs; + int i; + + if (!attrs && !cont->grp) + return; + + if (cont->grp) { + sysfs_remove_group(&classdev->kobj, cont->grp); + return ; + } + + for (i = 0; attrs[i]; i++) + device_remove_file(classdev, attrs[i]); +} + +/** + * 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 + * device_del. + */ +void +attribute_container_class_device_del(struct device *classdev) +{ + attribute_container_remove_attrs(classdev); + device_del(classdev); +} + +/** + * attribute_container_find_class_device - find the corresponding class_device + * + * @cont: the container + * @dev: the generic device + * + * Looks up the device in the container's list of class devices and returns + * the corresponding class_device. + */ +struct device * +attribute_container_find_class_device(struct attribute_container *cont, + struct device *dev) +{ + struct device *cdev = NULL; + struct internal_container *ic; + struct klist_iter iter; + + klist_for_each_entry(ic, &cont->containers, node, &iter) { + if (ic->classdev.parent == dev) { + cdev = &ic->classdev; + /* FIXME: must exit iterator then break */ + klist_iter_exit(&iter); + break; + } + } + + return cdev; +} +EXPORT_SYMBOL_GPL(attribute_container_find_class_device); diff --git a/drivers/base/base.h b/drivers/base/base.h new file mode 100644 index 0000000..0a5f055 --- /dev/null +++ b/drivers/base/base.h @@ -0,0 +1,107 @@ + +/** + * struct bus_type_private - structure to hold the private to the driver core portions of the bus_type structure. + * + * @subsys - the struct kset that defines this bus. This is the main kobject + * @drivers_kset - the list of drivers associated with this bus + * @devices_kset - the list of devices associated with this bus + * @klist_devices - the klist to iterate over the @devices_kset + * @klist_drivers - the klist to iterate over the @drivers_kset + * @bus_notifier - the bus notifier list for anything that cares about things + * on this bus. + * @bus - pointer back to the struct bus_type that this structure is associated + * with. + * + * This structure is the one that is the actual kobject allowing struct + * bus_type to be statically allocated safely. Nothing outside of the driver + * core should ever touch these fields. + */ +struct bus_type_private { + struct kset subsys; + struct kset *drivers_kset; + struct kset *devices_kset; + struct klist klist_devices; + struct klist klist_drivers; + struct blocking_notifier_head bus_notifier; + unsigned int drivers_autoprobe:1; + struct bus_type *bus; +}; + +struct driver_private { + struct kobject kobj; + struct klist klist_devices; + struct klist_node knode_bus; + struct module_kobject *mkobj; + struct device_driver *driver; +}; +#define to_driver(obj) container_of(obj, struct driver_private, kobj) + + +/** + * struct class_private - structure to hold the private to the driver core portions of the class structure. + * + * @class_subsys - the struct kset that defines this class. This is the main kobject + * @class_devices - list of devices associated with this class + * @class_interfaces - list of class_interfaces associated with this class + * @class_dirs - "glue" directory for virtual devices associated with this class + * @class_mutex - mutex to protect the children, devices, and interfaces lists. + * @class - pointer back to the struct class that this structure is associated + * with. + * + * This structure is the one that is the actual kobject allowing struct + * class to be statically allocated safely. Nothing outside of the driver + * core should ever touch these fields. + */ +struct class_private { + struct kset class_subsys; + struct klist class_devices; + struct list_head class_interfaces; + struct kset class_dirs; + struct mutex class_mutex; + struct class *class; +}; +#define to_class(obj) \ + container_of(obj, struct class_private, class_subsys.kobj) + +/* initialisation functions */ +extern int devices_init(void); +extern int buses_init(void); +extern int classes_init(void); +extern int firmware_init(void); +#ifdef CONFIG_SYS_HYPERVISOR +extern int hypervisor_init(void); +#else +static inline int hypervisor_init(void) { return 0; } +#endif +extern int platform_bus_init(void); +extern int system_bus_init(void); +extern int cpu_dev_init(void); + +extern int bus_add_device(struct device *dev); +extern void bus_attach_device(struct device *dev); +extern void bus_remove_device(struct device *dev); + +extern int bus_add_driver(struct device_driver *drv); +extern void bus_remove_driver(struct device_driver *drv); + +extern void driver_detach(struct device_driver *drv); +extern int driver_probe_device(struct device_driver *drv, struct device *dev); + +extern void sysdev_shutdown(void); +extern int sysdev_suspend(pm_message_t state); +extern int sysdev_resume(void); + +extern char *make_class_name(const char *name, struct kobject *kobj); + +extern int devres_release_all(struct device *dev); + +extern struct kset *devices_kset; + +#if defined(CONFIG_MODULES) && defined(CONFIG_SYSFS) +extern void module_add_driver(struct module *mod, struct device_driver *drv); +extern void module_remove_driver(struct device_driver *drv); +#else +static inline void module_add_driver(struct module *mod, + struct device_driver *drv) { } +static inline void module_remove_driver(struct device_driver *drv) { } +#endif diff --git a/drivers/base/bus.c b/drivers/base/bus.c new file mode 100644 index 0000000..5aee1c0 --- /dev/null +++ b/drivers/base/bus.c @@ -0,0 +1,1039 @@ +/* + * bus.c - bus driver management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2007 Novell Inc. + * + * This file is released under the GPLv2 + * + */ + +#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_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr) +#define to_bus(obj) container_of(obj, struct bus_type_private, subsys.kobj) + +/* + * sysfs bindings for drivers + */ + +#define to_drv_attr(_attr) container_of(_attr, struct driver_attribute, attr) + + +static int __must_check bus_rescan_devices_helper(struct device *dev, + void *data); + +static struct bus_type *bus_get(struct bus_type *bus) +{ + if (bus) { + kset_get(&bus->p->subsys); + return bus; + } + return NULL; +} + +static void bus_put(struct bus_type *bus) +{ + if (bus) + kset_put(&bus->p->subsys); +} + +static ssize_t drv_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct driver_attribute *drv_attr = to_drv_attr(attr); + struct driver_private *drv_priv = to_driver(kobj); + ssize_t ret = -EIO; + + if (drv_attr->show) + ret = drv_attr->show(drv_priv->driver, 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 driver_private *drv_priv = to_driver(kobj); + ssize_t ret = -EIO; + + if (drv_attr->store) + ret = drv_attr->store(drv_priv->driver, 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 driver_private *drv_priv = to_driver(kobj); + + pr_debug("driver: '%s': %s\n", kobject_name(kobj), __func__); + kfree(drv_priv); +} + +static struct kobj_type driver_ktype = { + .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_private *bus_priv = to_bus(kobj); + ssize_t ret = 0; + + if (bus_attr->show) + ret = bus_attr->show(bus_priv->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_private *bus_priv = to_bus(kobj); + ssize_t ret = 0; + + if (bus_attr->store) + ret = bus_attr->store(bus_priv->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 (bus_get(bus)) { + error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr); + bus_put(bus); + } else + error = -EINVAL; + return error; +} +EXPORT_SYMBOL_GPL(bus_create_file); + +void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr) +{ + if (bus_get(bus)) { + sysfs_remove_file(&bus->p->subsys.kobj, &attr->attr); + bus_put(bus); + } +} +EXPORT_SYMBOL_GPL(bus_remove_file); + +static struct kobj_type bus_ktype = { + .sysfs_ops = &bus_sysfs_ops, +}; + +static int bus_uevent_filter(struct kset *kset, struct kobject *kobj) +{ + struct kobj_type *ktype = get_ktype(kobj); + + if (ktype == &bus_ktype) + return 1; + return 0; +} + +static struct kset_uevent_ops bus_uevent_ops = { + .filter = bus_uevent_filter, +}; + +static struct kset *bus_kset; + + +#ifdef CONFIG_HOTPLUG +/* Manually detach a device from its associated driver. */ +static ssize_t driver_unbind(struct device_driver *drv, + const char *buf, size_t count) +{ + struct bus_type *bus = bus_get(drv->bus); + struct device *dev; + int err = -ENODEV; + + dev = bus_find_device_by_name(bus, NULL, buf); + if (dev && dev->driver == drv) { + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + device_release_driver(dev); + if (dev->parent) + up(&dev->parent->sem); + err = count; + } + put_device(dev); + bus_put(bus); + return err; +} +static DRIVER_ATTR(unbind, S_IWUSR, NULL, driver_unbind); + +/* + * Manually attach a device to a driver. + * Note: the driver must want to bind to the device, + * it is not possible to override the driver's id table. + */ +static ssize_t driver_bind(struct device_driver *drv, + const char *buf, size_t count) +{ + struct bus_type *bus = bus_get(drv->bus); + struct device *dev; + int err = -ENODEV; + + dev = bus_find_device_by_name(bus, NULL, buf); + if (dev && dev->driver == NULL) { + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + down(&dev->sem); + err = driver_probe_device(drv, dev); + up(&dev->sem); + if (dev->parent) + up(&dev->parent->sem); + + if (err > 0) { + /* success */ + err = count; + } else if (err == 0) { + /* driver didn't accept device */ + err = -ENODEV; + } + } + put_device(dev); + bus_put(bus); + return err; +} +static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind); + +static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf) +{ + return sprintf(buf, "%d\n", bus->p->drivers_autoprobe); +} + +static ssize_t store_drivers_autoprobe(struct bus_type *bus, + const char *buf, size_t count) +{ + if (buf[0] == '0') + bus->p->drivers_autoprobe = 0; + else + bus->p->drivers_autoprobe = 1; + return count; +} + +static ssize_t store_drivers_probe(struct bus_type *bus, + const char *buf, size_t count) +{ + struct device *dev; + + dev = bus_find_device_by_name(bus, NULL, buf); + if (!dev) + return -ENODEV; + if (bus_rescan_devices_helper(dev, NULL) != 0) + return -EINVAL; + return count; +} +#endif + +static struct device *next_device(struct klist_iter *i) +{ + struct klist_node *n = klist_next(i); + return n ? container_of(n, struct device, knode_bus) : NULL; +} + +/** + * 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 *)) +{ + struct klist_iter i; + struct device *dev; + int error = 0; + + if (!bus) + return -EINVAL; + + klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->knode_bus : NULL)); + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); + return error; +} +EXPORT_SYMBOL_GPL(bus_for_each_dev); + +/** + * bus_find_device - device iterator for locating a particular device. + * @bus: bus type + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the bus_for_each_dev() function above, but it + * returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct device *bus_find_device(struct bus_type *bus, + struct device *start, void *data, + int (*match)(struct device *dev, void *data)) +{ + struct klist_iter i; + struct device *dev; + + if (!bus) + return NULL; + + klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->knode_bus : NULL)); + while ((dev = next_device(&i))) + if (match(dev, data) && get_device(dev)) + break; + klist_iter_exit(&i); + return dev; +} +EXPORT_SYMBOL_GPL(bus_find_device); + +static int match_name(struct device *dev, void *data) +{ + const char *name = data; + + return sysfs_streq(name, dev->bus_id); +} + +/** + * bus_find_device_by_name - device iterator for locating a particular device of a specific name + * @bus: bus type + * @start: Device to begin with + * @name: name of the device to match + * + * This is similar to the bus_find_device() function above, but it handles + * searching by a name automatically, no need to write another strcmp matching + * function. + */ +struct device *bus_find_device_by_name(struct bus_type *bus, + struct device *start, const char *name) +{ + return bus_find_device(bus, start, (void *)name, match_name); +} +EXPORT_SYMBOL_GPL(bus_find_device_by_name); + +static struct device_driver *next_driver(struct klist_iter *i) +{ + struct klist_node *n = klist_next(i); + struct driver_private *drv_priv; + + if (n) { + drv_priv = container_of(n, struct driver_private, knode_bus); + return drv_priv->driver; + } + return NULL; +} + +/** + * 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 *)) +{ + struct klist_iter i; + struct device_driver *drv; + int error = 0; + + if (!bus) + return -EINVAL; + + klist_iter_init_node(&bus->p->klist_drivers, &i, + start ? &start->p->knode_bus : NULL); + while ((drv = next_driver(&i)) && !error) + error = fn(drv, data); + klist_iter_exit(&i); + return error; +} +EXPORT_SYMBOL_GPL(bus_for_each_drv); + +static int device_add_attrs(struct bus_type *bus, struct device *dev) +{ + int error = 0; + int i; + + if (!bus->dev_attrs) + return 0; + + for (i = 0; attr_name(bus->dev_attrs[i]); i++) { + error = device_create_file(dev, &bus->dev_attrs[i]); + if (error) { + while (--i >= 0) + device_remove_file(dev, &bus->dev_attrs[i]); + break; + } + } + return error; +} + +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]); + } +} + +#ifdef CONFIG_SYSFS_DEPRECATED +static int make_deprecated_bus_links(struct device *dev) +{ + return sysfs_create_link(&dev->kobj, + &dev->bus->p->subsys.kobj, "bus"); +} + +static void remove_deprecated_bus_links(struct device *dev) +{ + sysfs_remove_link(&dev->kobj, "bus"); +} +#else +static inline int make_deprecated_bus_links(struct device *dev) { return 0; } +static inline void remove_deprecated_bus_links(struct device *dev) { } +#endif + +/** + * bus_add_device - add device to bus + * @dev: device being added + * + * - Add the device to its bus's list of devices. + * - Create link to device's bus. + */ +int bus_add_device(struct device *dev) +{ + struct bus_type *bus = bus_get(dev->bus); + int error = 0; + + if (bus) { + pr_debug("bus: '%s': add device %s\n", bus->name, dev->bus_id); + error = device_add_attrs(bus, dev); + if (error) + goto out_put; + error = sysfs_create_link(&bus->p->devices_kset->kobj, + &dev->kobj, dev->bus_id); + if (error) + goto out_id; + error = sysfs_create_link(&dev->kobj, + &dev->bus->p->subsys.kobj, "subsystem"); + if (error) + goto out_subsys; + error = make_deprecated_bus_links(dev); + if (error) + goto out_deprecated; + } + return 0; + +out_deprecated: + sysfs_remove_link(&dev->kobj, "subsystem"); +out_subsys: + sysfs_remove_link(&bus->p->devices_kset->kobj, dev->bus_id); +out_id: + device_remove_attrs(bus, dev); +out_put: + bus_put(dev->bus); + return error; +} + +/** + * bus_attach_device - add device to bus + * @dev: device tried to attach to a driver + * + * - Add device to bus's list of devices. + * - Try to attach to driver. + */ +void bus_attach_device(struct device *dev) +{ + struct bus_type *bus = dev->bus; + int ret = 0; + + if (bus) { + if (bus->p->drivers_autoprobe) + ret = device_attach(dev); + WARN_ON(ret < 0); + if (ret >= 0) + klist_add_tail(&dev->knode_bus, &bus->p->klist_devices); + } +} + +/** + * 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, "subsystem"); + remove_deprecated_bus_links(dev); + sysfs_remove_link(&dev->bus->p->devices_kset->kobj, + dev->bus_id); + device_remove_attrs(dev->bus, dev); + if (klist_node_attached(&dev->knode_bus)) + klist_del(&dev->knode_bus); + + pr_debug("bus: '%s': remove device %s\n", + dev->bus->name, dev->bus_id); + device_release_driver(dev); + bus_put(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]); + } +} + +#ifdef CONFIG_HOTPLUG +/* + * Thanks to drivers making their tables __devinit, we can't allow manual + * bind and unbind from userspace unless CONFIG_HOTPLUG is enabled. + */ +static int __must_check add_bind_files(struct device_driver *drv) +{ + int ret; + + ret = driver_create_file(drv, &driver_attr_unbind); + if (ret == 0) { + ret = driver_create_file(drv, &driver_attr_bind); + if (ret) + driver_remove_file(drv, &driver_attr_unbind); + } + return ret; +} + +static void remove_bind_files(struct device_driver *drv) +{ + driver_remove_file(drv, &driver_attr_bind); + driver_remove_file(drv, &driver_attr_unbind); +} + +static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe); +static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO, + show_drivers_autoprobe, store_drivers_autoprobe); + +static int add_probe_files(struct bus_type *bus) +{ + int retval; + + retval = bus_create_file(bus, &bus_attr_drivers_probe); + if (retval) + goto out; + + retval = bus_create_file(bus, &bus_attr_drivers_autoprobe); + if (retval) + bus_remove_file(bus, &bus_attr_drivers_probe); +out: + return retval; +} + +static void remove_probe_files(struct bus_type *bus) +{ + bus_remove_file(bus, &bus_attr_drivers_autoprobe); + bus_remove_file(bus, &bus_attr_drivers_probe); +} +#else +static inline int add_bind_files(struct device_driver *drv) { return 0; } +static inline void remove_bind_files(struct device_driver *drv) {} +static inline int add_probe_files(struct bus_type *bus) { return 0; } +static inline void remove_probe_files(struct bus_type *bus) {} +#endif + +static ssize_t driver_uevent_store(struct device_driver *drv, + const char *buf, size_t count) +{ + enum kobject_action action; + + if (kobject_action_type(buf, count, &action) == 0) + kobject_uevent(&drv->p->kobj, action); + return count; +} +static DRIVER_ATTR(uevent, S_IWUSR, NULL, driver_uevent_store); + +/** + * bus_add_driver - Add a driver to the bus. + * @drv: driver. + */ +int bus_add_driver(struct device_driver *drv) +{ + struct bus_type *bus; + struct driver_private *priv; + int error = 0; + + bus = bus_get(drv->bus); + if (!bus) + return -EINVAL; + + pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + error = -ENOMEM; + goto out_put_bus; + } + klist_init(&priv->klist_devices, NULL, NULL); + priv->driver = drv; + drv->p = priv; + priv->kobj.kset = bus->p->drivers_kset; + error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, + "%s", drv->name); + if (error) + goto out_unregister; + + if (drv->bus->p->drivers_autoprobe) { + error = driver_attach(drv); + if (error) + goto out_unregister; + } + klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); + module_add_driver(drv->owner, drv); + + error = driver_create_file(drv, &driver_attr_uevent); + if (error) { + printk(KERN_ERR "%s: uevent attr (%s) failed\n", + __func__, drv->name); + } + error = driver_add_attrs(bus, drv); + if (error) { + /* How the hell do we get out of this pickle? Give up */ + printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n", + __func__, drv->name); + } + error = add_bind_files(drv); + if (error) { + /* Ditto */ + printk(KERN_ERR "%s: add_bind_files(%s) failed\n", + __func__, drv->name); + } + + kobject_uevent(&priv->kobj, KOBJ_ADD); + return error; +out_unregister: + kobject_put(&priv->kobj); +out_put_bus: + bus_put(bus); + 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) + return; + + remove_bind_files(drv); + driver_remove_attrs(drv->bus, drv); + driver_remove_file(drv, &driver_attr_uevent); + klist_remove(&drv->p->knode_bus); + pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name); + driver_detach(drv); + module_remove_driver(drv); + kobject_put(&drv->p->kobj); + bus_put(drv->bus); +} + +/* Helper for bus_rescan_devices's iter */ +static int __must_check bus_rescan_devices_helper(struct device *dev, + void *data) +{ + int ret = 0; + + if (!dev->driver) { + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + ret = device_attach(dev); + if (dev->parent) + up(&dev->parent->sem); + } + return ret < 0 ? ret : 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 by calling device_attach() for the unbound devices. + */ +int bus_rescan_devices(struct bus_type *bus) +{ + return bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper); +} +EXPORT_SYMBOL_GPL(bus_rescan_devices); + +/** + * device_reprobe - remove driver for a device and probe for a new driver + * @dev: the device to reprobe + * + * This function detaches the attached driver (if any) for the given + * device and restarts the driver probing process. It is intended + * to use if probing criteria changed during a devices lifetime and + * driver attachment should change accordingly. + */ +int device_reprobe(struct device *dev) +{ + if (dev->driver) { + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + device_release_driver(dev); + if (dev->parent) + up(&dev->parent->sem); + } + return bus_rescan_devices_helper(dev, NULL); +} +EXPORT_SYMBOL_GPL(device_reprobe); + +/** + * 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. + */ +#if 0 +struct bus_type *find_bus(char *name) +{ + struct kobject *k = kset_find_obj(bus_kset, name); + return k ? to_bus(k) : NULL; +} +#endif /* 0 */ + + +/** + * 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++) { + error = bus_create_file(bus, &bus->bus_attrs[i]); + if (error) + 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]); + } +} + +static void klist_devices_get(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_bus); + + get_device(dev); +} + +static void klist_devices_put(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_bus); + + put_device(dev); +} + +static ssize_t bus_uevent_store(struct bus_type *bus, + const char *buf, size_t count) +{ + enum kobject_action action; + + if (kobject_action_type(buf, count, &action) == 0) + kobject_uevent(&bus->p->subsys.kobj, action); + return count; +} +static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store); + +/** + * 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; + struct bus_type_private *priv; + + priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = bus; + bus->p = priv; + + BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); + + retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); + if (retval) + goto out; + + priv->subsys.kobj.kset = bus_kset; + priv->subsys.kobj.ktype = &bus_ktype; + priv->drivers_autoprobe = 1; + + retval = kset_register(&priv->subsys); + if (retval) + goto out; + + retval = bus_create_file(bus, &bus_attr_uevent); + if (retval) + goto bus_uevent_fail; + + priv->devices_kset = kset_create_and_add("devices", NULL, + &priv->subsys.kobj); + if (!priv->devices_kset) { + retval = -ENOMEM; + goto bus_devices_fail; + } + + priv->drivers_kset = kset_create_and_add("drivers", NULL, + &priv->subsys.kobj); + if (!priv->drivers_kset) { + retval = -ENOMEM; + goto bus_drivers_fail; + } + + klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); + klist_init(&priv->klist_drivers, NULL, NULL); + + retval = add_probe_files(bus); + if (retval) + goto bus_probe_files_fail; + + retval = bus_add_attrs(bus); + if (retval) + goto bus_attrs_fail; + + pr_debug("bus: '%s': registered\n", bus->name); + return 0; + +bus_attrs_fail: + remove_probe_files(bus); +bus_probe_files_fail: + kset_unregister(bus->p->drivers_kset); +bus_drivers_fail: + kset_unregister(bus->p->devices_kset); +bus_devices_fail: + bus_remove_file(bus, &bus_attr_uevent); +bus_uevent_fail: + kset_unregister(&bus->p->subsys); + kfree(bus->p); +out: + return retval; +} +EXPORT_SYMBOL_GPL(bus_register); + +/** + * bus_unregister - remove a bus from the system + * @bus: bus. + * + * Unregister the child subsystems and the bus itself. + * Finally, we call bus_put() to release the refcount + */ +void bus_unregister(struct bus_type *bus) +{ + pr_debug("bus: '%s': unregistering\n", bus->name); + bus_remove_attrs(bus); + remove_probe_files(bus); + kset_unregister(bus->p->drivers_kset); + kset_unregister(bus->p->devices_kset); + bus_remove_file(bus, &bus_attr_uevent); + kset_unregister(&bus->p->subsys); + kfree(bus->p); +} +EXPORT_SYMBOL_GPL(bus_unregister); + +int bus_register_notifier(struct bus_type *bus, struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&bus->p->bus_notifier, nb); +} +EXPORT_SYMBOL_GPL(bus_register_notifier); + +int bus_unregister_notifier(struct bus_type *bus, struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&bus->p->bus_notifier, nb); +} +EXPORT_SYMBOL_GPL(bus_unregister_notifier); + +struct kset *bus_get_kset(struct bus_type *bus) +{ + return &bus->p->subsys; +} +EXPORT_SYMBOL_GPL(bus_get_kset); + +struct klist *bus_get_device_klist(struct bus_type *bus) +{ + return &bus->p->klist_devices; +} +EXPORT_SYMBOL_GPL(bus_get_device_klist); + +/* + * Yes, this forcably breaks the klist abstraction temporarily. It + * just wants to sort the klist, not change reference counts and + * take/drop locks rapidly in the process. It does all this while + * holding the lock for the list, so objects can't otherwise be + * added/removed while we're swizzling. + */ +static void device_insertion_sort_klist(struct device *a, struct list_head *list, + int (*compare)(const struct device *a, + const struct device *b)) +{ + struct list_head *pos; + struct klist_node *n; + struct device *b; + + list_for_each(pos, list) { + n = container_of(pos, struct klist_node, n_node); + b = container_of(n, struct device, knode_bus); + if (compare(a, b) <= 0) { + list_move_tail(&a->knode_bus.n_node, + &b->knode_bus.n_node); + return; + } + } + list_move_tail(&a->knode_bus.n_node, list); +} + +void bus_sort_breadthfirst(struct bus_type *bus, + int (*compare)(const struct device *a, + const struct device *b)) +{ + LIST_HEAD(sorted_devices); + struct list_head *pos, *tmp; + struct klist_node *n; + struct device *dev; + struct klist *device_klist; + + device_klist = bus_get_device_klist(bus); + + spin_lock(&device_klist->k_lock); + list_for_each_safe(pos, tmp, &device_klist->k_list) { + n = container_of(pos, struct klist_node, n_node); + dev = container_of(n, struct device, knode_bus); + device_insertion_sort_klist(dev, &sorted_devices, compare); + } + list_splice(&sorted_devices, &device_klist->k_list); + spin_unlock(&device_klist->k_lock); +} +EXPORT_SYMBOL_GPL(bus_sort_breadthfirst); + +int __init buses_init(void) +{ + bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL); + if (!bus_kset) + return -ENOMEM; + return 0; +} diff --git a/drivers/base/class.c b/drivers/base/class.c new file mode 100644 index 0000000..eb85e43 --- /dev/null +++ b/drivers/base/class.c @@ -0,0 +1,505 @@ +/* + * 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/device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/kdev_t.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/genhd.h> +#include <linux/mutex.h> +#include "base.h" + +#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr) + +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_private *cp = to_class(kobj); + ssize_t ret = -EIO; + + if (class_attr->show) + ret = class_attr->show(cp->class, 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_private *cp = to_class(kobj); + ssize_t ret = -EIO; + + if (class_attr->store) + ret = class_attr->store(cp->class, buf, count); + return ret; +} + +static void class_release(struct kobject *kobj) +{ + struct class_private *cp = to_class(kobj); + struct class *class = cp->class; + + 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 class_ktype = { + .sysfs_ops = &class_sysfs_ops, + .release = class_release, +}; + +/* Hotplug events for classes go to the class class_subsys */ +static struct kset *class_kset; + + +int class_create_file(struct class *cls, const struct class_attribute *attr) +{ + int error; + if (cls) + error = sysfs_create_file(&cls->p->class_subsys.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->p->class_subsys.kobj, &attr->attr); +} + +static struct class *class_get(struct class *cls) +{ + if (cls) + kset_get(&cls->p->class_subsys); + return cls; +} + +static void class_put(struct class *cls) +{ + if (cls) + kset_put(&cls->p->class_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 error; + } + } +done: + return error; +error: + 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]); + } +} + +static void klist_class_dev_get(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_class); + + get_device(dev); +} + +static void klist_class_dev_put(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_class); + + put_device(dev); +} + +int __class_register(struct class *cls, struct lock_class_key *key) +{ + struct class_private *cp; + int error; + + pr_debug("device class '%s': registering\n", cls->name); + + cp = kzalloc(sizeof(*cp), GFP_KERNEL); + if (!cp) + return -ENOMEM; + klist_init(&cp->class_devices, klist_class_dev_get, klist_class_dev_put); + INIT_LIST_HEAD(&cp->class_interfaces); + kset_init(&cp->class_dirs); + __mutex_init(&cp->class_mutex, "struct class mutex", key); + error = kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name); + if (error) { + kfree(cp); + return error; + } + + /* set the default /sys/dev directory for devices of this class */ + if (!cls->dev_kobj) + cls->dev_kobj = sysfs_dev_char_kobj; + +#if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK) + /* let the block class directory show up in the root of sysfs */ + if (cls != &block_class) + cp->class_subsys.kobj.kset = class_kset; +#else + cp->class_subsys.kobj.kset = class_kset; +#endif + cp->class_subsys.kobj.ktype = &class_ktype; + cp->class = cls; + cls->p = cp; + + error = kset_register(&cp->class_subsys); + if (error) { + kfree(cp); + return error; + } + error = add_class_attrs(class_get(cls)); + class_put(cls); + return error; +} +EXPORT_SYMBOL_GPL(__class_register); + +void class_unregister(struct class *cls) +{ + pr_debug("device class '%s': unregistering\n", cls->name); + remove_class_attrs(cls); + kset_unregister(&cls->p->class_subsys); +} + +static void class_create_release(struct class *cls) +{ + pr_debug("%s called for %s\n", __func__, cls->name); + kfree(cls); +} + +/** + * class_create - create a struct class structure + * @owner: pointer to the module that is to "own" this struct class + * @name: pointer to a string for the name of this class. + * @key: the lock_class_key for this class; used by mutex lock debugging + * + * This is used to create a struct class pointer that can then be used + * in calls to device_create(). + * + * Note, the pointer created here is to be destroyed when finished by + * making a call to class_destroy(). + */ +struct class *__class_create(struct module *owner, const char *name, + struct lock_class_key *key) +{ + struct class *cls; + int retval; + + cls = kzalloc(sizeof(*cls), GFP_KERNEL); + if (!cls) { + retval = -ENOMEM; + goto error; + } + + cls->name = name; + cls->owner = owner; + cls->class_release = class_create_release; + + retval = __class_register(cls, key); + if (retval) + goto error; + + return cls; + +error: + kfree(cls); + return ERR_PTR(retval); +} +EXPORT_SYMBOL_GPL(__class_create); + +/** + * class_destroy - destroys a struct class structure + * @cls: pointer to the struct class that is to be destroyed + * + * Note, the pointer to be destroyed must have been created with a call + * to class_create(). + */ +void class_destroy(struct class *cls) +{ + if ((cls == NULL) || (IS_ERR(cls))) + return; + + class_unregister(cls); +} + +#ifdef CONFIG_SYSFS_DEPRECATED +char *make_class_name(const char *name, struct kobject *kobj) +{ + char *class_name; + int size; + + size = strlen(name) + strlen(kobject_name(kobj)) + 2; + + class_name = kmalloc(size, GFP_KERNEL); + if (!class_name) + return NULL; + + strcpy(class_name, name); + strcat(class_name, ":"); + strcat(class_name, kobject_name(kobj)); + return class_name; +} +#endif + +/** + * class_dev_iter_init - initialize class device iterator + * @iter: class iterator to initialize + * @class: the class we wanna iterate over + * @start: the device to start iterating from, if any + * @type: device_type of the devices to iterate over, NULL for all + * + * Initialize class iterator @iter such that it iterates over devices + * of @class. If @start is set, the list iteration will start there, + * otherwise if it is NULL, the iteration starts at the beginning of + * the list. + */ +void class_dev_iter_init(struct class_dev_iter *iter, struct class *class, + struct device *start, const struct device_type *type) +{ + struct klist_node *start_knode = NULL; + + if (start) + start_knode = &start->knode_class; + klist_iter_init_node(&class->p->class_devices, &iter->ki, start_knode); + iter->type = type; +} +EXPORT_SYMBOL_GPL(class_dev_iter_init); + +/** + * class_dev_iter_next - iterate to the next device + * @iter: class iterator to proceed + * + * Proceed @iter to the next device and return it. Returns NULL if + * iteration is complete. + * + * The returned device is referenced and won't be released till + * iterator is proceed to the next device or exited. The caller is + * free to do whatever it wants to do with the device including + * calling back into class code. + */ +struct device *class_dev_iter_next(struct class_dev_iter *iter) +{ + struct klist_node *knode; + struct device *dev; + + while (1) { + knode = klist_next(&iter->ki); + if (!knode) + return NULL; + dev = container_of(knode, struct device, knode_class); + if (!iter->type || iter->type == dev->type) + return dev; + } +} +EXPORT_SYMBOL_GPL(class_dev_iter_next); + +/** + * class_dev_iter_exit - finish iteration + * @iter: class iterator to finish + * + * Finish an iteration. Always call this function after iteration is + * complete whether the iteration ran till the end or not. + */ +void class_dev_iter_exit(struct class_dev_iter *iter) +{ + klist_iter_exit(&iter->ki); +} +EXPORT_SYMBOL_GPL(class_dev_iter_exit); + +/** + * class_for_each_device - device iterator + * @class: the class we're iterating + * @start: the device to start with in the list, if any. + * @data: data for the callback + * @fn: function to be called for each device + * + * Iterate over @class's list of devices, and call @fn for each, + * passing it @data. If @start is set, the list iteration will start + * there, otherwise if it is NULL, the iteration starts at the + * beginning of the list. + * + * We check the return of @fn each time. If it returns anything + * other than 0, we break out and return that value. + * + * @fn is allowed to do anything including calling back into class + * code. There's no locking restriction. + */ +int class_for_each_device(struct class *class, struct device *start, + void *data, int (*fn)(struct device *, void *)) +{ + struct class_dev_iter iter; + struct device *dev; + int error = 0; + + if (!class) + return -EINVAL; + if (!class->p) { + WARN(1, "%s called for class '%s' before it was initialized", + __func__, class->name); + return -EINVAL; + } + + class_dev_iter_init(&iter, class, start, NULL); + while ((dev = class_dev_iter_next(&iter))) { + error = fn(dev, data); + if (error) + break; + } + class_dev_iter_exit(&iter); + + return error; +} +EXPORT_SYMBOL_GPL(class_for_each_device); + +/** + * class_find_device - device iterator for locating a particular device + * @class: the class we're iterating + * @start: Device to begin with + * @data: data for the match function + * @match: function to check device + * + * This is similar to the class_for_each_dev() function above, but it + * returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + * + * Note, you will need to drop the reference with put_device() after use. + * + * @fn is allowed to do anything including calling back into class + * code. There's no locking restriction. + */ +struct device *class_find_device(struct class *class, struct device *start, + void *data, + int (*match)(struct device *, void *)) +{ + struct class_dev_iter iter; + struct device *dev; + + if (!class) + return NULL; + if (!class->p) { + WARN(1, "%s called for class '%s' before it was initialized", + __func__, class->name); + return NULL; + } + + class_dev_iter_init(&iter, class, start, NULL); + while ((dev = class_dev_iter_next(&iter))) { + if (match(dev, data)) { + get_device(dev); + break; + } + } + class_dev_iter_exit(&iter); + + return dev; +} +EXPORT_SYMBOL_GPL(class_find_device); + +int class_interface_register(struct class_interface *class_intf) +{ + struct class *parent; + struct class_dev_iter iter; + struct device *dev; + + if (!class_intf || !class_intf->class) + return -ENODEV; + + parent = class_get(class_intf->class); + if (!parent) + return -EINVAL; + + mutex_lock(&parent->p->class_mutex); + list_add_tail(&class_intf->node, &parent->p->class_interfaces); + if (class_intf->add_dev) { + class_dev_iter_init(&iter, parent, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) + class_intf->add_dev(dev, class_intf); + class_dev_iter_exit(&iter); + } + mutex_unlock(&parent->p->class_mutex); + + return 0; +} + +void class_interface_unregister(struct class_interface *class_intf) +{ + struct class *parent = class_intf->class; + struct class_dev_iter iter; + struct device *dev; + + if (!parent) + return; + + mutex_lock(&parent->p->class_mutex); + list_del_init(&class_intf->node); + if (class_intf->remove_dev) { + class_dev_iter_init(&iter, parent, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) + class_intf->remove_dev(dev, class_intf); + class_dev_iter_exit(&iter); + } + mutex_unlock(&parent->p->class_mutex); + + class_put(parent); +} + +int __init classes_init(void) +{ + class_kset = kset_create_and_add("class", NULL, NULL); + if (!class_kset) + return -ENOMEM; + return 0; +} + +EXPORT_SYMBOL_GPL(class_create_file); +EXPORT_SYMBOL_GPL(class_remove_file); +EXPORT_SYMBOL_GPL(class_unregister); +EXPORT_SYMBOL_GPL(class_destroy); + +EXPORT_SYMBOL_GPL(class_interface_register); +EXPORT_SYMBOL_GPL(class_interface_unregister); diff --git a/drivers/base/core.c b/drivers/base/core.c new file mode 100644 index 0000000..8c2cc26 --- /dev/null +++ b/drivers/base/core.c @@ -0,0 +1,1527 @@ +/* + * 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 + * Copyright (c) 2006 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2006 Novell, Inc. + * + * This file is released under the GPLv2 + * + */ + +#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 <linux/kdev_t.h> +#include <linux/notifier.h> +#include <linux/genhd.h> +#include <linux/kallsyms.h> +#include <linux/semaphore.h> +#include <linux/mutex.h> + +#include "base.h" +#include "power/power.h" + +int (*platform_notify)(struct device *dev) = NULL; +int (*platform_notify_remove)(struct device *dev) = NULL; +static struct kobject *dev_kobj; +struct kobject *sysfs_dev_char_kobj; +struct kobject *sysfs_dev_block_kobj; + +#ifdef CONFIG_BLOCK +static inline int device_is_not_partition(struct device *dev) +{ + return !(dev->type == &part_type); +} +#else +static inline int device_is_not_partition(struct device *dev) +{ + return 1; +} +#endif + +/** + * dev_driver_string - Return a device's driver name, if at all possible + * @dev: struct device to get the name of + * + * Will return the device's driver's name if it is bound to a device. If + * the device is not bound to a device, it will return the name of the bus + * it is attached to. If it is not attached to a bus either, an empty + * string will be returned. + */ +const char *dev_driver_string(const struct device *dev) +{ + return dev->driver ? dev->driver->name : + (dev->bus ? dev->bus->name : + (dev->class ? dev->class->name : "")); +} +EXPORT_SYMBOL(dev_driver_string); + +#define to_dev(obj) container_of(obj, struct device, kobj) +#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) + +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 = -EIO; + + if (dev_attr->show) + ret = dev_attr->show(dev, dev_attr, buf); + if (ret >= (ssize_t)PAGE_SIZE) { + print_symbol("dev_attr_show: %s returned bad count\n", + (unsigned long)dev_attr->show); + } + 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 = -EIO; + + if (dev_attr->store) + ret = dev_attr->store(dev, dev_attr, 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 if (dev->type && dev->type->release) + dev->type->release(dev); + else if (dev->class && dev->class->dev_release) + dev->class->dev_release(dev); + else + WARN(1, KERN_ERR "Device '%s' does not have a release() " + "function, it is broken and must be fixed.\n", + dev->bus_id); +} + +static struct kobj_type device_ktype = { + .release = device_release, + .sysfs_ops = &dev_sysfs_ops, +}; + + +static int dev_uevent_filter(struct kset *kset, struct kobject *kobj) +{ + struct kobj_type *ktype = get_ktype(kobj); + + if (ktype == &device_ktype) { + struct device *dev = to_dev(kobj); + if (dev->uevent_suppress) + return 0; + if (dev->bus) + return 1; + if (dev->class) + return 1; + } + return 0; +} + +static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj) +{ + struct device *dev = to_dev(kobj); + + if (dev->bus) + return dev->bus->name; + if (dev->class) + return dev->class->name; + return NULL; +} + +static int dev_uevent(struct kset *kset, struct kobject *kobj, + struct kobj_uevent_env *env) +{ + struct device *dev = to_dev(kobj); + int retval = 0; + + /* add the major/minor if present */ + if (MAJOR(dev->devt)) { + add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt)); + add_uevent_var(env, "MINOR=%u", MINOR(dev->devt)); + } + + if (dev->type && dev->type->name) + add_uevent_var(env, "DEVTYPE=%s", dev->type->name); + + if (dev->driver) + add_uevent_var(env, "DRIVER=%s", dev->driver->name); + +#ifdef CONFIG_SYSFS_DEPRECATED + if (dev->class) { + struct device *parent = dev->parent; + + /* find first bus device in parent chain */ + while (parent && !parent->bus) + parent = parent->parent; + if (parent && parent->bus) { + const char *path; + + path = kobject_get_path(&parent->kobj, GFP_KERNEL); + if (path) { + add_uevent_var(env, "PHYSDEVPATH=%s", path); + kfree(path); + } + + add_uevent_var(env, "PHYSDEVBUS=%s", parent->bus->name); + + if (parent->driver) + add_uevent_var(env, "PHYSDEVDRIVER=%s", + parent->driver->name); + } + } else if (dev->bus) { + add_uevent_var(env, "PHYSDEVBUS=%s", dev->bus->name); + + if (dev->driver) + add_uevent_var(env, "PHYSDEVDRIVER=%s", + dev->driver->name); + } +#endif + + /* have the bus specific function add its stuff */ + if (dev->bus && dev->bus->uevent) { + retval = dev->bus->uevent(dev, env); + if (retval) + pr_debug("device: '%s': %s: bus uevent() returned %d\n", + dev->bus_id, __func__, retval); + } + + /* have the class specific function add its stuff */ + if (dev->class && dev->class->dev_uevent) { + retval = dev->class->dev_uevent(dev, env); + if (retval) + pr_debug("device: '%s': %s: class uevent() " + "returned %d\n", dev->bus_id, + __func__, retval); + } + + /* have the device type specific fuction add its stuff */ + if (dev->type && dev->type->uevent) { + retval = dev->type->uevent(dev, env); + if (retval) + pr_debug("device: '%s': %s: dev_type uevent() " + "returned %d\n", dev->bus_id, + __func__, retval); + } + + return retval; +} + +static struct kset_uevent_ops device_uevent_ops = { + .filter = dev_uevent_filter, + .name = dev_uevent_name, + .uevent = dev_uevent, +}; + +static ssize_t show_uevent(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct kobject *top_kobj; + struct kset *kset; + struct kobj_uevent_env *env = NULL; + int i; + size_t count = 0; + int retval; + + /* search the kset, the device belongs to */ + top_kobj = &dev->kobj; + while (!top_kobj->kset && top_kobj->parent) + top_kobj = top_kobj->parent; + if (!top_kobj->kset) + goto out; + + kset = top_kobj->kset; + if (!kset->uevent_ops || !kset->uevent_ops->uevent) + goto out; + + /* respect filter */ + if (kset->uevent_ops && kset->uevent_ops->filter) + if (!kset->uevent_ops->filter(kset, &dev->kobj)) + goto out; + + env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); + if (!env) + return -ENOMEM; + + /* let the kset specific function add its keys */ + retval = kset->uevent_ops->uevent(kset, &dev->kobj, env); + if (retval) + goto out; + + /* copy keys to file */ + for (i = 0; i < env->envp_idx; i++) + count += sprintf(&buf[count], "%s\n", env->envp[i]); +out: + kfree(env); + return count; +} + +static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + enum kobject_action action; + + if (kobject_action_type(buf, count, &action) == 0) { + kobject_uevent(&dev->kobj, action); + goto out; + } + + dev_err(dev, "uevent: unsupported action-string; this will " + "be ignored in a future kernel version\n"); + kobject_uevent(&dev->kobj, KOBJ_ADD); +out: + return count; +} + +static struct device_attribute uevent_attr = + __ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent); + +static int device_add_attributes(struct device *dev, + struct device_attribute *attrs) +{ + int error = 0; + int i; + + if (attrs) { + for (i = 0; attr_name(attrs[i]); i++) { + error = device_create_file(dev, &attrs[i]); + if (error) + break; + } + if (error) + while (--i >= 0) + device_remove_file(dev, &attrs[i]); + } + return error; +} + +static void device_remove_attributes(struct device *dev, + struct device_attribute *attrs) +{ + int i; + + if (attrs) + for (i = 0; attr_name(attrs[i]); i++) + device_remove_file(dev, &attrs[i]); +} + +static int device_add_groups(struct device *dev, + struct attribute_group **groups) +{ + int error = 0; + int i; + + if (groups) { + for (i = 0; groups[i]; i++) { + error = sysfs_create_group(&dev->kobj, groups[i]); + if (error) { + while (--i >= 0) + sysfs_remove_group(&dev->kobj, + groups[i]); + break; + } + } + } + return error; +} + +static void device_remove_groups(struct device *dev, + struct attribute_group **groups) +{ + int i; + + if (groups) + for (i = 0; groups[i]; i++) + sysfs_remove_group(&dev->kobj, groups[i]); +} + +static int device_add_attrs(struct device *dev) +{ + struct class *class = dev->class; + struct device_type *type = dev->type; + int error; + + if (class) { + error = device_add_attributes(dev, class->dev_attrs); + if (error) + return error; + } + + if (type) { + error = device_add_groups(dev, type->groups); + if (error) + goto err_remove_class_attrs; + } + + error = device_add_groups(dev, dev->groups); + if (error) + goto err_remove_type_groups; + + return 0; + + err_remove_type_groups: + if (type) + device_remove_groups(dev, type->groups); + err_remove_class_attrs: + if (class) + device_remove_attributes(dev, class->dev_attrs); + + return error; +} + +static void device_remove_attrs(struct device *dev) +{ + struct class *class = dev->class; + struct device_type *type = dev->type; + + device_remove_groups(dev, dev->groups); + + if (type) + device_remove_groups(dev, type->groups); + + if (class) + device_remove_attributes(dev, class->dev_attrs); +} + + +static ssize_t show_dev(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return print_dev_t(buf, dev->devt); +} + +static struct device_attribute devt_attr = + __ATTR(dev, S_IRUGO, show_dev, NULL); + +/* kset to create /sys/devices/ */ +struct kset *devices_kset; + +/** + * 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 (dev) + error = sysfs_create_file(&dev->kobj, &attr->attr); + 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 (dev) + sysfs_remove_file(&dev->kobj, &attr->attr); +} + +/** + * device_create_bin_file - create sysfs binary attribute file for device. + * @dev: device. + * @attr: device binary attribute descriptor. + */ +int device_create_bin_file(struct device *dev, struct bin_attribute *attr) +{ + int error = -EINVAL; + if (dev) + error = sysfs_create_bin_file(&dev->kobj, attr); + return error; +} +EXPORT_SYMBOL_GPL(device_create_bin_file); + +/** + * device_remove_bin_file - remove sysfs binary attribute file + * @dev: device. + * @attr: device binary attribute descriptor. + */ +void device_remove_bin_file(struct device *dev, struct bin_attribute *attr) +{ + if (dev) + sysfs_remove_bin_file(&dev->kobj, attr); +} +EXPORT_SYMBOL_GPL(device_remove_bin_file); + +/** + * device_schedule_callback_owner - helper to schedule a callback for a device + * @dev: device. + * @func: callback function to invoke later. + * @owner: module owning the callback routine + * + * Attribute methods must not unregister themselves or their parent device + * (which would amount to the same thing). Attempts to do so will deadlock, + * since unregistration is mutually exclusive with driver callbacks. + * + * Instead methods can call this routine, which will attempt to allocate + * and schedule a workqueue request to call back @func with @dev as its + * argument in the workqueue's process context. @dev will be pinned until + * @func returns. + * + * This routine is usually called via the inline device_schedule_callback(), + * which automatically sets @owner to THIS_MODULE. + * + * Returns 0 if the request was submitted, -ENOMEM if storage could not + * be allocated, -ENODEV if a reference to @owner isn't available. + * + * NOTE: This routine won't work if CONFIG_SYSFS isn't set! It uses an + * underlying sysfs routine (since it is intended for use by attribute + * methods), and if sysfs isn't available you'll get nothing but -ENOSYS. + */ +int device_schedule_callback_owner(struct device *dev, + void (*func)(struct device *), struct module *owner) +{ + return sysfs_schedule_callback(&dev->kobj, + (void (*)(void *)) func, dev, owner); +} +EXPORT_SYMBOL_GPL(device_schedule_callback_owner); + +static void klist_children_get(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_parent); + + get_device(dev); +} + +static void klist_children_put(struct klist_node *n) +{ + struct device *dev = container_of(n, struct device, knode_parent); + + put_device(dev); +} + +/** + * device_initialize - init device structure. + * @dev: device. + * + * This prepares the device for use by other layers by initializing + * its fields. + * It is the first half of device_register(), if called by + * that function, though it can also be called separately, so one + * may use @dev's fields. In particular, get_device()/put_device() + * may be used for reference counting of @dev after calling this + * function. + * + * NOTE: Use put_device() to give up your reference instead of freeing + * @dev directly once you have called this function. + */ +void device_initialize(struct device *dev) +{ + dev->kobj.kset = devices_kset; + kobject_init(&dev->kobj, &device_ktype); + klist_init(&dev->klist_children, klist_children_get, + klist_children_put); + INIT_LIST_HEAD(&dev->dma_pools); + init_MUTEX(&dev->sem); + spin_lock_init(&dev->devres_lock); + INIT_LIST_HEAD(&dev->devres_head); + device_init_wakeup(dev, 0); + device_pm_init(dev); + set_dev_node(dev, -1); +} + +#ifdef CONFIG_SYSFS_DEPRECATED +static struct kobject *get_device_parent(struct device *dev, + struct device *parent) +{ + /* class devices without a parent live in /sys/class/<classname>/ */ + if (dev->class && (!parent || parent->class != dev->class)) + return &dev->class->p->class_subsys.kobj; + /* all other devices keep their parent */ + else if (parent) + return &parent->kobj; + + return NULL; +} + +static inline void cleanup_device_parent(struct device *dev) {} +static inline void cleanup_glue_dir(struct device *dev, + struct kobject *glue_dir) {} +#else +static struct kobject *virtual_device_parent(struct device *dev) +{ + static struct kobject *virtual_dir = NULL; + + if (!virtual_dir) + virtual_dir = kobject_create_and_add("virtual", + &devices_kset->kobj); + + return virtual_dir; +} + +static struct kobject *get_device_parent(struct device *dev, + struct device *parent) +{ + int retval; + + if (dev->class) { + struct kobject *kobj = NULL; + struct kobject *parent_kobj; + struct kobject *k; + + /* + * If we have no parent, we live in "virtual". + * Class-devices with a non class-device as parent, live + * in a "glue" directory to prevent namespace collisions. + */ + if (parent == NULL) + parent_kobj = virtual_device_parent(dev); + else if (parent->class) + return &parent->kobj; + else + parent_kobj = &parent->kobj; + + /* find our class-directory at the parent and reference it */ + spin_lock(&dev->class->p->class_dirs.list_lock); + list_for_each_entry(k, &dev->class->p->class_dirs.list, entry) + if (k->parent == parent_kobj) { + kobj = kobject_get(k); + break; + } + spin_unlock(&dev->class->p->class_dirs.list_lock); + if (kobj) + return kobj; + + /* or create a new class-directory at the parent device */ + k = kobject_create(); + if (!k) + return NULL; + k->kset = &dev->class->p->class_dirs; + retval = kobject_add(k, parent_kobj, "%s", dev->class->name); + if (retval < 0) { + kobject_put(k); + return NULL; + } + /* do not emit an uevent for this simple "glue" directory */ + return k; + } + + if (parent) + return &parent->kobj; + return NULL; +} + +static void cleanup_glue_dir(struct device *dev, struct kobject *glue_dir) +{ + /* see if we live in a "glue" directory */ + if (!glue_dir || !dev->class || + glue_dir->kset != &dev->class->p->class_dirs) + return; + + kobject_put(glue_dir); +} + +static void cleanup_device_parent(struct device *dev) +{ + cleanup_glue_dir(dev, dev->kobj.parent); +} +#endif + +static void setup_parent(struct device *dev, struct device *parent) +{ + struct kobject *kobj; + kobj = get_device_parent(dev, parent); + if (kobj) + dev->kobj.parent = kobj; +} + +static int device_add_class_symlinks(struct device *dev) +{ + int error; + + if (!dev->class) + return 0; + + error = sysfs_create_link(&dev->kobj, + &dev->class->p->class_subsys.kobj, + "subsystem"); + if (error) + goto out; + +#ifdef CONFIG_SYSFS_DEPRECATED + /* stacked class devices need a symlink in the class directory */ + if (dev->kobj.parent != &dev->class->p->class_subsys.kobj && + device_is_not_partition(dev)) { + error = sysfs_create_link(&dev->class->p->class_subsys.kobj, + &dev->kobj, dev->bus_id); + if (error) + goto out_subsys; + } + + if (dev->parent && device_is_not_partition(dev)) { + struct device *parent = dev->parent; + char *class_name; + + /* + * stacked class devices have the 'device' link + * pointing to the bus device instead of the parent + */ + while (parent->class && !parent->bus && parent->parent) + parent = parent->parent; + + error = sysfs_create_link(&dev->kobj, + &parent->kobj, + "device"); + if (error) + goto out_busid; + + class_name = make_class_name(dev->class->name, + &dev->kobj); + if (class_name) + error = sysfs_create_link(&dev->parent->kobj, + &dev->kobj, class_name); + kfree(class_name); + if (error) + goto out_device; + } + return 0; + +out_device: + if (dev->parent && device_is_not_partition(dev)) + sysfs_remove_link(&dev->kobj, "device"); +out_busid: + if (dev->kobj.parent != &dev->class->p->class_subsys.kobj && + device_is_not_partition(dev)) + sysfs_remove_link(&dev->class->p->class_subsys.kobj, + dev->bus_id); +#else + /* link in the class directory pointing to the device */ + error = sysfs_create_link(&dev->class->p->class_subsys.kobj, + &dev->kobj, dev->bus_id); + if (error) + goto out_subsys; + + if (dev->parent && device_is_not_partition(dev)) { + error = sysfs_create_link(&dev->kobj, &dev->parent->kobj, + "device"); + if (error) + goto out_busid; + } + return 0; + +out_busid: + sysfs_remove_link(&dev->class->p->class_subsys.kobj, dev->bus_id); +#endif + +out_subsys: + sysfs_remove_link(&dev->kobj, "subsystem"); +out: + return error; +} + +static void device_remove_class_symlinks(struct device *dev) +{ + if (!dev->class) + return; + +#ifdef CONFIG_SYSFS_DEPRECATED + if (dev->parent && device_is_not_partition(dev)) { + char *class_name; + + class_name = make_class_name(dev->class->name, &dev->kobj); + if (class_name) { + sysfs_remove_link(&dev->parent->kobj, class_name); + kfree(class_name); + } + sysfs_remove_link(&dev->kobj, "device"); + } + + if (dev->kobj.parent != &dev->class->p->class_subsys.kobj && + device_is_not_partition(dev)) + sysfs_remove_link(&dev->class->p->class_subsys.kobj, + dev->bus_id); +#else + if (dev->parent && device_is_not_partition(dev)) + sysfs_remove_link(&dev->kobj, "device"); + + sysfs_remove_link(&dev->class->p->class_subsys.kobj, dev->bus_id); +#endif + + sysfs_remove_link(&dev->kobj, "subsystem"); +} + +/** + * dev_set_name - set a device name + * @dev: device + * @fmt: format string for the device's name + */ +int dev_set_name(struct device *dev, const char *fmt, ...) +{ + va_list vargs; + + va_start(vargs, fmt); + vsnprintf(dev->bus_id, sizeof(dev->bus_id), fmt, vargs); + va_end(vargs); + return 0; +} +EXPORT_SYMBOL_GPL(dev_set_name); + +/** + * device_to_dev_kobj - select a /sys/dev/ directory for the device + * @dev: device + * + * By default we select char/ for new entries. Setting class->dev_obj + * to NULL prevents an entry from being created. class->dev_kobj must + * be set (or cleared) before any devices are registered to the class + * otherwise device_create_sys_dev_entry() and + * device_remove_sys_dev_entry() will disagree about the the presence + * of the link. + */ +static struct kobject *device_to_dev_kobj(struct device *dev) +{ + struct kobject *kobj; + + if (dev->class) + kobj = dev->class->dev_kobj; + else + kobj = sysfs_dev_char_kobj; + + return kobj; +} + +static int device_create_sys_dev_entry(struct device *dev) +{ + struct kobject *kobj = device_to_dev_kobj(dev); + int error = 0; + char devt_str[15]; + + if (kobj) { + format_dev_t(devt_str, dev->devt); + error = sysfs_create_link(kobj, &dev->kobj, devt_str); + } + + return error; +} + +static void device_remove_sys_dev_entry(struct device *dev) +{ + struct kobject *kobj = device_to_dev_kobj(dev); + char devt_str[15]; + + if (kobj) { + format_dev_t(devt_str, dev->devt); + sysfs_remove_link(kobj, devt_str); + } +} + +/** + * 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 @dev 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. + * + * NOTE: _Never_ directly free @dev after calling this function, even + * if it returned an error! Always use put_device() to give up your + * reference instead. + */ +int device_add(struct device *dev) +{ + struct device *parent = NULL; + struct class_interface *class_intf; + int error = -EINVAL; + + dev = get_device(dev); + if (!dev) + goto done; + + /* Temporarily support init_name if it is set. + * It will override bus_id for now */ + if (dev->init_name) + dev_set_name(dev, "%s", dev->init_name); + + if (!strlen(dev->bus_id)) + goto done; + + pr_debug("device: '%s': %s\n", dev->bus_id, __func__); + + parent = get_device(dev->parent); + setup_parent(dev, parent); + + /* use parent numa_node */ + if (parent) + set_dev_node(dev, dev_to_node(parent)); + + /* first, register with generic layer. */ + error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id); + if (error) + goto Error; + + /* notify platform of device entry */ + if (platform_notify) + platform_notify(dev); + + /* notify clients of device entry (new way) */ + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_ADD_DEVICE, dev); + + error = device_create_file(dev, &uevent_attr); + if (error) + goto attrError; + + if (MAJOR(dev->devt)) { + error = device_create_file(dev, &devt_attr); + if (error) + goto ueventattrError; + + error = device_create_sys_dev_entry(dev); + if (error) + goto devtattrError; + } + + error = device_add_class_symlinks(dev); + if (error) + goto SymlinkError; + error = device_add_attrs(dev); + if (error) + goto AttrsError; + error = bus_add_device(dev); + if (error) + goto BusError; + error = dpm_sysfs_add(dev); + if (error) + goto DPMError; + device_pm_add(dev); + kobject_uevent(&dev->kobj, KOBJ_ADD); + bus_attach_device(dev); + if (parent) + klist_add_tail(&dev->knode_parent, &parent->klist_children); + + if (dev->class) { + mutex_lock(&dev->class->p->class_mutex); + /* tie the class to the device */ + klist_add_tail(&dev->knode_class, + &dev->class->p->class_devices); + + /* notify any interfaces that the device is here */ + list_for_each_entry(class_intf, + &dev->class->p->class_interfaces, node) + if (class_intf->add_dev) + class_intf->add_dev(dev, class_intf); + mutex_unlock(&dev->class->p->class_mutex); + } +done: + put_device(dev); + return error; + DPMError: + bus_remove_device(dev); + BusError: + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_DEL_DEVICE, dev); + device_remove_attrs(dev); + AttrsError: + device_remove_class_symlinks(dev); + SymlinkError: + if (MAJOR(dev->devt)) + device_remove_sys_dev_entry(dev); + devtattrError: + if (MAJOR(dev->devt)) + device_remove_file(dev, &devt_attr); + ueventattrError: + device_remove_file(dev, &uevent_attr); + attrError: + kobject_uevent(&dev->kobj, KOBJ_REMOVE); + kobject_del(&dev->kobj); + Error: + cleanup_device_parent(dev); + 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. + * + * NOTE: _Never_ directly free @dev after calling this function, even + * if it returned an error! Always use put_device() to give up the + * reference initialized in this function instead. + */ +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) +{ + /* might_sleep(); */ + 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; + struct class_interface *class_intf; + + device_pm_remove(dev); + dpm_sysfs_remove(dev); + if (parent) + klist_del(&dev->knode_parent); + if (MAJOR(dev->devt)) { + device_remove_sys_dev_entry(dev); + device_remove_file(dev, &devt_attr); + } + if (dev->class) { + device_remove_class_symlinks(dev); + + mutex_lock(&dev->class->p->class_mutex); + /* notify any interfaces that the device is now gone */ + list_for_each_entry(class_intf, + &dev->class->p->class_interfaces, node) + if (class_intf->remove_dev) + class_intf->remove_dev(dev, class_intf); + /* remove the device from the class list */ + klist_del(&dev->knode_class); + mutex_unlock(&dev->class->p->class_mutex); + } + device_remove_file(dev, &uevent_attr); + device_remove_attrs(dev); + bus_remove_device(dev); + + /* + * Some platform devices are driven without driver attached + * and managed resources may have been acquired. Make sure + * all resources are released. + */ + devres_release_all(dev); + + /* Notify the platform of the removal, in case they + * need to do anything... + */ + if (platform_notify_remove) + platform_notify_remove(dev); + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_DEL_DEVICE, dev); + kobject_uevent(&dev->kobj, KOBJ_REMOVE); + cleanup_device_parent(dev); + kobject_del(&dev->kobj); + 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("device: '%s': %s\n", dev->bus_id, __func__); + device_del(dev); + put_device(dev); +} + +static struct device *next_device(struct klist_iter *i) +{ + struct klist_node *n = klist_next(i); + return n ? container_of(n, struct device, knode_parent) : NULL; +} + +/** + * device_for_each_child - device child iterator. + * @parent: parent struct device. + * @data: data for the callback. + * @fn: function to be called for each device. + * + * Iterate over @parent'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 *parent, void *data, + int (*fn)(struct device *dev, void *data)) +{ + struct klist_iter i; + struct device *child; + int error = 0; + + klist_iter_init(&parent->klist_children, &i); + while ((child = next_device(&i)) && !error) + error = fn(child, data); + klist_iter_exit(&i); + return error; +} + +/** + * device_find_child - device iterator for locating a particular device. + * @parent: parent struct device + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the device_for_each_child() function above, but it + * returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero and a reference to the + * current device can be obtained, this function will return to the caller + * and not iterate over any more devices. + */ +struct device *device_find_child(struct device *parent, void *data, + int (*match)(struct device *dev, void *data)) +{ + struct klist_iter i; + struct device *child; + + if (!parent) + return NULL; + + klist_iter_init(&parent->klist_children, &i); + while ((child = next_device(&i))) + if (match(child, data) && get_device(child)) + break; + klist_iter_exit(&i); + return child; +} + +int __init devices_init(void) +{ + devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); + if (!devices_kset) + return -ENOMEM; + dev_kobj = kobject_create_and_add("dev", NULL); + if (!dev_kobj) + goto dev_kobj_err; + sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj); + if (!sysfs_dev_block_kobj) + goto block_kobj_err; + sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); + if (!sysfs_dev_char_kobj) + goto char_kobj_err; + + return 0; + + char_kobj_err: + kobject_put(sysfs_dev_block_kobj); + block_kobj_err: + kobject_put(dev_kobj); + dev_kobj_err: + kset_unregister(devices_kset); + return -ENOMEM; +} + +EXPORT_SYMBOL_GPL(device_for_each_child); +EXPORT_SYMBOL_GPL(device_find_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_create_file); +EXPORT_SYMBOL_GPL(device_remove_file); + + +static void device_create_release(struct device *dev) +{ + pr_debug("device: '%s': %s\n", dev->bus_id, __func__); + kfree(dev); +} + +/** + * device_create_vargs - creates a device and registers it with sysfs + * @class: pointer to the struct class that this device should be registered to + * @parent: pointer to the parent struct device of this new device, if any + * @devt: the dev_t for the char device to be added + * @drvdata: the data to be added to the device for callbacks + * @fmt: string for the device's name + * @args: va_list for the device's name + * + * This function can be used by char device classes. A struct device + * will be created in sysfs, registered to the specified class. + * + * A "dev" file will be created, showing the dev_t for the device, if + * the dev_t is not 0,0. + * If a pointer to a parent struct device is passed in, the newly created + * struct device will be a child of that device in sysfs. + * The pointer to the struct 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 passed to this function must have previously + * been created with a call to class_create(). + */ +struct device *device_create_vargs(struct class *class, struct device *parent, + dev_t devt, void *drvdata, const char *fmt, + va_list args) +{ + struct device *dev = NULL; + int retval = -ENODEV; + + if (class == NULL || IS_ERR(class)) + goto error; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + retval = -ENOMEM; + goto error; + } + + dev->devt = devt; + dev->class = class; + dev->parent = parent; + dev->release = device_create_release; + dev_set_drvdata(dev, drvdata); + + vsnprintf(dev->bus_id, BUS_ID_SIZE, fmt, args); + retval = device_register(dev); + if (retval) + goto error; + + return dev; + +error: + put_device(dev); + return ERR_PTR(retval); +} +EXPORT_SYMBOL_GPL(device_create_vargs); + +/** + * device_create - creates a device and registers it with sysfs + * @class: pointer to the struct class that this device should be registered to + * @parent: pointer to the parent struct device of this new device, if any + * @devt: the dev_t for the char device to be added + * @drvdata: the data to be added to the device for callbacks + * @fmt: string for the device's name + * + * This function can be used by char device classes. A struct device + * will be created in sysfs, registered to the specified class. + * + * A "dev" file will be created, showing the dev_t for the device, if + * the dev_t is not 0,0. + * If a pointer to a parent struct device is passed in, the newly created + * struct device will be a child of that device in sysfs. + * The pointer to the struct 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 passed to this function must have previously + * been created with a call to class_create(). + */ +struct device *device_create(struct class *class, struct device *parent, + dev_t devt, void *drvdata, const char *fmt, ...) +{ + va_list vargs; + struct device *dev; + + va_start(vargs, fmt); + dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs); + va_end(vargs); + return dev; +} +EXPORT_SYMBOL_GPL(device_create); + +static int __match_devt(struct device *dev, void *data) +{ + dev_t *devt = data; + + return dev->devt == *devt; +} + +/** + * device_destroy - removes a device that was created with device_create() + * @class: pointer to the struct class that this device was registered with + * @devt: the dev_t of the device that was previously registered + * + * This call unregisters and cleans up a device that was created with a + * call to device_create(). + */ +void device_destroy(struct class *class, dev_t devt) +{ + struct device *dev; + + dev = class_find_device(class, NULL, &devt, __match_devt); + if (dev) { + put_device(dev); + device_unregister(dev); + } +} +EXPORT_SYMBOL_GPL(device_destroy); + +/** + * device_rename - renames a device + * @dev: the pointer to the struct device to be renamed + * @new_name: the new name of the device + * + * It is the responsibility of the caller to provide mutual + * exclusion between two different calls of device_rename + * on the same device to ensure that new_name is valid and + * won't conflict with other devices. + */ +int device_rename(struct device *dev, char *new_name) +{ + char *old_class_name = NULL; + char *new_class_name = NULL; + char *old_device_name = NULL; + int error; + + dev = get_device(dev); + if (!dev) + return -EINVAL; + + pr_debug("device: '%s': %s: renaming to '%s'\n", dev->bus_id, + __func__, new_name); + +#ifdef CONFIG_SYSFS_DEPRECATED + if ((dev->class) && (dev->parent)) + old_class_name = make_class_name(dev->class->name, &dev->kobj); +#endif + + old_device_name = kmalloc(BUS_ID_SIZE, GFP_KERNEL); + if (!old_device_name) { + error = -ENOMEM; + goto out; + } + strlcpy(old_device_name, dev->bus_id, BUS_ID_SIZE); + strlcpy(dev->bus_id, new_name, BUS_ID_SIZE); + + error = kobject_rename(&dev->kobj, new_name); + if (error) { + strlcpy(dev->bus_id, old_device_name, BUS_ID_SIZE); + goto out; + } + +#ifdef CONFIG_SYSFS_DEPRECATED + if (old_class_name) { + new_class_name = make_class_name(dev->class->name, &dev->kobj); + if (new_class_name) { + error = sysfs_create_link_nowarn(&dev->parent->kobj, + &dev->kobj, + new_class_name); + if (error) + goto out; + sysfs_remove_link(&dev->parent->kobj, old_class_name); + } + } +#else + if (dev->class) { + error = sysfs_create_link_nowarn(&dev->class->p->class_subsys.kobj, + &dev->kobj, dev->bus_id); + if (error) + goto out; + sysfs_remove_link(&dev->class->p->class_subsys.kobj, + old_device_name); + } +#endif + +out: + put_device(dev); + + kfree(new_class_name); + kfree(old_class_name); + kfree(old_device_name); + + return error; +} +EXPORT_SYMBOL_GPL(device_rename); + +static int device_move_class_links(struct device *dev, + struct device *old_parent, + struct device *new_parent) +{ + int error = 0; +#ifdef CONFIG_SYSFS_DEPRECATED + char *class_name; + + class_name = make_class_name(dev->class->name, &dev->kobj); + if (!class_name) { + error = -ENOMEM; + goto out; + } + if (old_parent) { + sysfs_remove_link(&dev->kobj, "device"); + sysfs_remove_link(&old_parent->kobj, class_name); + } + if (new_parent) { + error = sysfs_create_link(&dev->kobj, &new_parent->kobj, + "device"); + if (error) + goto out; + error = sysfs_create_link(&new_parent->kobj, &dev->kobj, + class_name); + if (error) + sysfs_remove_link(&dev->kobj, "device"); + } else + error = 0; +out: + kfree(class_name); + return error; +#else + if (old_parent) + sysfs_remove_link(&dev->kobj, "device"); + if (new_parent) + error = sysfs_create_link(&dev->kobj, &new_parent->kobj, + "device"); + return error; +#endif +} + +/** + * device_move - moves a device to a new parent + * @dev: the pointer to the struct device to be moved + * @new_parent: the new parent of the device (can by NULL) + */ +int device_move(struct device *dev, struct device *new_parent) +{ + int error; + struct device *old_parent; + struct kobject *new_parent_kobj; + + dev = get_device(dev); + if (!dev) + return -EINVAL; + + new_parent = get_device(new_parent); + new_parent_kobj = get_device_parent(dev, new_parent); + + pr_debug("device: '%s': %s: moving to '%s'\n", dev->bus_id, + __func__, new_parent ? new_parent->bus_id : "<NULL>"); + error = kobject_move(&dev->kobj, new_parent_kobj); + if (error) { + cleanup_glue_dir(dev, new_parent_kobj); + put_device(new_parent); + goto out; + } + old_parent = dev->parent; + dev->parent = new_parent; + if (old_parent) + klist_remove(&dev->knode_parent); + if (new_parent) { + klist_add_tail(&dev->knode_parent, &new_parent->klist_children); + set_dev_node(dev, dev_to_node(new_parent)); + } + + if (!dev->class) + goto out_put; + error = device_move_class_links(dev, old_parent, new_parent); + if (error) { + /* We ignore errors on cleanup since we're hosed anyway... */ + device_move_class_links(dev, new_parent, old_parent); + if (!kobject_move(&dev->kobj, &old_parent->kobj)) { + if (new_parent) + klist_remove(&dev->knode_parent); + dev->parent = old_parent; + if (old_parent) { + klist_add_tail(&dev->knode_parent, + &old_parent->klist_children); + set_dev_node(dev, dev_to_node(old_parent)); + } + } + cleanup_glue_dir(dev, new_parent_kobj); + put_device(new_parent); + goto out; + } +out_put: + put_device(old_parent); +out: + put_device(dev); + return error; +} +EXPORT_SYMBOL_GPL(device_move); + +/** + * device_shutdown - call ->shutdown() on each device to shutdown. + */ +void device_shutdown(void) +{ + struct device *dev, *devn; + + list_for_each_entry_safe_reverse(dev, devn, &devices_kset->list, + kobj.entry) { + if (dev->bus && dev->bus->shutdown) { + dev_dbg(dev, "shutdown\n"); + dev->bus->shutdown(dev); + } else if (dev->driver && dev->driver->shutdown) { + dev_dbg(dev, "shutdown\n"); + dev->driver->shutdown(dev); + } + } + kobject_put(sysfs_dev_char_kobj); + kobject_put(sysfs_dev_block_kobj); + kobject_put(dev_kobj); +} diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c new file mode 100644 index 0000000..64f5d54 --- /dev/null +++ b/drivers/base/cpu.c @@ -0,0 +1,206 @@ +/* + * drivers/base/cpu.c - basic CPU class support + */ + +#include <linux/sysdev.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/cpu.h> +#include <linux/topology.h> +#include <linux/device.h> +#include <linux/node.h> + +#include "base.h" + +struct sysdev_class cpu_sysdev_class = { + .name = "cpu", +}; +EXPORT_SYMBOL(cpu_sysdev_class); + +static DEFINE_PER_CPU(struct sys_device *, cpu_sys_devices); + +#ifdef CONFIG_HOTPLUG_CPU +static ssize_t show_online(struct sys_device *dev, struct sysdev_attribute *attr, + char *buf) +{ + struct cpu *cpu = container_of(dev, struct cpu, sysdev); + + return sprintf(buf, "%u\n", !!cpu_online(cpu->sysdev.id)); +} + +static ssize_t __ref store_online(struct sys_device *dev, struct sysdev_attribute *attr, + 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_uevent(&dev->kobj, KOBJ_OFFLINE); + break; + case '1': + ret = cpu_up(cpu->sysdev.id); + if (!ret) + kobject_uevent(&dev->kobj, KOBJ_ONLINE); + break; + default: + ret = -EINVAL; + } + + if (ret >= 0) + ret = count; + return ret; +} +static SYSDEV_ATTR(online, 0644, show_online, store_online); + +static void __cpuinit register_cpu_control(struct cpu *cpu) +{ + sysdev_create_file(&cpu->sysdev, &attr_online); +} +void unregister_cpu(struct cpu *cpu) +{ + int logical_cpu = cpu->sysdev.id; + + unregister_cpu_under_node(logical_cpu, cpu_to_node(logical_cpu)); + + sysdev_remove_file(&cpu->sysdev, &attr_online); + + sysdev_unregister(&cpu->sysdev); + per_cpu(cpu_sys_devices, logical_cpu) = NULL; + return; +} +#else /* ... !CONFIG_HOTPLUG_CPU */ +static inline void register_cpu_control(struct cpu *cpu) +{ +} +#endif /* CONFIG_HOTPLUG_CPU */ + +#ifdef CONFIG_KEXEC +#include <linux/kexec.h> + +static ssize_t show_crash_notes(struct sys_device *dev, struct sysdev_attribute *attr, + char *buf) +{ + struct cpu *cpu = container_of(dev, struct cpu, sysdev); + ssize_t rc; + unsigned long long addr; + int cpunum; + + cpunum = cpu->sysdev.id; + + /* + * Might be reading other cpu's data based on which cpu read thread + * has been scheduled. But cpu data (memory) is allocated once during + * boot up and this data does not change there after. Hence this + * operation should be safe. No locking required. + */ + addr = __pa(per_cpu_ptr(crash_notes, cpunum)); + rc = sprintf(buf, "%Lx\n", addr); + return rc; +} +static SYSDEV_ATTR(crash_notes, 0400, show_crash_notes, NULL); +#endif + +/* + * Print cpu online, possible, present, and system maps + */ +static ssize_t print_cpus_map(char *buf, cpumask_t *map) +{ + int n = cpulist_scnprintf(buf, PAGE_SIZE-2, *map); + + buf[n++] = '\n'; + buf[n] = '\0'; + return n; +} + +#define print_cpus_func(type) \ +static ssize_t print_cpus_##type(struct sysdev_class *class, char *buf) \ +{ \ + return print_cpus_map(buf, &cpu_##type##_map); \ +} \ +static struct sysdev_class_attribute attr_##type##_map = \ + _SYSDEV_CLASS_ATTR(type, 0444, print_cpus_##type, NULL) + +print_cpus_func(online); +print_cpus_func(possible); +print_cpus_func(present); + +static struct sysdev_class_attribute *cpu_state_attr[] = { + &attr_online_map, + &attr_possible_map, + &attr_present_map, +}; + +static int cpu_states_init(void) +{ + int i; + int err = 0; + + for (i = 0; i < ARRAY_SIZE(cpu_state_attr); i++) { + int ret; + ret = sysdev_class_create_file(&cpu_sysdev_class, + cpu_state_attr[i]); + if (!err) + err = ret; + } + return err; +} + +/* + * register_cpu - Setup a sysfs device for a CPU. + * @cpu - cpu->hotpluggable field set to 1 will 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 __cpuinit register_cpu(struct cpu *cpu, int num) +{ + 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 && cpu->hotpluggable) + register_cpu_control(cpu); + if (!error) + per_cpu(cpu_sys_devices, num) = &cpu->sysdev; + if (!error) + register_cpu_under_node(num, cpu_to_node(num)); + +#ifdef CONFIG_KEXEC + if (!error) + error = sysdev_create_file(&cpu->sysdev, &attr_crash_notes); +#endif + return error; +} + +struct sys_device *get_cpu_sysdev(unsigned cpu) +{ + if (cpu < nr_cpu_ids && cpu_possible(cpu)) + return per_cpu(cpu_sys_devices, cpu); + else + return NULL; +} +EXPORT_SYMBOL_GPL(get_cpu_sysdev); + +int __init cpu_dev_init(void) +{ + int err; + + err = sysdev_class_register(&cpu_sysdev_class); + if (!err) + err = cpu_states_init(); + +#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT) + if (!err) + err = sched_create_sysfs_power_savings_entries(&cpu_sysdev_class); +#endif + + return err; +} diff --git a/drivers/base/dd.c b/drivers/base/dd.c new file mode 100644 index 0000000..20febc0 --- /dev/null +++ b/drivers/base/dd.c @@ -0,0 +1,367 @@ +/* + * drivers/base/dd.c - The core device/driver interactions. + * + * This file contains the (sometimes tricky) code that controls the + * interactions between devices and drivers, which primarily includes + * driver binding and unbinding. + * + * All of this code used to exist in drivers/base/bus.c, but was + * relocated to here in the name of compartmentalization (since it wasn't + * strictly code just for the 'struct bus_type'. + * + * Copyright (c) 2002-5 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2007 Novell Inc. + * + * This file is released under the GPLv2 + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/wait.h> + +#include "base.h" +#include "power/power.h" + + +static void driver_bound(struct device *dev) +{ + if (klist_node_attached(&dev->knode_driver)) { + printk(KERN_WARNING "%s: device %s already bound\n", + __func__, kobject_name(&dev->kobj)); + return; + } + + pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->bus_id, + __func__, dev->driver->name); + + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_BOUND_DRIVER, dev); + + klist_add_tail(&dev->knode_driver, &dev->driver->p->klist_devices); +} + +static int driver_sysfs_add(struct device *dev) +{ + int ret; + + ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj, + kobject_name(&dev->kobj)); + if (ret == 0) { + ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj, + "driver"); + if (ret) + sysfs_remove_link(&dev->driver->p->kobj, + kobject_name(&dev->kobj)); + } + return ret; +} + +static void driver_sysfs_remove(struct device *dev) +{ + struct device_driver *drv = dev->driver; + + if (drv) { + sysfs_remove_link(&drv->p->kobj, kobject_name(&dev->kobj)); + sysfs_remove_link(&dev->kobj, "driver"); + } +} + +/** + * 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.) + * + * This function must be called with @dev->sem held. + */ +int device_bind_driver(struct device *dev) +{ + int ret; + + ret = driver_sysfs_add(dev); + if (!ret) + driver_bound(dev); + return ret; +} +EXPORT_SYMBOL_GPL(device_bind_driver); + +static atomic_t probe_count = ATOMIC_INIT(0); +static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue); + +static int really_probe(struct device *dev, struct device_driver *drv) +{ + int ret = 0; + + atomic_inc(&probe_count); + pr_debug("bus: '%s': %s: probing driver %s with device %s\n", + drv->bus->name, __func__, drv->name, dev->bus_id); + WARN_ON(!list_empty(&dev->devres_head)); + + dev->driver = drv; + if (driver_sysfs_add(dev)) { + printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", + __func__, dev->bus_id); + goto probe_failed; + } + + if (dev->bus->probe) { + ret = dev->bus->probe(dev); + if (ret) + goto probe_failed; + } else if (drv->probe) { + ret = drv->probe(dev); + if (ret) + goto probe_failed; + } + + driver_bound(dev); + ret = 1; + pr_debug("bus: '%s': %s: bound device %s to driver %s\n", + drv->bus->name, __func__, dev->bus_id, drv->name); + goto done; + +probe_failed: + devres_release_all(dev); + driver_sysfs_remove(dev); + dev->driver = NULL; + + if (ret != -ENODEV && ret != -ENXIO) { + /* driver matched but the probe failed */ + printk(KERN_WARNING + "%s: probe of %s failed with error %d\n", + drv->name, dev->bus_id, ret); + } + /* + * Ignore errors returned by ->probe so that the next driver can try + * its luck. + */ + ret = 0; +done: + atomic_dec(&probe_count); + wake_up(&probe_waitqueue); + return ret; +} + +/** + * driver_probe_done + * Determine if the probe sequence is finished or not. + * + * Should somehow figure out how to use a semaphore, not an atomic variable... + */ +int driver_probe_done(void) +{ + pr_debug("%s: probe_count = %d\n", __func__, + atomic_read(&probe_count)); + if (atomic_read(&probe_count)) + return -EBUSY; + return 0; +} + +/** + * driver_probe_device - attempt to bind device & driver together + * @drv: driver to bind a device to + * @dev: device to try to bind to the driver + * + * 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. + * + * This function returns 1 if a match is found, -ENODEV if the device is + * not registered, and 0 otherwise. + * + * This function must be called with @dev->sem held. When called for a + * USB interface, @dev->parent->sem must be held as well. + */ +int driver_probe_device(struct device_driver *drv, struct device *dev) +{ + int ret = 0; + + if (!device_is_registered(dev)) + return -ENODEV; + if (drv->bus->match && !drv->bus->match(dev, drv)) + goto done; + + pr_debug("bus: '%s': %s: matched device %s with driver %s\n", + drv->bus->name, __func__, dev->bus_id, drv->name); + + ret = really_probe(dev, drv); + +done: + return ret; +} + +static int __device_attach(struct device_driver *drv, void *data) +{ + struct device *dev = data; + return driver_probe_device(drv, dev); +} + +/** + * 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. + * + * Returns 1 if the device was bound to a driver; + * 0 if no matching device was found; + * -ENODEV if the device is not registered. + * + * When called for a USB interface, @dev->parent->sem must be held. + */ +int device_attach(struct device *dev) +{ + int ret = 0; + + down(&dev->sem); + if (dev->driver) { + ret = device_bind_driver(dev); + if (ret == 0) + ret = 1; + else { + dev->driver = NULL; + ret = 0; + } + } else { + ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); + } + up(&dev->sem); + return ret; +} +EXPORT_SYMBOL_GPL(device_attach); + +static int __driver_attach(struct device *dev, void *data) +{ + struct device_driver *drv = data; + + /* + * Lock device and try to bind to it. We drop the error + * here and always return 0, because we need to keep trying + * to bind to devices and some drivers will return an error + * simply if it didn't support the device. + * + * driver_probe_device() will spit a warning if there + * is an error. + */ + + if (drv->bus->match && !drv->bus->match(dev, drv)) + return 0; + + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + down(&dev->sem); + if (!dev->driver) + driver_probe_device(drv, dev); + up(&dev->sem); + if (dev->parent) + up(&dev->parent->sem); + + 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. + */ +int driver_attach(struct device_driver *drv) +{ + return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); +} +EXPORT_SYMBOL_GPL(driver_attach); + +/* + * __device_release_driver() must be called with @dev->sem held. + * When called for a USB interface, @dev->parent->sem must be held as well. + */ +static void __device_release_driver(struct device *dev) +{ + struct device_driver *drv; + + drv = dev->driver; + if (drv) { + driver_sysfs_remove(dev); + sysfs_remove_link(&dev->kobj, "driver"); + + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_UNBIND_DRIVER, + dev); + + if (dev->bus && dev->bus->remove) + dev->bus->remove(dev); + else if (drv->remove) + drv->remove(dev); + devres_release_all(dev); + dev->driver = NULL; + klist_remove(&dev->knode_driver); + } +} + +/** + * device_release_driver - manually detach device from driver. + * @dev: device. + * + * Manually detach device from driver. + * When called for a USB interface, @dev->parent->sem must be held. + */ +void device_release_driver(struct device *dev) +{ + /* + * If anyone calls device_release_driver() recursively from + * within their ->remove callback for the same device, they + * will deadlock right here. + */ + down(&dev->sem); + __device_release_driver(dev); + up(&dev->sem); +} +EXPORT_SYMBOL_GPL(device_release_driver); + +/** + * driver_detach - detach driver from all devices it controls. + * @drv: driver. + */ +void driver_detach(struct device_driver *drv) +{ + struct device *dev; + + for (;;) { + spin_lock(&drv->p->klist_devices.k_lock); + if (list_empty(&drv->p->klist_devices.k_list)) { + spin_unlock(&drv->p->klist_devices.k_lock); + break; + } + dev = list_entry(drv->p->klist_devices.k_list.prev, + struct device, knode_driver.n_node); + get_device(dev); + spin_unlock(&drv->p->klist_devices.k_lock); + + if (dev->parent) /* Needed for USB */ + down(&dev->parent->sem); + down(&dev->sem); + if (dev->driver == drv) + __device_release_driver(dev); + up(&dev->sem); + if (dev->parent) + up(&dev->parent->sem); + put_device(dev); + } +} diff --git a/drivers/base/devres.c b/drivers/base/devres.c new file mode 100644 index 0000000..e8beb8e --- /dev/null +++ b/drivers/base/devres.c @@ -0,0 +1,646 @@ +/* + * drivers/base/devres.c - device resource management + * + * Copyright (c) 2006 SUSE Linux Products GmbH + * Copyright (c) 2006 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + */ + +#include <linux/device.h> +#include <linux/module.h> + +#include "base.h" + +struct devres_node { + struct list_head entry; + dr_release_t release; +#ifdef CONFIG_DEBUG_DEVRES + const char *name; + size_t size; +#endif +}; + +struct devres { + struct devres_node node; + /* -- 3 pointers */ + unsigned long long data[]; /* guarantee ull alignment */ +}; + +struct devres_group { + struct devres_node node[2]; + void *id; + int color; + /* -- 8 pointers */ +}; + +#ifdef CONFIG_DEBUG_DEVRES +static int log_devres = 0; +module_param_named(log, log_devres, int, S_IRUGO | S_IWUSR); + +static void set_node_dbginfo(struct devres_node *node, const char *name, + size_t size) +{ + node->name = name; + node->size = size; +} + +static void devres_log(struct device *dev, struct devres_node *node, + const char *op) +{ + if (unlikely(log_devres)) + dev_printk(KERN_ERR, dev, "DEVRES %3s %p %s (%lu bytes)\n", + op, node, node->name, (unsigned long)node->size); +} +#else /* CONFIG_DEBUG_DEVRES */ +#define set_node_dbginfo(node, n, s) do {} while (0) +#define devres_log(dev, node, op) do {} while (0) +#endif /* CONFIG_DEBUG_DEVRES */ + +/* + * Release functions for devres group. These callbacks are used only + * for identification. + */ +static void group_open_release(struct device *dev, void *res) +{ + /* noop */ +} + +static void group_close_release(struct device *dev, void *res) +{ + /* noop */ +} + +static struct devres_group * node_to_group(struct devres_node *node) +{ + if (node->release == &group_open_release) + return container_of(node, struct devres_group, node[0]); + if (node->release == &group_close_release) + return container_of(node, struct devres_group, node[1]); + return NULL; +} + +static __always_inline struct devres * alloc_dr(dr_release_t release, + size_t size, gfp_t gfp) +{ + size_t tot_size = sizeof(struct devres) + size; + struct devres *dr; + + dr = kmalloc_track_caller(tot_size, gfp); + if (unlikely(!dr)) + return NULL; + + memset(dr, 0, tot_size); + INIT_LIST_HEAD(&dr->node.entry); + dr->node.release = release; + return dr; +} + +static void add_dr(struct device *dev, struct devres_node *node) +{ + devres_log(dev, node, "ADD"); + BUG_ON(!list_empty(&node->entry)); + list_add_tail(&node->entry, &dev->devres_head); +} + +#ifdef CONFIG_DEBUG_DEVRES +void * __devres_alloc(dr_release_t release, size_t size, gfp_t gfp, + const char *name) +{ + struct devres *dr; + + dr = alloc_dr(release, size, gfp); + if (unlikely(!dr)) + return NULL; + set_node_dbginfo(&dr->node, name, size); + return dr->data; +} +EXPORT_SYMBOL_GPL(__devres_alloc); +#else +/** + * devres_alloc - Allocate device resource data + * @release: Release function devres will be associated with + * @size: Allocation size + * @gfp: Allocation flags + * + * Allocate devres of @size bytes. The allocated area is zeroed, then + * associated with @release. The returned pointer can be passed to + * other devres_*() functions. + * + * RETURNS: + * Pointer to allocated devres on success, NULL on failure. + */ +void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp) +{ + struct devres *dr; + + dr = alloc_dr(release, size, gfp); + if (unlikely(!dr)) + return NULL; + return dr->data; +} +EXPORT_SYMBOL_GPL(devres_alloc); +#endif + +/** + * devres_free - Free device resource data + * @res: Pointer to devres data to free + * + * Free devres created with devres_alloc(). + */ +void devres_free(void *res) +{ + if (res) { + struct devres *dr = container_of(res, struct devres, data); + + BUG_ON(!list_empty(&dr->node.entry)); + kfree(dr); + } +} +EXPORT_SYMBOL_GPL(devres_free); + +/** + * devres_add - Register device resource + * @dev: Device to add resource to + * @res: Resource to register + * + * Register devres @res to @dev. @res should have been allocated + * using devres_alloc(). On driver detach, the associated release + * function will be invoked and devres will be freed automatically. + */ +void devres_add(struct device *dev, void *res) +{ + struct devres *dr = container_of(res, struct devres, data); + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + add_dr(dev, &dr->node); + spin_unlock_irqrestore(&dev->devres_lock, flags); +} +EXPORT_SYMBOL_GPL(devres_add); + +static struct devres *find_dr(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + struct devres_node *node; + + list_for_each_entry_reverse(node, &dev->devres_head, entry) { + struct devres *dr = container_of(node, struct devres, node); + + if (node->release != release) + continue; + if (match && !match(dev, dr->data, match_data)) + continue; + return dr; + } + + return NULL; +} + +/** + * devres_find - Find device resource + * @dev: Device to lookup resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev which is associated with @release + * and for which @match returns 1. If @match is NULL, it's considered + * to match all. + * + * RETURNS: + * Pointer to found devres, NULL if not found. + */ +void * devres_find(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + struct devres *dr; + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + dr = find_dr(dev, release, match, match_data); + spin_unlock_irqrestore(&dev->devres_lock, flags); + + if (dr) + return dr->data; + return NULL; +} +EXPORT_SYMBOL_GPL(devres_find); + +/** + * devres_get - Find devres, if non-existent, add one atomically + * @dev: Device to lookup or add devres for + * @new_res: Pointer to new initialized devres to add if not found + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev which has the same release function + * as @new_res and for which @match return 1. If found, @new_res is + * freed; otherwise, @new_res is added atomically. + * + * RETURNS: + * Pointer to found or added devres. + */ +void * devres_get(struct device *dev, void *new_res, + dr_match_t match, void *match_data) +{ + struct devres *new_dr = container_of(new_res, struct devres, data); + struct devres *dr; + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + dr = find_dr(dev, new_dr->node.release, match, match_data); + if (!dr) { + add_dr(dev, &new_dr->node); + dr = new_dr; + new_dr = NULL; + } + spin_unlock_irqrestore(&dev->devres_lock, flags); + devres_free(new_dr); + + return dr->data; +} +EXPORT_SYMBOL_GPL(devres_get); + +/** + * devres_remove - Find a device resource and remove it + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically and + * returned. + * + * RETURNS: + * Pointer to removed devres on success, NULL if not found. + */ +void * devres_remove(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + struct devres *dr; + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + dr = find_dr(dev, release, match, match_data); + if (dr) { + list_del_init(&dr->node.entry); + devres_log(dev, &dr->node, "REM"); + } + spin_unlock_irqrestore(&dev->devres_lock, flags); + + if (dr) + return dr->data; + return NULL; +} +EXPORT_SYMBOL_GPL(devres_remove); + +/** + * devres_destroy - Find a device resource and destroy it + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically and freed. + * + * RETURNS: + * 0 if devres is found and freed, -ENOENT if not found. + */ +int devres_destroy(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + void *res; + + res = devres_remove(dev, release, match, match_data); + if (unlikely(!res)) + return -ENOENT; + + devres_free(res); + return 0; +} +EXPORT_SYMBOL_GPL(devres_destroy); + +static int remove_nodes(struct device *dev, + struct list_head *first, struct list_head *end, + struct list_head *todo) +{ + int cnt = 0, nr_groups = 0; + struct list_head *cur; + + /* First pass - move normal devres entries to @todo and clear + * devres_group colors. + */ + cur = first; + while (cur != end) { + struct devres_node *node; + struct devres_group *grp; + + node = list_entry(cur, struct devres_node, entry); + cur = cur->next; + + grp = node_to_group(node); + if (grp) { + /* clear color of group markers in the first pass */ + grp->color = 0; + nr_groups++; + } else { + /* regular devres entry */ + if (&node->entry == first) + first = first->next; + list_move_tail(&node->entry, todo); + cnt++; + } + } + + if (!nr_groups) + return cnt; + + /* Second pass - Scan groups and color them. A group gets + * color value of two iff the group is wholly contained in + * [cur, end). That is, for a closed group, both opening and + * closing markers should be in the range, while just the + * opening marker is enough for an open group. + */ + cur = first; + while (cur != end) { + struct devres_node *node; + struct devres_group *grp; + + node = list_entry(cur, struct devres_node, entry); + cur = cur->next; + + grp = node_to_group(node); + BUG_ON(!grp || list_empty(&grp->node[0].entry)); + + grp->color++; + if (list_empty(&grp->node[1].entry)) + grp->color++; + + BUG_ON(grp->color <= 0 || grp->color > 2); + if (grp->color == 2) { + /* No need to update cur or end. The removed + * nodes are always before both. + */ + list_move_tail(&grp->node[0].entry, todo); + list_del_init(&grp->node[1].entry); + } + } + + return cnt; +} + +static int release_nodes(struct device *dev, struct list_head *first, + struct list_head *end, unsigned long flags) +{ + LIST_HEAD(todo); + int cnt; + struct devres *dr, *tmp; + + cnt = remove_nodes(dev, first, end, &todo); + + spin_unlock_irqrestore(&dev->devres_lock, flags); + + /* Release. Note that both devres and devres_group are + * handled as devres in the following loop. This is safe. + */ + list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) { + devres_log(dev, &dr->node, "REL"); + dr->node.release(dev, dr->data); + kfree(dr); + } + + return cnt; +} + +/** + * devres_release_all - Release all managed resources + * @dev: Device to release resources for + * + * Release all resources associated with @dev. This function is + * called on driver detach. + */ +int devres_release_all(struct device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + return release_nodes(dev, dev->devres_head.next, &dev->devres_head, + flags); +} + +/** + * devres_open_group - Open a new devres group + * @dev: Device to open devres group for + * @id: Separator ID + * @gfp: Allocation flags + * + * Open a new devres group for @dev with @id. For @id, using a + * pointer to an object which won't be used for another group is + * recommended. If @id is NULL, address-wise unique ID is created. + * + * RETURNS: + * ID of the new group, NULL on failure. + */ +void * devres_open_group(struct device *dev, void *id, gfp_t gfp) +{ + struct devres_group *grp; + unsigned long flags; + + grp = kmalloc(sizeof(*grp), gfp); + if (unlikely(!grp)) + return NULL; + + grp->node[0].release = &group_open_release; + grp->node[1].release = &group_close_release; + INIT_LIST_HEAD(&grp->node[0].entry); + INIT_LIST_HEAD(&grp->node[1].entry); + set_node_dbginfo(&grp->node[0], "grp<", 0); + set_node_dbginfo(&grp->node[1], "grp>", 0); + grp->id = grp; + if (id) + grp->id = id; + + spin_lock_irqsave(&dev->devres_lock, flags); + add_dr(dev, &grp->node[0]); + spin_unlock_irqrestore(&dev->devres_lock, flags); + return grp->id; +} +EXPORT_SYMBOL_GPL(devres_open_group); + +/* Find devres group with ID @id. If @id is NULL, look for the latest. */ +static struct devres_group * find_group(struct device *dev, void *id) +{ + struct devres_node *node; + + list_for_each_entry_reverse(node, &dev->devres_head, entry) { + struct devres_group *grp; + + if (node->release != &group_open_release) + continue; + + grp = container_of(node, struct devres_group, node[0]); + + if (id) { + if (grp->id == id) + return grp; + } else if (list_empty(&grp->node[1].entry)) + return grp; + } + + return NULL; +} + +/** + * devres_close_group - Close a devres group + * @dev: Device to close devres group for + * @id: ID of target group, can be NULL + * + * Close the group identified by @id. If @id is NULL, the latest open + * group is selected. + */ +void devres_close_group(struct device *dev, void *id) +{ + struct devres_group *grp; + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + + grp = find_group(dev, id); + if (grp) + add_dr(dev, &grp->node[1]); + else + WARN_ON(1); + + spin_unlock_irqrestore(&dev->devres_lock, flags); +} +EXPORT_SYMBOL_GPL(devres_close_group); + +/** + * devres_remove_group - Remove a devres group + * @dev: Device to remove group for + * @id: ID of target group, can be NULL + * + * Remove the group identified by @id. If @id is NULL, the latest + * open group is selected. Note that removing a group doesn't affect + * any other resources. + */ +void devres_remove_group(struct device *dev, void *id) +{ + struct devres_group *grp; + unsigned long flags; + + spin_lock_irqsave(&dev->devres_lock, flags); + + grp = find_group(dev, id); + if (grp) { + list_del_init(&grp->node[0].entry); + list_del_init(&grp->node[1].entry); + devres_log(dev, &grp->node[0], "REM"); + } else + WARN_ON(1); + + spin_unlock_irqrestore(&dev->devres_lock, flags); + + kfree(grp); +} +EXPORT_SYMBOL_GPL(devres_remove_group); + +/** + * devres_release_group - Release resources in a devres group + * @dev: Device to release group for + * @id: ID of target group, can be NULL + * + * Release all resources in the group identified by @id. If @id is + * NULL, the latest open group is selected. The selected group and + * groups properly nested inside the selected group are removed. + * + * RETURNS: + * The number of released non-group resources. + */ +int devres_release_group(struct device *dev, void *id) +{ + struct devres_group *grp; + unsigned long flags; + int cnt = 0; + + spin_lock_irqsave(&dev->devres_lock, flags); + + grp = find_group(dev, id); + if (grp) { + struct list_head *first = &grp->node[0].entry; + struct list_head *end = &dev->devres_head; + + if (!list_empty(&grp->node[1].entry)) + end = grp->node[1].entry.next; + + cnt = release_nodes(dev, first, end, flags); + } else { + WARN_ON(1); + spin_unlock_irqrestore(&dev->devres_lock, flags); + } + + return cnt; +} +EXPORT_SYMBOL_GPL(devres_release_group); + +/* + * Managed kzalloc/kfree + */ +static void devm_kzalloc_release(struct device *dev, void *res) +{ + /* noop */ +} + +static int devm_kzalloc_match(struct device *dev, void *res, void *data) +{ + return res == data; +} + +/** + * devm_kzalloc - Resource-managed kzalloc + * @dev: Device to allocate memory for + * @size: Allocation size + * @gfp: Allocation gfp flags + * + * Managed kzalloc. Memory allocated with this function is + * automatically freed on driver detach. Like all other devres + * resources, guaranteed alignment is unsigned long long. + * + * RETURNS: + * Pointer to allocated memory on success, NULL on failure. + */ +void * devm_kzalloc(struct device *dev, size_t size, gfp_t gfp) +{ + struct devres *dr; + + /* use raw alloc_dr for kmalloc caller tracing */ + dr = alloc_dr(devm_kzalloc_release, size, gfp); + if (unlikely(!dr)) + return NULL; + + set_node_dbginfo(&dr->node, "devm_kzalloc_release", size); + devres_add(dev, dr->data); + return dr->data; +} +EXPORT_SYMBOL_GPL(devm_kzalloc); + +/** + * devm_kfree - Resource-managed kfree + * @dev: Device this memory belongs to + * @p: Memory to free + * + * Free memory allocated with dev_kzalloc(). + */ +void devm_kfree(struct device *dev, void *p) +{ + int rc; + + rc = devres_destroy(dev, devm_kzalloc_release, devm_kzalloc_match, p); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_kfree); diff --git a/drivers/base/dma-mapping.c b/drivers/base/dma-mapping.c new file mode 100644 index 0000000..ca9186f --- /dev/null +++ b/drivers/base/dma-mapping.c @@ -0,0 +1,218 @@ +/* + * drivers/base/dma-mapping.c - arch-independent dma-mapping routines + * + * Copyright (c) 2006 SUSE Linux Products GmbH + * Copyright (c) 2006 Tejun Heo <teheo@suse.de> + * + * This file is released under the GPLv2. + */ + +#include <linux/dma-mapping.h> + +/* + * Managed DMA API + */ +struct dma_devres { + size_t size; + void *vaddr; + dma_addr_t dma_handle; +}; + +static void dmam_coherent_release(struct device *dev, void *res) +{ + struct dma_devres *this = res; + + dma_free_coherent(dev, this->size, this->vaddr, this->dma_handle); +} + +static void dmam_noncoherent_release(struct device *dev, void *res) +{ + struct dma_devres *this = res; + + dma_free_noncoherent(dev, this->size, this->vaddr, this->dma_handle); +} + +static int dmam_match(struct device *dev, void *res, void *match_data) +{ + struct dma_devres *this = res, *match = match_data; + + if (this->vaddr == match->vaddr) { + WARN_ON(this->size != match->size || + this->dma_handle != match->dma_handle); + return 1; + } + return 0; +} + +/** + * dmam_alloc_coherent - Managed dma_alloc_coherent() + * @dev: Device to allocate coherent memory for + * @size: Size of allocation + * @dma_handle: Out argument for allocated DMA handle + * @gfp: Allocation flags + * + * Managed dma_alloc_coherent(). Memory allocated using this function + * will be automatically released on driver detach. + * + * RETURNS: + * Pointer to allocated memory on success, NULL on failure. + */ +void * dmam_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t gfp) +{ + struct dma_devres *dr; + void *vaddr; + + dr = devres_alloc(dmam_coherent_release, sizeof(*dr), gfp); + if (!dr) + return NULL; + + vaddr = dma_alloc_coherent(dev, size, dma_handle, gfp); + if (!vaddr) { + devres_free(dr); + return NULL; + } + + dr->vaddr = vaddr; + dr->dma_handle = *dma_handle; + dr->size = size; + + devres_add(dev, dr); + + return vaddr; +} +EXPORT_SYMBOL(dmam_alloc_coherent); + +/** + * dmam_free_coherent - Managed dma_free_coherent() + * @dev: Device to free coherent memory for + * @size: Size of allocation + * @vaddr: Virtual address of the memory to free + * @dma_handle: DMA handle of the memory to free + * + * Managed dma_free_coherent(). + */ +void dmam_free_coherent(struct device *dev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + struct dma_devres match_data = { size, vaddr, dma_handle }; + + dma_free_coherent(dev, size, vaddr, dma_handle); + WARN_ON(devres_destroy(dev, dmam_coherent_release, dmam_match, + &match_data)); +} +EXPORT_SYMBOL(dmam_free_coherent); + +/** + * dmam_alloc_non_coherent - Managed dma_alloc_non_coherent() + * @dev: Device to allocate non_coherent memory for + * @size: Size of allocation + * @dma_handle: Out argument for allocated DMA handle + * @gfp: Allocation flags + * + * Managed dma_alloc_non_coherent(). Memory allocated using this + * function will be automatically released on driver detach. + * + * RETURNS: + * Pointer to allocated memory on success, NULL on failure. + */ +void *dmam_alloc_noncoherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t gfp) +{ + struct dma_devres *dr; + void *vaddr; + + dr = devres_alloc(dmam_noncoherent_release, sizeof(*dr), gfp); + if (!dr) + return NULL; + + vaddr = dma_alloc_noncoherent(dev, size, dma_handle, gfp); + if (!vaddr) { + devres_free(dr); + return NULL; + } + + dr->vaddr = vaddr; + dr->dma_handle = *dma_handle; + dr->size = size; + + devres_add(dev, dr); + + return vaddr; +} +EXPORT_SYMBOL(dmam_alloc_noncoherent); + +/** + * dmam_free_coherent - Managed dma_free_noncoherent() + * @dev: Device to free noncoherent memory for + * @size: Size of allocation + * @vaddr: Virtual address of the memory to free + * @dma_handle: DMA handle of the memory to free + * + * Managed dma_free_noncoherent(). + */ +void dmam_free_noncoherent(struct device *dev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + struct dma_devres match_data = { size, vaddr, dma_handle }; + + dma_free_noncoherent(dev, size, vaddr, dma_handle); + WARN_ON(!devres_destroy(dev, dmam_noncoherent_release, dmam_match, + &match_data)); +} +EXPORT_SYMBOL(dmam_free_noncoherent); + +#ifdef ARCH_HAS_DMA_DECLARE_COHERENT_MEMORY + +static void dmam_coherent_decl_release(struct device *dev, void *res) +{ + dma_release_declared_memory(dev); +} + +/** + * dmam_declare_coherent_memory - Managed dma_declare_coherent_memory() + * @dev: Device to declare coherent memory for + * @bus_addr: Bus address of coherent memory to be declared + * @device_addr: Device address of coherent memory to be declared + * @size: Size of coherent memory to be declared + * @flags: Flags + * + * Managed dma_declare_coherent_memory(). + * + * RETURNS: + * 0 on success, -errno on failure. + */ +int dmam_declare_coherent_memory(struct device *dev, dma_addr_t bus_addr, + dma_addr_t device_addr, size_t size, int flags) +{ + void *res; + int rc; + + res = devres_alloc(dmam_coherent_decl_release, 0, GFP_KERNEL); + if (!res) + return -ENOMEM; + + rc = dma_declare_coherent_memory(dev, bus_addr, device_addr, size, + flags); + if (rc == 0) + devres_add(dev, res); + else + devres_free(res); + + return rc; +} +EXPORT_SYMBOL(dmam_declare_coherent_memory); + +/** + * dmam_release_declared_memory - Managed dma_release_declared_memory(). + * @dev: Device to release declared coherent memory for + * + * Managed dmam_release_declared_memory(). + */ +void dmam_release_declared_memory(struct device *dev) +{ + WARN_ON(devres_destroy(dev, dmam_coherent_decl_release, NULL, NULL)); +} +EXPORT_SYMBOL(dmam_release_declared_memory); + +#endif diff --git a/drivers/base/driver.c b/drivers/base/driver.c new file mode 100644 index 0000000..1e2bda7 --- /dev/null +++ b/drivers/base/driver.c @@ -0,0 +1,277 @@ +/* + * driver.c - centralized device driver management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2007 Novell Inc. + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/string.h> +#include "base.h" + +static struct device *next_device(struct klist_iter *i) +{ + struct klist_node *n = klist_next(i); + return n ? container_of(n, struct device, knode_driver) : NULL; +} + +/** + * driver_for_each_device - Iterator for devices bound to a driver. + * @drv: Driver we're iterating. + * @start: Device to begin with + * @data: Data to pass to the callback. + * @fn: Function to call for each device. + * + * Iterate over the @drv's list of devices calling @fn for each one. + */ +int driver_for_each_device(struct device_driver *drv, struct device *start, + void *data, int (*fn)(struct device *, void *)) +{ + struct klist_iter i; + struct device *dev; + int error = 0; + + if (!drv) + return -EINVAL; + + klist_iter_init_node(&drv->p->klist_devices, &i, + start ? &start->knode_driver : NULL); + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); + return error; +} +EXPORT_SYMBOL_GPL(driver_for_each_device); + +/** + * driver_find_device - device iterator for locating a particular device. + * @drv: The device's driver + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the driver_for_each_device() function above, but + * it returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct device *driver_find_device(struct device_driver *drv, + struct device *start, void *data, + int (*match)(struct device *dev, void *data)) +{ + struct klist_iter i; + struct device *dev; + + if (!drv) + return NULL; + + klist_iter_init_node(&drv->p->klist_devices, &i, + (start ? &start->knode_driver : NULL)); + while ((dev = next_device(&i))) + if (match(dev, data) && get_device(dev)) + break; + klist_iter_exit(&i); + return dev; +} +EXPORT_SYMBOL_GPL(driver_find_device); + +/** + * 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 (drv) + error = sysfs_create_file(&drv->p->kobj, &attr->attr); + else + error = -EINVAL; + return error; +} +EXPORT_SYMBOL_GPL(driver_create_file); + +/** + * 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 (drv) + sysfs_remove_file(&drv->p->kobj, &attr->attr); +} +EXPORT_SYMBOL_GPL(driver_remove_file); + +/** + * driver_add_kobj - add a kobject below the specified driver + * @drv: requesting device driver + * @kobj: kobject to add below this driver + * @fmt: format string that names the kobject + * + * You really don't want to do this, this is only here due to one looney + * iseries driver, go poke those developers if you are annoyed about + * this... + */ +int driver_add_kobj(struct device_driver *drv, struct kobject *kobj, + const char *fmt, ...) +{ + va_list args; + char *name; + int ret; + + va_start(args, fmt); + name = kvasprintf(GFP_KERNEL, fmt, args); + va_end(args); + + if (!name) + return -ENOMEM; + + ret = kobject_add(kobj, &drv->p->kobj, "%s", name); + kfree(name); + return ret; +} +EXPORT_SYMBOL_GPL(driver_add_kobj); + +/** + * get_driver - increment driver reference count. + * @drv: driver. + */ +struct device_driver *get_driver(struct device_driver *drv) +{ + if (drv) { + struct driver_private *priv; + struct kobject *kobj; + + kobj = kobject_get(&drv->p->kobj); + priv = to_driver(kobj); + return priv->driver; + } + return NULL; +} +EXPORT_SYMBOL_GPL(get_driver); + +/** + * put_driver - decrement driver's refcount. + * @drv: driver. + */ +void put_driver(struct device_driver *drv) +{ + kobject_put(&drv->p->kobj); +} +EXPORT_SYMBOL_GPL(put_driver); + +static int driver_add_groups(struct device_driver *drv, + struct attribute_group **groups) +{ + int error = 0; + int i; + + if (groups) { + for (i = 0; groups[i]; i++) { + error = sysfs_create_group(&drv->p->kobj, groups[i]); + if (error) { + while (--i >= 0) + sysfs_remove_group(&drv->p->kobj, + groups[i]); + break; + } + } + } + return error; +} + +static void driver_remove_groups(struct device_driver *drv, + struct attribute_group **groups) +{ + int i; + + if (groups) + for (i = 0; groups[i]; i++) + sysfs_remove_group(&drv->p->kobj, groups[i]); +} + +/** + * 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. + */ +int driver_register(struct device_driver *drv) +{ + int ret; + struct device_driver *other; + + if ((drv->bus->probe && drv->probe) || + (drv->bus->remove && drv->remove) || + (drv->bus->shutdown && drv->shutdown)) + printk(KERN_WARNING "Driver '%s' needs updating - please use " + "bus_type methods\n", drv->name); + + other = driver_find(drv->name, drv->bus); + if (other) { + put_driver(other); + printk(KERN_ERR "Error: Driver '%s' is already registered, " + "aborting...\n", drv->name); + return -EEXIST; + } + + ret = bus_add_driver(drv); + if (ret) + return ret; + ret = driver_add_groups(drv, drv->groups); + if (ret) + bus_remove_driver(drv); + return ret; +} +EXPORT_SYMBOL_GPL(driver_register); + +/** + * driver_unregister - remove driver from system. + * @drv: driver. + * + * Again, we pass off most of the work to the bus-level call. + */ +void driver_unregister(struct device_driver *drv) +{ + driver_remove_groups(drv, drv->groups); + bus_remove_driver(drv); +} +EXPORT_SYMBOL_GPL(driver_unregister); + +/** + * 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->p->drivers_kset, name); + struct driver_private *priv; + + if (k) { + priv = to_driver(k); + return priv->driver; + } + return NULL; +} +EXPORT_SYMBOL_GPL(driver_find); diff --git a/drivers/base/firmware.c b/drivers/base/firmware.c new file mode 100644 index 0000000..1138155 --- /dev/null +++ b/drivers/base/firmware.c @@ -0,0 +1,27 @@ +/* + * firmware.c - firmware subsystem hoohaw. + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2007 Novell Inc. + * + * This file is released under the GPLv2 + */ +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> + +#include "base.h" + +struct kobject *firmware_kobj; +EXPORT_SYMBOL_GPL(firmware_kobj); + +int __init firmware_init(void) +{ + firmware_kobj = kobject_create_and_add("firmware", NULL); + if (!firmware_kobj) + return -ENOMEM; + return 0; +} diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c new file mode 100644 index 0000000..b7e5710 --- /dev/null +++ b/drivers/base/firmware_class.c @@ -0,0 +1,617 @@ +/* + * firmware_class.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * Please see Documentation/firmware_class/ for more information. + * + */ + +#include <linux/capability.h> +#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 <linux/mutex.h> +#include <linux/kthread.h> + +#include <linux/firmware.h> +#include "base.h" + +#define to_dev(obj) container_of(obj, struct device, kobj) + +MODULE_AUTHOR("Manuel Estrada Sainz"); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +enum { + FW_STATUS_LOADING, + FW_STATUS_DONE, + FW_STATUS_ABORT, +}; + +static int loading_timeout = 60; /* 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 DEFINE_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; +}; + +#ifdef CONFIG_FW_LOADER +extern struct builtin_fw __start_builtin_fw[]; +extern struct builtin_fw __end_builtin_fw[]; +#else /* Module case. Avoid ifdefs later; it'll all optimise out */ +static struct builtin_fw *__start_builtin_fw; +static struct builtin_fw *__end_builtin_fw; +#endif + +static 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 - set number of seconds to wait for firmware + * @class: device class pointer + * @buf: buffer to scan for timeout value + * @count: number of bytes in @buf + * + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be returned to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait forever'. + **/ +static ssize_t +firmware_timeout_store(struct class *class, const char *buf, size_t count) +{ + loading_timeout = simple_strtol(buf, NULL, 10); + if (loading_timeout < 0) + loading_timeout = 0; + return count; +} + +static CLASS_ATTR(timeout, 0644, firmware_timeout_show, firmware_timeout_store); + +static void fw_dev_release(struct device *dev); + +static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + + if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id)) + return -ENOMEM; + if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout)) + return -ENOMEM; + + return 0; +} + +static struct class firmware_class = { + .name = "firmware", + .dev_uevent = firmware_uevent, + .dev_release = fw_dev_release, +}; + +static ssize_t firmware_loading_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status); + return sprintf(buf, "%d\n", loading); +} + +/** + * firmware_loading_store - set value in the 'loading' control file + * @dev: device pointer + * @attr: device attribute pointer + * @buf: buffer to scan for loading control value + * @count: number of bytes in @buf + * + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and hand 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 device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + int loading = simple_strtol(buf, NULL, 10); + + switch (loading) { + case 1: + mutex_lock(&fw_lock); + if (!fw_priv->fw) { + mutex_unlock(&fw_lock); + break; + } + 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); + mutex_unlock(&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: + dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); + /* fallthrough */ + case -1: + fw_load_abort(fw_priv); + break; + } + + return count; +} + +static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); + +static ssize_t +firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = to_dev(kobj); + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + struct firmware *fw; + ssize_t ret_count; + + mutex_lock(&fw_lock); + fw = fw_priv->fw; + if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) { + ret_count = -ENODEV; + goto out; + } + ret_count = memory_read_from_buffer(buffer, count, &offset, + fw->data, fw->size); +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static int +fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) +{ + u8 *new_data; + int new_size = fw_priv->alloc_size; + + if (min_size <= fw_priv->alloc_size) + return 0; + + new_size = ALIGN(min_size, PAGE_SIZE); + new_data = vmalloc(new_size); + if (!new_data) { + printk(KERN_ERR "%s: unable to alloc buffer\n", __func__); + /* Make sure that we don't keep incomplete data */ + fw_load_abort(fw_priv); + return -ENOMEM; + } + fw_priv->alloc_size = new_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 - write method for firmware + * @kobj: kobject for the device + * @bin_attr: bin_attr structure + * @buffer: buffer being written + * @offset: buffer offset for write in total data store area + * @count: buffer size + * + * Data written to the 'data' attribute will be later handed to + * the driver as a firmware image. + **/ +static ssize_t +firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = to_dev(kobj); + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + struct firmware *fw; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + fw = fw_priv->fw; + if (!fw || 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((u8 *)fw->data + offset, buffer, count); + + fw->size = max_t(size_t, offset + count, fw->size); + retval = count; +out: + mutex_unlock(&fw_lock); + return retval; +} + +static struct bin_attribute firmware_attr_data_tmpl = { + .attr = {.name = "data", .mode = 0644}, + .size = 0, + .read = firmware_data_read, + .write = firmware_data_write, +}; + +static void fw_dev_release(struct device *dev) +{ + struct firmware_priv *fw_priv = dev_get_drvdata(dev); + + kfree(fw_priv); + kfree(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_device_id(struct device *f_dev, struct device *dev) +{ + /* XXX warning we should watch out for name collisions */ + strlcpy(f_dev->bus_id, dev->bus_id, BUS_ID_SIZE); +} + +static int fw_register_device(struct device **dev_p, const char *fw_name, + struct device *device) +{ + int retval; + struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv), + GFP_KERNEL); + struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL); + + *dev_p = NULL; + + if (!fw_priv || !f_dev) { + dev_err(device, "%s: kmalloc failed\n", __func__); + retval = -ENOMEM; + goto error_kfree; + } + + 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_device_id(f_dev, device); + f_dev->parent = device; + f_dev->class = &firmware_class; + dev_set_drvdata(f_dev, fw_priv); + f_dev->uevent_suppress = 1; + retval = device_register(f_dev); + if (retval) { + dev_err(device, "%s: device_register failed\n", __func__); + goto error_kfree; + } + *dev_p = f_dev; + return 0; + +error_kfree: + kfree(fw_priv); + kfree(f_dev); + return retval; +} + +static int fw_setup_device(struct firmware *fw, struct device **dev_p, + const char *fw_name, struct device *device, + int uevent) +{ + struct device *f_dev; + struct firmware_priv *fw_priv; + int retval; + + *dev_p = NULL; + retval = fw_register_device(&f_dev, fw_name, device); + if (retval) + goto out; + + /* Need to pin this module until class device is destroyed */ + __module_get(THIS_MODULE); + + fw_priv = dev_get_drvdata(f_dev); + + fw_priv->fw = fw; + retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data); + if (retval) { + dev_err(device, "%s: sysfs_create_bin_file failed\n", __func__); + goto error_unreg; + } + + retval = device_create_file(f_dev, &dev_attr_loading); + if (retval) { + dev_err(device, "%s: device_create_file failed\n", __func__); + goto error_unreg; + } + + if (uevent) + f_dev->uevent_suppress = 0; + *dev_p = f_dev; + goto out; + +error_unreg: + device_unregister(f_dev); +out: + return retval; +} + +static int +_request_firmware(const struct firmware **firmware_p, const char *name, + struct device *device, int uevent) +{ + struct device *f_dev; + struct firmware_priv *fw_priv; + struct firmware *firmware; + struct builtin_fw *builtin; + int retval; + + if (!firmware_p) + return -EINVAL; + + *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); + if (!firmware) { + dev_err(device, "%s: kmalloc(struct firmware) failed\n", + __func__); + retval = -ENOMEM; + goto out; + } + + for (builtin = __start_builtin_fw; builtin != __end_builtin_fw; + builtin++) { + if (strcmp(name, builtin->name)) + continue; + dev_info(device, "firmware: using built-in firmware %s\n", + name); + firmware->size = builtin->size; + firmware->data = builtin->data; + return 0; + } + + if (uevent) + dev_info(device, "firmware: requesting %s\n", name); + + retval = fw_setup_device(firmware, &f_dev, name, device, uevent); + if (retval) + goto error_kfree_fw; + + fw_priv = dev_get_drvdata(f_dev); + + if (uevent) { + if (loading_timeout > 0) { + fw_priv->timeout.expires = jiffies + loading_timeout * HZ; + add_timer(&fw_priv->timeout); + } + + kobject_uevent(&f_dev->kobj, KOBJ_ADD); + wait_for_completion(&fw_priv->completion); + set_bit(FW_STATUS_DONE, &fw_priv->status); + del_timer_sync(&fw_priv->timeout); + } else + wait_for_completion(&fw_priv->completion); + + mutex_lock(&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; + mutex_unlock(&fw_lock); + device_unregister(f_dev); + goto out; + +error_kfree_fw: + kfree(firmware); + *firmware_p = NULL; +out: + return retval; +} + +/** + * request_firmware: - send firmware request and wait for it + * @firmware_p: pointer to firmware image + * @name: name of firmware file + * @device: device for which firmware is being loaded + * + * @firmware_p 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 used as $FIRMWARE in the uevent 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) +{ + int uevent = 1; + return _request_firmware(firmware_p, name, device, uevent); +} + +/** + * release_firmware: - release the resource associated with a firmware image + * @fw: firmware resource to release + **/ +void +release_firmware(const struct firmware *fw) +{ + struct builtin_fw *builtin; + + if (fw) { + for (builtin = __start_builtin_fw; builtin != __end_builtin_fw; + builtin++) { + if (fw->data == builtin->data) + goto free_fw; + } + vfree(fw->data); + free_fw: + kfree(fw); + } +} + +/* 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); + int uevent; +}; + +static int +request_firmware_work_func(void *arg) +{ + struct firmware_work *fw_work = arg; + const struct firmware *fw; + int ret; + if (!arg) { + WARN_ON(1); + return 0; + } + ret = _request_firmware(&fw, fw_work->name, fw_work->device, + fw_work->uevent); + if (ret < 0) + fw_work->cont(NULL, fw_work->context); + else { + fw_work->cont(fw, fw_work->context); + release_firmware(fw); + } + module_put(fw_work->module); + kfree(fw_work); + return ret; +} + +/** + * request_firmware_nowait: asynchronous version of request_firmware + * @module: module requesting the firmware + * @uevent: sends uevent to copy the firmware image if this flag + * is non-zero else the firmware copy must be done manually. + * @name: name of firmware file + * @device: device for which firmware is being loaded + * @context: will be passed over to @cont, and + * @fw may be %NULL if firmware request fails. + * @cont: function will be called asynchronously when the firmware + * request is over. + * + * Asynchronous variant of request_firmware() for contexts where + * it is not possible to sleep. + **/ +int +request_firmware_nowait( + struct module *module, int uevent, + const char *name, struct device *device, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct task_struct *task; + struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work), + GFP_ATOMIC); + + 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, + .uevent = uevent, + }; + + task = kthread_run(request_firmware_work_func, fw_work, + "firmware/%s", name); + + if (IS_ERR(task)) { + fw_work->cont(NULL, fw_work->context); + module_put(fw_work->module); + kfree(fw_work); + return PTR_ERR(task); + } + 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", __func__); + return error; + } + error = class_create_file(&firmware_class, &class_attr_timeout); + if (error) { + printk(KERN_ERR "%s: class_create_file failed\n", + __func__); + class_unregister(&firmware_class); + } + return error; + +} +static void __exit +firmware_class_exit(void) +{ + class_unregister(&firmware_class); +} + +fs_initcall(firmware_class_init); +module_exit(firmware_class_exit); + +EXPORT_SYMBOL(release_firmware); +EXPORT_SYMBOL(request_firmware); +EXPORT_SYMBOL(request_firmware_nowait); diff --git a/drivers/base/hypervisor.c b/drivers/base/hypervisor.c new file mode 100644 index 0000000..6428cba --- /dev/null +++ b/drivers/base/hypervisor.c @@ -0,0 +1,24 @@ +/* + * hypervisor.c - /sys/hypervisor subsystem. + * + * Copyright (C) IBM Corp. 2006 + * Copyright (C) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (C) 2007 Novell Inc. + * + * This file is released under the GPLv2 + */ + +#include <linux/kobject.h> +#include <linux/device.h> +#include "base.h" + +struct kobject *hypervisor_kobj; +EXPORT_SYMBOL_GPL(hypervisor_kobj); + +int __init hypervisor_init(void) +{ + hypervisor_kobj = kobject_create_and_add("hypervisor", NULL); + if (!hypervisor_kobj) + return -ENOMEM; + return 0; +} diff --git a/drivers/base/init.c b/drivers/base/init.c new file mode 100644 index 0000000..7bd9b6a --- /dev/null +++ b/drivers/base/init.c @@ -0,0 +1,36 @@ +/* + * 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> +#include <linux/memory.h> + +#include "base.h" + +/** + * 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(); + hypervisor_init(); + + /* These are also core pieces, but must come after the + * core core pieces. + */ + platform_bus_init(); + system_bus_init(); + cpu_dev_init(); + memory_dev_init(); +} diff --git a/drivers/base/isa.c b/drivers/base/isa.c new file mode 100644 index 0000000..efd5775 --- /dev/null +++ b/drivers/base/isa.c @@ -0,0 +1,184 @@ +/* + * ISA bus. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/isa.h> + +static struct device isa_bus = { + .bus_id = "isa" +}; + +struct isa_dev { + struct device dev; + struct device *next; + unsigned int id; +}; + +#define to_isa_dev(x) container_of((x), struct isa_dev, dev) + +static int isa_bus_match(struct device *dev, struct device_driver *driver) +{ + struct isa_driver *isa_driver = to_isa_driver(driver); + + if (dev->platform_data == isa_driver) { + if (!isa_driver->match || + isa_driver->match(dev, to_isa_dev(dev)->id)) + return 1; + dev->platform_data = NULL; + } + return 0; +} + +static int isa_bus_probe(struct device *dev) +{ + struct isa_driver *isa_driver = dev->platform_data; + + if (isa_driver->probe) + return isa_driver->probe(dev, to_isa_dev(dev)->id); + + return 0; +} + +static int isa_bus_remove(struct device *dev) +{ + struct isa_driver *isa_driver = dev->platform_data; + + if (isa_driver->remove) + return isa_driver->remove(dev, to_isa_dev(dev)->id); + + return 0; +} + +static void isa_bus_shutdown(struct device *dev) +{ + struct isa_driver *isa_driver = dev->platform_data; + + if (isa_driver->shutdown) + isa_driver->shutdown(dev, to_isa_dev(dev)->id); +} + +static int isa_bus_suspend(struct device *dev, pm_message_t state) +{ + struct isa_driver *isa_driver = dev->platform_data; + + if (isa_driver->suspend) + return isa_driver->suspend(dev, to_isa_dev(dev)->id, state); + + return 0; +} + +static int isa_bus_resume(struct device *dev) +{ + struct isa_driver *isa_driver = dev->platform_data; + + if (isa_driver->resume) + return isa_driver->resume(dev, to_isa_dev(dev)->id); + + return 0; +} + +static struct bus_type isa_bus_type = { + .name = "isa", + .match = isa_bus_match, + .probe = isa_bus_probe, + .remove = isa_bus_remove, + .shutdown = isa_bus_shutdown, + .suspend = isa_bus_suspend, + .resume = isa_bus_resume +}; + +static void isa_dev_release(struct device *dev) +{ + kfree(to_isa_dev(dev)); +} + +void isa_unregister_driver(struct isa_driver *isa_driver) +{ + struct device *dev = isa_driver->devices; + + while (dev) { + struct device *tmp = to_isa_dev(dev)->next; + device_unregister(dev); + dev = tmp; + } + driver_unregister(&isa_driver->driver); +} +EXPORT_SYMBOL_GPL(isa_unregister_driver); + +int isa_register_driver(struct isa_driver *isa_driver, unsigned int ndev) +{ + int error; + unsigned int id; + + isa_driver->driver.bus = &isa_bus_type; + isa_driver->devices = NULL; + + error = driver_register(&isa_driver->driver); + if (error) + return error; + + for (id = 0; id < ndev; id++) { + struct isa_dev *isa_dev; + + isa_dev = kzalloc(sizeof *isa_dev, GFP_KERNEL); + if (!isa_dev) { + error = -ENOMEM; + break; + } + + isa_dev->dev.parent = &isa_bus; + isa_dev->dev.bus = &isa_bus_type; + + snprintf(isa_dev->dev.bus_id, BUS_ID_SIZE, "%s.%u", + isa_driver->driver.name, id); + + isa_dev->dev.platform_data = isa_driver; + isa_dev->dev.release = isa_dev_release; + isa_dev->id = id; + + isa_dev->dev.coherent_dma_mask = DMA_24BIT_MASK; + isa_dev->dev.dma_mask = &isa_dev->dev.coherent_dma_mask; + + error = device_register(&isa_dev->dev); + if (error) { + put_device(&isa_dev->dev); + break; + } + + if (isa_dev->dev.platform_data) { + isa_dev->next = isa_driver->devices; + isa_driver->devices = &isa_dev->dev; + } else + device_unregister(&isa_dev->dev); + } + + if (!error && !isa_driver->devices) + error = -ENODEV; + + if (error) + isa_unregister_driver(isa_driver); + + return error; +} +EXPORT_SYMBOL_GPL(isa_register_driver); + +static int __init isa_bus_init(void) +{ + int error; + + error = bus_register(&isa_bus_type); + if (!error) { + error = device_register(&isa_bus); + if (error) + bus_unregister(&isa_bus_type); + } + return error; +} + +device_initcall(isa_bus_init); diff --git a/drivers/base/map.c b/drivers/base/map.c new file mode 100644 index 0000000..e87017f --- /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/mutex.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 mutex *lock; +}; + +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; + } + mutex_lock(domain->lock); + 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; + } + mutex_unlock(domain->lock); + 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; + + mutex_lock(domain->lock); + 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; + } + } + } + mutex_unlock(domain->lock); + 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: + mutex_lock(domain->lock); + 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; + } + mutex_unlock(domain->lock); + kobj = probe(dev, index, data); + /* Currently ->owner protects _only_ ->probe() itself. */ + module_put(owner); + if (kobj) + return kobj; + goto retry; + } + mutex_unlock(domain->lock); + return NULL; +} + +struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock) +{ + struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL); + struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL); + int i; + + if ((p == NULL) || (base == NULL)) { + kfree(p); + kfree(base); + return NULL; + } + + base->dev = 1; + base->range = ~0; + base->get = base_probe; + for (i = 0; i < 255; i++) + p->probes[i] = base; + p->lock = lock; + return p; +} diff --git a/drivers/base/memory.c b/drivers/base/memory.c new file mode 100644 index 0000000..5260e9e --- /dev/null +++ b/drivers/base/memory.c @@ -0,0 +1,476 @@ +/* + * drivers/base/memory.c - basic Memory class support + * + * Written by Matt Tolentino <matthew.e.tolentino@intel.com> + * Dave Hansen <haveblue@us.ibm.com> + * + * This file provides the necessary infrastructure to represent + * a SPARSEMEM-memory-model system's physical memory in /sysfs. + * All arch-independent code that assumes MEMORY_HOTPLUG requires + * SPARSEMEM should be contained here, or in mm/memory_hotplug.c. + */ + +#include <linux/sysdev.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/topology.h> +#include <linux/capability.h> +#include <linux/device.h> +#include <linux/memory.h> +#include <linux/kobject.h> +#include <linux/memory_hotplug.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/stat.h> + +#include <asm/atomic.h> +#include <asm/uaccess.h> + +#define MEMORY_CLASS_NAME "memory" + +static struct sysdev_class memory_sysdev_class = { + .name = MEMORY_CLASS_NAME, +}; + +static const char *memory_uevent_name(struct kset *kset, struct kobject *kobj) +{ + return MEMORY_CLASS_NAME; +} + +static int memory_uevent(struct kset *kset, struct kobject *obj, struct kobj_uevent_env *env) +{ + int retval = 0; + + return retval; +} + +static struct kset_uevent_ops memory_uevent_ops = { + .name = memory_uevent_name, + .uevent = memory_uevent, +}; + +static BLOCKING_NOTIFIER_HEAD(memory_chain); + +int register_memory_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&memory_chain, nb); +} +EXPORT_SYMBOL(register_memory_notifier); + +void unregister_memory_notifier(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&memory_chain, nb); +} +EXPORT_SYMBOL(unregister_memory_notifier); + +/* + * register_memory - Setup a sysfs device for a memory block + */ +static +int register_memory(struct memory_block *memory, struct mem_section *section) +{ + int error; + + memory->sysdev.cls = &memory_sysdev_class; + memory->sysdev.id = __section_nr(section); + + error = sysdev_register(&memory->sysdev); + return error; +} + +static void +unregister_memory(struct memory_block *memory, struct mem_section *section) +{ + BUG_ON(memory->sysdev.cls != &memory_sysdev_class); + BUG_ON(memory->sysdev.id != __section_nr(section)); + + /* drop the ref. we got in remove_memory_block() */ + kobject_put(&memory->sysdev.kobj); + sysdev_unregister(&memory->sysdev); +} + +/* + * use this as the physical section index that this memsection + * uses. + */ + +static ssize_t show_mem_phys_index(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + struct memory_block *mem = + container_of(dev, struct memory_block, sysdev); + return sprintf(buf, "%08lx\n", mem->phys_index); +} + +/* + * Show whether the section of memory is likely to be hot-removable + */ +static ssize_t show_mem_removable(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + unsigned long start_pfn; + int ret; + struct memory_block *mem = + container_of(dev, struct memory_block, sysdev); + + start_pfn = section_nr_to_pfn(mem->phys_index); + ret = is_mem_section_removable(start_pfn, PAGES_PER_SECTION); + return sprintf(buf, "%d\n", ret); +} + +/* + * online, offline, going offline, etc. + */ +static ssize_t show_mem_state(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + struct memory_block *mem = + container_of(dev, struct memory_block, sysdev); + ssize_t len = 0; + + /* + * We can probably put these states in a nice little array + * so that they're not open-coded + */ + switch (mem->state) { + case MEM_ONLINE: + len = sprintf(buf, "online\n"); + break; + case MEM_OFFLINE: + len = sprintf(buf, "offline\n"); + break; + case MEM_GOING_OFFLINE: + len = sprintf(buf, "going-offline\n"); + break; + default: + len = sprintf(buf, "ERROR-UNKNOWN-%ld\n", + mem->state); + WARN_ON(1); + break; + } + + return len; +} + +int memory_notify(unsigned long val, void *v) +{ + return blocking_notifier_call_chain(&memory_chain, val, v); +} + +/* + * MEMORY_HOTPLUG depends on SPARSEMEM in mm/Kconfig, so it is + * OK to have direct references to sparsemem variables in here. + */ +static int +memory_block_action(struct memory_block *mem, unsigned long action) +{ + int i; + unsigned long psection; + unsigned long start_pfn, start_paddr; + struct page *first_page; + int ret; + int old_state = mem->state; + + psection = mem->phys_index; + first_page = pfn_to_page(psection << PFN_SECTION_SHIFT); + + /* + * The probe routines leave the pages reserved, just + * as the bootmem code does. Make sure they're still + * that way. + */ + if (action == MEM_ONLINE) { + for (i = 0; i < PAGES_PER_SECTION; i++) { + if (PageReserved(first_page+i)) + continue; + + printk(KERN_WARNING "section number %ld page number %d " + "not reserved, was it already online? \n", + psection, i); + return -EBUSY; + } + } + + switch (action) { + case MEM_ONLINE: + start_pfn = page_to_pfn(first_page); + ret = online_pages(start_pfn, PAGES_PER_SECTION); + break; + case MEM_OFFLINE: + mem->state = MEM_GOING_OFFLINE; + start_paddr = page_to_pfn(first_page) << PAGE_SHIFT; + ret = remove_memory(start_paddr, + PAGES_PER_SECTION << PAGE_SHIFT); + if (ret) { + mem->state = old_state; + break; + } + break; + default: + WARN(1, KERN_WARNING "%s(%p, %ld) unknown action: %ld\n", + __func__, mem, action, action); + ret = -EINVAL; + } + + return ret; +} + +static int memory_block_change_state(struct memory_block *mem, + unsigned long to_state, unsigned long from_state_req) +{ + int ret = 0; + mutex_lock(&mem->state_mutex); + + if (mem->state != from_state_req) { + ret = -EINVAL; + goto out; + } + + ret = memory_block_action(mem, to_state); + if (!ret) + mem->state = to_state; + +out: + mutex_unlock(&mem->state_mutex); + return ret; +} + +static ssize_t +store_mem_state(struct sys_device *dev, + struct sysdev_attribute *attr, const char *buf, size_t count) +{ + struct memory_block *mem; + unsigned int phys_section_nr; + int ret = -EINVAL; + + mem = container_of(dev, struct memory_block, sysdev); + phys_section_nr = mem->phys_index; + + if (!present_section_nr(phys_section_nr)) + goto out; + + if (!strncmp(buf, "online", min((int)count, 6))) + ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE); + else if(!strncmp(buf, "offline", min((int)count, 7))) + ret = memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE); +out: + if (ret) + return ret; + return count; +} + +/* + * phys_device is a bad name for this. What I really want + * is a way to differentiate between memory ranges that + * are part of physical devices that constitute + * a complete removable unit or fru. + * i.e. do these ranges belong to the same physical device, + * s.t. if I offline all of these sections I can then + * remove the physical device? + */ +static ssize_t show_phys_device(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + struct memory_block *mem = + container_of(dev, struct memory_block, sysdev); + return sprintf(buf, "%d\n", mem->phys_device); +} + +static SYSDEV_ATTR(phys_index, 0444, show_mem_phys_index, NULL); +static SYSDEV_ATTR(state, 0644, show_mem_state, store_mem_state); +static SYSDEV_ATTR(phys_device, 0444, show_phys_device, NULL); +static SYSDEV_ATTR(removable, 0444, show_mem_removable, NULL); + +#define mem_create_simple_file(mem, attr_name) \ + sysdev_create_file(&mem->sysdev, &attr_##attr_name) +#define mem_remove_simple_file(mem, attr_name) \ + sysdev_remove_file(&mem->sysdev, &attr_##attr_name) + +/* + * Block size attribute stuff + */ +static ssize_t +print_block_size(struct class *class, char *buf) +{ + return sprintf(buf, "%lx\n", (unsigned long)PAGES_PER_SECTION * PAGE_SIZE); +} + +static CLASS_ATTR(block_size_bytes, 0444, print_block_size, NULL); + +static int block_size_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_block_size_bytes.attr); +} + +/* + * Some architectures will have custom drivers to do this, and + * will not need to do it from userspace. The fake hot-add code + * as well as ppc64 will do all of their discovery in userspace + * and will require this interface. + */ +#ifdef CONFIG_ARCH_MEMORY_PROBE +static ssize_t +memory_probe_store(struct class *class, const char *buf, size_t count) +{ + u64 phys_addr; + int nid; + int ret; + + phys_addr = simple_strtoull(buf, NULL, 0); + + nid = memory_add_physaddr_to_nid(phys_addr); + ret = add_memory(nid, phys_addr, PAGES_PER_SECTION << PAGE_SHIFT); + + if (ret) + count = ret; + + return count; +} +static CLASS_ATTR(probe, S_IWUSR, NULL, memory_probe_store); + +static int memory_probe_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_probe.attr); +} +#else +static inline int memory_probe_init(void) +{ + return 0; +} +#endif + +/* + * Note that phys_device is optional. It is here to allow for + * differentiation between which *physical* devices each + * section belongs to... + */ + +static int add_memory_block(unsigned long node_id, struct mem_section *section, + unsigned long state, int phys_device) +{ + struct memory_block *mem = kzalloc(sizeof(*mem), GFP_KERNEL); + int ret = 0; + + if (!mem) + return -ENOMEM; + + mem->phys_index = __section_nr(section); + mem->state = state; + mutex_init(&mem->state_mutex); + mem->phys_device = phys_device; + + ret = register_memory(mem, section); + if (!ret) + ret = mem_create_simple_file(mem, phys_index); + if (!ret) + ret = mem_create_simple_file(mem, state); + if (!ret) + ret = mem_create_simple_file(mem, phys_device); + if (!ret) + ret = mem_create_simple_file(mem, removable); + + return ret; +} + +/* + * For now, we have a linear search to go find the appropriate + * memory_block corresponding to a particular phys_index. If + * this gets to be a real problem, we can always use a radix + * tree or something here. + * + * This could be made generic for all sysdev classes. + */ +static struct memory_block *find_memory_block(struct mem_section *section) +{ + struct kobject *kobj; + struct sys_device *sysdev; + struct memory_block *mem; + char name[sizeof(MEMORY_CLASS_NAME) + 9 + 1]; + + /* + * This only works because we know that section == sysdev->id + * slightly redundant with sysdev_register() + */ + sprintf(&name[0], "%s%d", MEMORY_CLASS_NAME, __section_nr(section)); + + kobj = kset_find_obj(&memory_sysdev_class.kset, name); + if (!kobj) + return NULL; + + sysdev = container_of(kobj, struct sys_device, kobj); + mem = container_of(sysdev, struct memory_block, sysdev); + + return mem; +} + +int remove_memory_block(unsigned long node_id, struct mem_section *section, + int phys_device) +{ + struct memory_block *mem; + + mem = find_memory_block(section); + mem_remove_simple_file(mem, phys_index); + mem_remove_simple_file(mem, state); + mem_remove_simple_file(mem, phys_device); + mem_remove_simple_file(mem, removable); + unregister_memory(mem, section); + + return 0; +} + +/* + * need an interface for the VM to add new memory regions, + * but without onlining it. + */ +int register_new_memory(struct mem_section *section) +{ + return add_memory_block(0, section, MEM_OFFLINE, 0); +} + +int unregister_memory_section(struct mem_section *section) +{ + if (!present_section(section)) + return -EINVAL; + + return remove_memory_block(0, section, 0); +} + +/* + * Initialize the sysfs support for memory devices... + */ +int __init memory_dev_init(void) +{ + unsigned int i; + int ret; + int err; + + memory_sysdev_class.kset.uevent_ops = &memory_uevent_ops; + ret = sysdev_class_register(&memory_sysdev_class); + if (ret) + goto out; + + /* + * Create entries for memory sections that were found + * during boot and have been initialized + */ + for (i = 0; i < NR_MEM_SECTIONS; i++) { + if (!present_section_nr(i)) + continue; + err = add_memory_block(0, __nr_to_section(i), MEM_ONLINE, 0); + if (!ret) + ret = err; + } + + err = memory_probe_init(); + if (!ret) + ret = err; + err = block_size_init(); + if (!ret) + ret = err; +out: + if (ret) + printk(KERN_ERR "%s() failed: %d\n", __func__, ret); + return ret; +} diff --git a/drivers/base/module.c b/drivers/base/module.c new file mode 100644 index 0000000..103be9c --- /dev/null +++ b/drivers/base/module.c @@ -0,0 +1,94 @@ +/* + * module.c - module sysfs fun for drivers + * + * This file is released under the GPLv2 + * + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/string.h> +#include "base.h" + +static char *make_driver_name(struct device_driver *drv) +{ + char *driver_name; + + driver_name = kmalloc(strlen(drv->name) + strlen(drv->bus->name) + 2, + GFP_KERNEL); + if (!driver_name) + return NULL; + + sprintf(driver_name, "%s:%s", drv->bus->name, drv->name); + return driver_name; +} + +static void module_create_drivers_dir(struct module_kobject *mk) +{ + if (!mk || mk->drivers_dir) + return; + + mk->drivers_dir = kobject_create_and_add("drivers", &mk->kobj); +} + +void module_add_driver(struct module *mod, struct device_driver *drv) +{ + char *driver_name; + int no_warn; + struct module_kobject *mk = NULL; + + if (!drv) + return; + + if (mod) + mk = &mod->mkobj; + else if (drv->mod_name) { + struct kobject *mkobj; + + /* Lookup built-in module entry in /sys/modules */ + mkobj = kset_find_obj(module_kset, drv->mod_name); + if (mkobj) { + mk = container_of(mkobj, struct module_kobject, kobj); + /* remember our module structure */ + drv->p->mkobj = mk; + /* kset_find_obj took a reference */ + kobject_put(mkobj); + } + } + + if (!mk) + return; + + /* Don't check return codes; these calls are idempotent */ + no_warn = sysfs_create_link(&drv->p->kobj, &mk->kobj, "module"); + driver_name = make_driver_name(drv); + if (driver_name) { + module_create_drivers_dir(mk); + no_warn = sysfs_create_link(mk->drivers_dir, &drv->p->kobj, + driver_name); + kfree(driver_name); + } +} + +void module_remove_driver(struct device_driver *drv) +{ + struct module_kobject *mk = NULL; + char *driver_name; + + if (!drv) + return; + + sysfs_remove_link(&drv->p->kobj, "module"); + + if (drv->owner) + mk = &drv->owner->mkobj; + else if (drv->p->mkobj) + mk = drv->p->mkobj; + if (mk && mk->drivers_dir) { + driver_name = make_driver_name(drv); + if (driver_name) { + sysfs_remove_link(mk->drivers_dir, driver_name); + kfree(driver_name); + } + } +} diff --git a/drivers/base/node.c b/drivers/base/node.c new file mode 100644 index 0000000..f520709 --- /dev/null +++ b/drivers/base/node.c @@ -0,0 +1,373 @@ +/* + * 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> +#include <linux/cpu.h> +#include <linux/device.h> +#include <linux/swap.h> + +static struct sysdev_class node_class = { + .name = "node", +}; + + +static ssize_t node_read_cpumap(struct sys_device *dev, int type, char *buf) +{ + struct node *node_dev = to_node(dev); + node_to_cpumask_ptr(mask, node_dev->sysdev.id); + int len; + + /* 2008/04/07: buf currently PAGE_SIZE, need 9 chars per 32 bits. */ + BUILD_BUG_ON((NR_CPUS/32 * 9) > (PAGE_SIZE-1)); + + len = type? + cpulist_scnprintf(buf, PAGE_SIZE-2, *mask): + cpumask_scnprintf(buf, PAGE_SIZE-2, *mask); + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} + +static inline ssize_t node_read_cpumask(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + return node_read_cpumap(dev, 0, buf); +} +static inline ssize_t node_read_cpulist(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + return node_read_cpumap(dev, 1, buf); +} + +static SYSDEV_ATTR(cpumap, S_IRUGO, node_read_cpumask, NULL); +static SYSDEV_ATTR(cpulist, S_IRUGO, node_read_cpulist, NULL); + +#define K(x) ((x) << (PAGE_SHIFT - 10)) +static ssize_t node_read_meminfo(struct sys_device * dev, + struct sysdev_attribute *attr, char * buf) +{ + int n; + int nid = dev->id; + struct sysinfo i; + + si_meminfo_node(&i, 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 Active(anon): %8lu kB\n" + "Node %d Inactive(anon): %8lu kB\n" + "Node %d Active(file): %8lu kB\n" + "Node %d Inactive(file): %8lu kB\n" +#ifdef CONFIG_UNEVICTABLE_LRU + "Node %d Unevictable: %8lu kB\n" + "Node %d Mlocked: %8lu kB\n" +#endif +#ifdef CONFIG_HIGHMEM + "Node %d HighTotal: %8lu kB\n" + "Node %d HighFree: %8lu kB\n" + "Node %d LowTotal: %8lu kB\n" + "Node %d LowFree: %8lu kB\n" +#endif + "Node %d Dirty: %8lu kB\n" + "Node %d Writeback: %8lu kB\n" + "Node %d FilePages: %8lu kB\n" + "Node %d Mapped: %8lu kB\n" + "Node %d AnonPages: %8lu kB\n" + "Node %d PageTables: %8lu kB\n" + "Node %d NFS_Unstable: %8lu kB\n" + "Node %d Bounce: %8lu kB\n" + "Node %d WritebackTmp: %8lu kB\n" + "Node %d Slab: %8lu kB\n" + "Node %d SReclaimable: %8lu kB\n" + "Node %d SUnreclaim: %8lu kB\n", + nid, K(i.totalram), + nid, K(i.freeram), + nid, K(i.totalram - i.freeram), + nid, K(node_page_state(nid, NR_ACTIVE_ANON) + + node_page_state(nid, NR_ACTIVE_FILE)), + nid, K(node_page_state(nid, NR_INACTIVE_ANON) + + node_page_state(nid, NR_INACTIVE_FILE)), + nid, K(node_page_state(nid, NR_ACTIVE_ANON)), + nid, K(node_page_state(nid, NR_INACTIVE_ANON)), + nid, K(node_page_state(nid, NR_ACTIVE_FILE)), + nid, K(node_page_state(nid, NR_INACTIVE_FILE)), +#ifdef CONFIG_UNEVICTABLE_LRU + nid, K(node_page_state(nid, NR_UNEVICTABLE)), + nid, K(node_page_state(nid, NR_MLOCK)), +#endif +#ifdef CONFIG_HIGHMEM + nid, K(i.totalhigh), + nid, K(i.freehigh), + nid, K(i.totalram - i.totalhigh), + nid, K(i.freeram - i.freehigh), +#endif + nid, K(node_page_state(nid, NR_FILE_DIRTY)), + nid, K(node_page_state(nid, NR_WRITEBACK)), + nid, K(node_page_state(nid, NR_FILE_PAGES)), + nid, K(node_page_state(nid, NR_FILE_MAPPED)), + nid, K(node_page_state(nid, NR_ANON_PAGES)), + nid, K(node_page_state(nid, NR_PAGETABLE)), + nid, K(node_page_state(nid, NR_UNSTABLE_NFS)), + nid, K(node_page_state(nid, NR_BOUNCE)), + nid, K(node_page_state(nid, NR_WRITEBACK_TEMP)), + nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE) + + node_page_state(nid, NR_SLAB_UNRECLAIMABLE)), + nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE)), + nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE))); + 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, + struct sysdev_attribute *attr, char * buf) +{ + 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", + node_page_state(dev->id, NUMA_HIT), + node_page_state(dev->id, NUMA_MISS), + node_page_state(dev->id, NUMA_FOREIGN), + node_page_state(dev->id, NUMA_INTERLEAVE_HIT), + node_page_state(dev->id, NUMA_LOCAL), + node_page_state(dev->id, NUMA_OTHER)); +} +static SYSDEV_ATTR(numastat, S_IRUGO, node_read_numastat, NULL); + +static ssize_t node_read_distance(struct sys_device * dev, + struct sysdev_attribute *attr, 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 sysfs device for a node. + * @num - Node number to use when creating the device. + * + * Initialize and register the node device. + */ +int 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_cpulist); + sysdev_create_file(&node->sysdev, &attr_meminfo); + sysdev_create_file(&node->sysdev, &attr_numastat); + sysdev_create_file(&node->sysdev, &attr_distance); + + scan_unevictable_register_node(node); + } + return error; +} + +/** + * unregister_node - unregister a node device + * @node: node going away + * + * Unregisters a node device @node. All the devices on the node must be + * unregistered before calling this function. + */ +void unregister_node(struct node *node) +{ + sysdev_remove_file(&node->sysdev, &attr_cpumap); + sysdev_remove_file(&node->sysdev, &attr_cpulist); + sysdev_remove_file(&node->sysdev, &attr_meminfo); + sysdev_remove_file(&node->sysdev, &attr_numastat); + sysdev_remove_file(&node->sysdev, &attr_distance); + + scan_unevictable_unregister_node(node); + + sysdev_unregister(&node->sysdev); +} + +struct node node_devices[MAX_NUMNODES]; + +/* + * register cpu under node + */ +int register_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (!obj) + return 0; + return sysfs_create_link(&node_devices[nid].sysdev.kobj, + &obj->kobj, + kobject_name(&obj->kobj)); + } + + return 0; +} + +int unregister_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (obj) + sysfs_remove_link(&node_devices[nid].sysdev.kobj, + kobject_name(&obj->kobj)); + } + return 0; +} + +int register_one_node(int nid) +{ + int error = 0; + int cpu; + + if (node_online(nid)) { + int p_node = parent_node(nid); + struct node *parent = NULL; + + if (p_node != nid) + parent = &node_devices[p_node]; + + error = register_node(&node_devices[nid], nid, parent); + + /* link cpu under this node */ + for_each_present_cpu(cpu) { + if (cpu_to_node(cpu) == nid) + register_cpu_under_node(cpu, nid); + } + } + + return error; + +} + +void unregister_one_node(int nid) +{ + unregister_node(&node_devices[nid]); +} + +/* + * node states attributes + */ + +static ssize_t print_nodes_state(enum node_states state, char *buf) +{ + int n; + + n = nodelist_scnprintf(buf, PAGE_SIZE, node_states[state]); + if (n > 0 && PAGE_SIZE > n + 1) { + *(buf + n++) = '\n'; + *(buf + n++) = '\0'; + } + return n; +} + +static ssize_t print_nodes_possible(struct sysdev_class *class, char *buf) +{ + return print_nodes_state(N_POSSIBLE, buf); +} + +static ssize_t print_nodes_online(struct sysdev_class *class, char *buf) +{ + return print_nodes_state(N_ONLINE, buf); +} + +static ssize_t print_nodes_has_normal_memory(struct sysdev_class *class, + char *buf) +{ + return print_nodes_state(N_NORMAL_MEMORY, buf); +} + +static ssize_t print_nodes_has_cpu(struct sysdev_class *class, char *buf) +{ + return print_nodes_state(N_CPU, buf); +} + +static SYSDEV_CLASS_ATTR(possible, 0444, print_nodes_possible, NULL); +static SYSDEV_CLASS_ATTR(online, 0444, print_nodes_online, NULL); +static SYSDEV_CLASS_ATTR(has_normal_memory, 0444, print_nodes_has_normal_memory, + NULL); +static SYSDEV_CLASS_ATTR(has_cpu, 0444, print_nodes_has_cpu, NULL); + +#ifdef CONFIG_HIGHMEM +static ssize_t print_nodes_has_high_memory(struct sysdev_class *class, + char *buf) +{ + return print_nodes_state(N_HIGH_MEMORY, buf); +} + +static SYSDEV_CLASS_ATTR(has_high_memory, 0444, print_nodes_has_high_memory, + NULL); +#endif + +struct sysdev_class_attribute *node_state_attr[] = { + &attr_possible, + &attr_online, + &attr_has_normal_memory, +#ifdef CONFIG_HIGHMEM + &attr_has_high_memory, +#endif + &attr_has_cpu, +}; + +static int node_states_init(void) +{ + int i; + int err = 0; + + for (i = 0; i < NR_NODE_STATES; i++) { + int ret; + ret = sysdev_class_create_file(&node_class, node_state_attr[i]); + if (!err) + err = ret; + } + return err; +} + +static int __init register_node_type(void) +{ + int ret; + + ret = sysdev_class_register(&node_class); + if (!ret) + ret = node_states_init(); + + /* + * Note: we're not going to unregister the node class if we fail + * to register the node state class attribute files. + */ + return ret; +} +postcore_initcall(register_node_type); diff --git a/drivers/base/platform.c b/drivers/base/platform.c new file mode 100644 index 0000000..dfcbfe5 --- /dev/null +++ b/drivers/base/platform.c @@ -0,0 +1,984 @@ +/* + * 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/platform_device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/bootmem.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include "base.h" + +#define to_platform_driver(drv) (container_of((drv), struct platform_driver, \ + driver)) + +struct device platform_bus = { + .bus_id = "platform", +}; +EXPORT_SYMBOL_GPL(platform_bus); + +/** + * 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 (type == resource_type(r) && num-- == 0) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(platform_get_resource); + +/** + * 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 : -ENXIO; +} +EXPORT_SYMBOL_GPL(platform_get_irq); + +/** + * 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 (type == resource_type(r) && !strcmp(r->name, name)) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(platform_get_resource_byname); + +/** + * 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 : -ENXIO; +} +EXPORT_SYMBOL_GPL(platform_get_irq_byname); + +/** + * 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; +} +EXPORT_SYMBOL_GPL(platform_add_devices); + +struct platform_object { + struct platform_device pdev; + char name[1]; +}; + +/** + * platform_device_put + * @pdev: platform device to free + * + * Free all memory associated with a platform device. This function must + * _only_ be externally called in error cases. All other usage is a bug. + */ +void platform_device_put(struct platform_device *pdev) +{ + if (pdev) + put_device(&pdev->dev); +} +EXPORT_SYMBOL_GPL(platform_device_put); + +static void platform_device_release(struct device *dev) +{ + struct platform_object *pa = container_of(dev, struct platform_object, + pdev.dev); + + kfree(pa->pdev.dev.platform_data); + kfree(pa->pdev.resource); + kfree(pa); +} + +/** + * platform_device_alloc + * @name: base name of the device we're adding + * @id: instance id + * + * Create a platform device object which can have other objects attached + * to it, and which will have attached objects freed when it is released. + */ +struct platform_device *platform_device_alloc(const char *name, int id) +{ + struct platform_object *pa; + + pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL); + if (pa) { + strcpy(pa->name, name); + pa->pdev.name = pa->name; + pa->pdev.id = id; + device_initialize(&pa->pdev.dev); + pa->pdev.dev.release = platform_device_release; + } + + return pa ? &pa->pdev : NULL; +} +EXPORT_SYMBOL_GPL(platform_device_alloc); + +/** + * platform_device_add_resources + * @pdev: platform device allocated by platform_device_alloc to add resources to + * @res: set of resources that needs to be allocated for the device + * @num: number of resources + * + * Add a copy of the resources to the platform device. The memory + * associated with the resources will be freed when the platform device is + * released. + */ +int platform_device_add_resources(struct platform_device *pdev, + struct resource *res, unsigned int num) +{ + struct resource *r; + + r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL); + if (r) { + memcpy(r, res, sizeof(struct resource) * num); + pdev->resource = r; + pdev->num_resources = num; + } + return r ? 0 : -ENOMEM; +} +EXPORT_SYMBOL_GPL(platform_device_add_resources); + +/** + * platform_device_add_data + * @pdev: platform device allocated by platform_device_alloc to add resources to + * @data: platform specific data for this platform device + * @size: size of platform specific data + * + * Add a copy of platform specific data to the platform device's + * platform_data pointer. The memory associated with the platform data + * will be freed when the platform device is released. + */ +int platform_device_add_data(struct platform_device *pdev, const void *data, + size_t size) +{ + void *d; + + d = kmalloc(size, GFP_KERNEL); + if (d) { + memcpy(d, data, size); + pdev->dev.platform_data = d; + } + return d ? 0 : -ENOMEM; +} +EXPORT_SYMBOL_GPL(platform_device_add_data); + +/** + * platform_device_add - add a platform device to device hierarchy + * @pdev: platform device we're adding + * + * This is part 2 of platform_device_register(), though may be called + * separately _iff_ pdev was allocated by platform_device_alloc(). + */ +int platform_device_add(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.%d", 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 (resource_type(r) == IORESOURCE_MEM) + p = &iomem_resource; + else if (resource_type(r) == IORESOURCE_IO) + p = &ioport_resource; + } + + if (p && insert_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_add(&pdev->dev); + if (ret == 0) + return ret; + + failed: + while (--i >= 0) { + struct resource *r = &pdev->resource[i]; + unsigned long type = resource_type(r); + + if (type == IORESOURCE_MEM || type == IORESOURCE_IO) + release_resource(r); + } + + return ret; +} +EXPORT_SYMBOL_GPL(platform_device_add); + +/** + * platform_device_del - remove a platform-level device + * @pdev: platform device we're removing + * + * Note that this function will also release all memory- and port-based + * resources owned by the device (@dev->resource). This function must + * _only_ be externally called in error cases. All other usage is a bug. + */ +void platform_device_del(struct platform_device *pdev) +{ + int i; + + if (pdev) { + device_del(&pdev->dev); + + for (i = 0; i < pdev->num_resources; i++) { + struct resource *r = &pdev->resource[i]; + unsigned long type = resource_type(r); + + if (type == IORESOURCE_MEM || type == IORESOURCE_IO) + release_resource(r); + } + } +} +EXPORT_SYMBOL_GPL(platform_device_del); + +/** + * platform_device_register - add a platform-level device + * @pdev: platform device we're adding + */ +int platform_device_register(struct platform_device *pdev) +{ + device_initialize(&pdev->dev); + return platform_device_add(pdev); +} +EXPORT_SYMBOL_GPL(platform_device_register); + +/** + * platform_device_unregister - unregister a platform-level device + * @pdev: platform device we're unregistering + * + * Unregistration is done in 2 steps. First we release all resources + * and remove it from the subsystem, then we drop reference count by + * calling platform_device_put(). + */ +void platform_device_unregister(struct platform_device *pdev) +{ + platform_device_del(pdev); + platform_device_put(pdev); +} +EXPORT_SYMBOL_GPL(platform_device_unregister); + +/** + * 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 without waiting for the last reference to the device to be + * dropped. + * + * This interface is primarily intended for use with legacy drivers which + * probe hardware directly. Because such drivers create sysfs device nodes + * themselves, rather than letting system infrastructure handle such device + * enumeration tasks, they don't fully conform to the Linux driver model. + * In particular, when such drivers are built as modules, they can't be + * "hotplugged". + */ +struct platform_device *platform_device_register_simple(const char *name, + int id, + struct resource *res, + unsigned int num) +{ + struct platform_device *pdev; + int retval; + + pdev = platform_device_alloc(name, id); + if (!pdev) { + retval = -ENOMEM; + goto error; + } + + if (num) { + retval = platform_device_add_resources(pdev, res, num); + if (retval) + goto error; + } + + retval = platform_device_add(pdev); + if (retval) + goto error; + + return pdev; + +error: + platform_device_put(pdev); + return ERR_PTR(retval); +} +EXPORT_SYMBOL_GPL(platform_device_register_simple); + +/** + * platform_device_register_data + * @parent: parent device for the device we're adding + * @name: base name of the device we're adding + * @id: instance id + * @data: platform specific data for this platform device + * @size: size of platform specific data + * + * 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 without waiting for the last reference to the device to be + * dropped. + */ +struct platform_device *platform_device_register_data( + struct device *parent, + const char *name, int id, + const void *data, size_t size) +{ + struct platform_device *pdev; + int retval; + + pdev = platform_device_alloc(name, id); + if (!pdev) { + retval = -ENOMEM; + goto error; + } + + pdev->dev.parent = parent; + + if (size) { + retval = platform_device_add_data(pdev, data, size); + if (retval) + goto error; + } + + retval = platform_device_add(pdev); + if (retval) + goto error; + + return pdev; + +error: + platform_device_put(pdev); + return ERR_PTR(retval); +} + +static int platform_drv_probe(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + return drv->probe(dev); +} + +static int platform_drv_probe_fail(struct device *_dev) +{ + return -ENXIO; +} + +static int platform_drv_remove(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + return drv->remove(dev); +} + +static void platform_drv_shutdown(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + drv->shutdown(dev); +} + +static int platform_drv_suspend(struct device *_dev, pm_message_t state) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + return drv->suspend(dev, state); +} + +static int platform_drv_resume(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + return drv->resume(dev); +} + +/** + * platform_driver_register + * @drv: platform driver structure + */ +int platform_driver_register(struct platform_driver *drv) +{ + drv->driver.bus = &platform_bus_type; + if (drv->probe) + drv->driver.probe = platform_drv_probe; + if (drv->remove) + drv->driver.remove = platform_drv_remove; + if (drv->shutdown) + drv->driver.shutdown = platform_drv_shutdown; + if (drv->suspend) + drv->driver.suspend = platform_drv_suspend; + if (drv->resume) + drv->driver.resume = platform_drv_resume; + if (drv->pm) + drv->driver.pm = &drv->pm->base; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(platform_driver_register); + +/** + * platform_driver_unregister + * @drv: platform driver structure + */ +void platform_driver_unregister(struct platform_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(platform_driver_unregister); + +/** + * platform_driver_probe - register driver for non-hotpluggable device + * @drv: platform driver structure + * @probe: the driver probe routine, probably from an __init section + * + * Use this instead of platform_driver_register() when you know the device + * is not hotpluggable and has already been registered, and you want to + * remove its run-once probe() infrastructure from memory after the driver + * has bound to the device. + * + * One typical use for this would be with drivers for controllers integrated + * into system-on-chip processors, where the controller devices have been + * configured as part of board setup. + * + * Returns zero if the driver registered and bound to a device, else returns + * a negative error code and with the driver not registered. + */ +int __init_or_module platform_driver_probe(struct platform_driver *drv, + int (*probe)(struct platform_device *)) +{ + int retval, code; + + /* temporary section violation during probe() */ + drv->probe = probe; + retval = code = platform_driver_register(drv); + + /* Fixup that section violation, being paranoid about code scanning + * the list of drivers in order to probe new devices. Check to see + * if the probe was successful, and make sure any forced probes of + * new devices fail. + */ + spin_lock(&platform_bus_type.p->klist_drivers.k_lock); + drv->probe = NULL; + if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list)) + retval = -ENODEV; + drv->driver.probe = platform_drv_probe_fail; + spin_unlock(&platform_bus_type.p->klist_drivers.k_lock); + + if (code != retval) + platform_driver_unregister(drv); + return retval; +} +EXPORT_SYMBOL_GPL(platform_driver_probe); + +/* modalias support enables more hands-off userspace setup: + * (a) environment variable lets new-style hotplug events work once system is + * fully running: "modprobe $MODALIAS" + * (b) sysfs attribute lets new-style coldplug recover from hotplug events + * mishandled before system is fully running: "modprobe $(cat modalias)" + */ +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + int len = snprintf(buf, PAGE_SIZE, "platform:%s\n", pdev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute platform_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int platform_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct platform_device *pdev = to_platform_device(dev); + + add_uevent_var(env, "MODALIAS=platform:%s", pdev->name); + return 0; +} + +/** + * 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; + + pdev = container_of(dev, struct platform_device, dev); + return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); +} + +#ifdef CONFIG_PM_SLEEP + +static int platform_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + int ret = 0; + + if (dev->driver && dev->driver->suspend) + ret = dev->driver->suspend(dev, mesg); + + return ret; +} + +static int platform_legacy_suspend_late(struct device *dev, pm_message_t mesg) +{ + struct platform_driver *drv = to_platform_driver(dev->driver); + struct platform_device *pdev; + int ret = 0; + + pdev = container_of(dev, struct platform_device, dev); + if (dev->driver && drv->suspend_late) + ret = drv->suspend_late(pdev, mesg); + + return ret; +} + +static int platform_legacy_resume_early(struct device *dev) +{ + struct platform_driver *drv = to_platform_driver(dev->driver); + struct platform_device *pdev; + int ret = 0; + + pdev = container_of(dev, struct platform_device, dev); + if (dev->driver && drv->resume_early) + ret = drv->resume_early(pdev); + + return ret; +} + +static int platform_legacy_resume(struct device *dev) +{ + int ret = 0; + + if (dev->driver && dev->driver->resume) + ret = dev->driver->resume(dev); + + return ret; +} + +static int platform_pm_prepare(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm && drv->pm->prepare) + ret = drv->pm->prepare(dev); + + return ret; +} + +static void platform_pm_complete(struct device *dev) +{ + struct device_driver *drv = dev->driver; + + if (drv && drv->pm && drv->pm->complete) + drv->pm->complete(dev); +} + +#ifdef CONFIG_SUSPEND + +static int platform_pm_suspend(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm) { + if (drv->pm->suspend) + ret = drv->pm->suspend(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_SUSPEND); + } + + return ret; +} + +static int platform_pm_suspend_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->suspend_noirq) + ret = pdrv->pm->suspend_noirq(dev); + } else { + ret = platform_legacy_suspend_late(dev, PMSG_SUSPEND); + } + + return ret; +} + +static int platform_pm_resume(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm) { + if (drv->pm->resume) + ret = drv->pm->resume(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +static int platform_pm_resume_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->resume_noirq) + ret = pdrv->pm->resume_noirq(dev); + } else { + ret = platform_legacy_resume_early(dev); + } + + return ret; +} + +#else /* !CONFIG_SUSPEND */ + +#define platform_pm_suspend NULL +#define platform_pm_resume NULL +#define platform_pm_suspend_noirq NULL +#define platform_pm_resume_noirq NULL + +#endif /* !CONFIG_SUSPEND */ + +#ifdef CONFIG_HIBERNATION + +static int platform_pm_freeze(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->freeze) + ret = drv->pm->freeze(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_FREEZE); + } + + return ret; +} + +static int platform_pm_freeze_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->freeze_noirq) + ret = pdrv->pm->freeze_noirq(dev); + } else { + ret = platform_legacy_suspend_late(dev, PMSG_FREEZE); + } + + return ret; +} + +static int platform_pm_thaw(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm) { + if (drv->pm->thaw) + ret = drv->pm->thaw(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +static int platform_pm_thaw_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->thaw_noirq) + ret = pdrv->pm->thaw_noirq(dev); + } else { + ret = platform_legacy_resume_early(dev); + } + + return ret; +} + +static int platform_pm_poweroff(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm) { + if (drv->pm->poweroff) + ret = drv->pm->poweroff(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_HIBERNATE); + } + + return ret; +} + +static int platform_pm_poweroff_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->poweroff_noirq) + ret = pdrv->pm->poweroff_noirq(dev); + } else { + ret = platform_legacy_suspend_late(dev, PMSG_HIBERNATE); + } + + return ret; +} + +static int platform_pm_restore(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm) { + if (drv->pm->restore) + ret = drv->pm->restore(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +static int platform_pm_restore_noirq(struct device *dev) +{ + struct platform_driver *pdrv; + int ret = 0; + + if (!dev->driver) + return 0; + + pdrv = to_platform_driver(dev->driver); + if (pdrv->pm) { + if (pdrv->pm->restore_noirq) + ret = pdrv->pm->restore_noirq(dev); + } else { + ret = platform_legacy_resume_early(dev); + } + + return ret; +} + +#else /* !CONFIG_HIBERNATION */ + +#define platform_pm_freeze NULL +#define platform_pm_thaw NULL +#define platform_pm_poweroff NULL +#define platform_pm_restore NULL +#define platform_pm_freeze_noirq NULL +#define platform_pm_thaw_noirq NULL +#define platform_pm_poweroff_noirq NULL +#define platform_pm_restore_noirq NULL + +#endif /* !CONFIG_HIBERNATION */ + +static struct pm_ext_ops platform_pm_ops = { + .base = { + .prepare = platform_pm_prepare, + .complete = platform_pm_complete, + .suspend = platform_pm_suspend, + .resume = platform_pm_resume, + .freeze = platform_pm_freeze, + .thaw = platform_pm_thaw, + .poweroff = platform_pm_poweroff, + .restore = platform_pm_restore, + }, + .suspend_noirq = platform_pm_suspend_noirq, + .resume_noirq = platform_pm_resume_noirq, + .freeze_noirq = platform_pm_freeze_noirq, + .thaw_noirq = platform_pm_thaw_noirq, + .poweroff_noirq = platform_pm_poweroff_noirq, + .restore_noirq = platform_pm_restore_noirq, +}; + +#define PLATFORM_PM_OPS_PTR &platform_pm_ops + +#else /* !CONFIG_PM_SLEEP */ + +#define PLATFORM_PM_OPS_PTR NULL + +#endif /* !CONFIG_PM_SLEEP */ + +struct bus_type platform_bus_type = { + .name = "platform", + .dev_attrs = platform_dev_attrs, + .match = platform_match, + .uevent = platform_uevent, + .pm = PLATFORM_PM_OPS_PTR, +}; +EXPORT_SYMBOL_GPL(platform_bus_type); + +int __init platform_bus_init(void) +{ + int error; + + error = device_register(&platform_bus); + if (error) + return error; + error = bus_register(&platform_bus_type); + if (error) + device_unregister(&platform_bus); + return error; +} + +#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; +} +EXPORT_SYMBOL_GPL(dma_get_required_mask); +#endif diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile new file mode 100644 index 0000000..911208b --- /dev/null +++ b/drivers/base/power/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PM) += sysfs.o +obj-$(CONFIG_PM_SLEEP) += main.o +obj-$(CONFIG_PM_TRACE_RTC) += trace.o + +ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG +ccflags-$(CONFIG_PM_VERBOSE) += -DDEBUG diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c new file mode 100644 index 0000000..692c20b --- /dev/null +++ b/drivers/base/power/main.c @@ -0,0 +1,784 @@ +/* + * 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 separate list is used for keeping track of power info, because the power + * domain dependencies may differ from the ancestral dependencies that the + * subsystem list maintains. + */ + +#include <linux/device.h> +#include <linux/kallsyms.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/resume-trace.h> +#include <linux/rwsem.h> + +#include "../base.h" +#include "power.h" + +/* + * The entries in the dpm_list 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. + * + * Since device_pm_add() may be called with a device semaphore held, + * we must never try to acquire a device semaphore while holding + * dpm_list_mutex. + */ + +LIST_HEAD(dpm_list); + +static DEFINE_MUTEX(dpm_list_mtx); + +/* + * Set once the preparation of devices for a PM transition has started, reset + * before starting to resume devices. Protected by dpm_list_mtx. + */ +static bool transition_started; + +/** + * device_pm_lock - lock the list of active devices used by the PM core + */ +void device_pm_lock(void) +{ + mutex_lock(&dpm_list_mtx); +} + +/** + * device_pm_unlock - unlock the list of active devices used by the PM core + */ +void device_pm_unlock(void) +{ + mutex_unlock(&dpm_list_mtx); +} + +/** + * device_pm_add - add a device to the list of active devices + * @dev: Device to be added to the list + */ +void device_pm_add(struct device *dev) +{ + pr_debug("PM: Adding info for %s:%s\n", + dev->bus ? dev->bus->name : "No Bus", + kobject_name(&dev->kobj)); + mutex_lock(&dpm_list_mtx); + if (dev->parent) { + if (dev->parent->power.status >= DPM_SUSPENDING) + dev_warn(dev, "parent %s should not be sleeping\n", + dev->parent->bus_id); + } else if (transition_started) { + /* + * We refuse to register parentless devices while a PM + * transition is in progress in order to avoid leaving them + * unhandled down the road + */ + dev_WARN(dev, "Parentless device registered during a PM transaction\n"); + } + + list_add_tail(&dev->power.entry, &dpm_list); + mutex_unlock(&dpm_list_mtx); +} + +/** + * device_pm_remove - remove a device from the list of active devices + * @dev: Device to be removed from the list + * + * This function also removes the device's PM-related sysfs attributes. + */ +void device_pm_remove(struct device *dev) +{ + pr_debug("PM: Removing info for %s:%s\n", + dev->bus ? dev->bus->name : "No Bus", + kobject_name(&dev->kobj)); + mutex_lock(&dpm_list_mtx); + list_del_init(&dev->power.entry); + mutex_unlock(&dpm_list_mtx); +} + +/** + * pm_op - execute the PM operation appropiate for given PM event + * @dev: Device. + * @ops: PM operations to choose from. + * @state: PM transition of the system being carried out. + */ +static int pm_op(struct device *dev, struct pm_ops *ops, pm_message_t state) +{ + int error = 0; + + switch (state.event) { +#ifdef CONFIG_SUSPEND + case PM_EVENT_SUSPEND: + if (ops->suspend) { + error = ops->suspend(dev); + suspend_report_result(ops->suspend, error); + } + break; + case PM_EVENT_RESUME: + if (ops->resume) { + error = ops->resume(dev); + suspend_report_result(ops->resume, error); + } + break; +#endif /* CONFIG_SUSPEND */ +#ifdef CONFIG_HIBERNATION + case PM_EVENT_FREEZE: + case PM_EVENT_QUIESCE: + if (ops->freeze) { + error = ops->freeze(dev); + suspend_report_result(ops->freeze, error); + } + break; + case PM_EVENT_HIBERNATE: + if (ops->poweroff) { + error = ops->poweroff(dev); + suspend_report_result(ops->poweroff, error); + } + break; + case PM_EVENT_THAW: + case PM_EVENT_RECOVER: + if (ops->thaw) { + error = ops->thaw(dev); + suspend_report_result(ops->thaw, error); + } + break; + case PM_EVENT_RESTORE: + if (ops->restore) { + error = ops->restore(dev); + suspend_report_result(ops->restore, error); + } + break; +#endif /* CONFIG_HIBERNATION */ + default: + error = -EINVAL; + } + return error; +} + +/** + * pm_noirq_op - execute the PM operation appropiate for given PM event + * @dev: Device. + * @ops: PM operations to choose from. + * @state: PM transition of the system being carried out. + * + * The operation is executed with interrupts disabled by the only remaining + * functional CPU in the system. + */ +static int pm_noirq_op(struct device *dev, struct pm_ext_ops *ops, + pm_message_t state) +{ + int error = 0; + + switch (state.event) { +#ifdef CONFIG_SUSPEND + case PM_EVENT_SUSPEND: + if (ops->suspend_noirq) { + error = ops->suspend_noirq(dev); + suspend_report_result(ops->suspend_noirq, error); + } + break; + case PM_EVENT_RESUME: + if (ops->resume_noirq) { + error = ops->resume_noirq(dev); + suspend_report_result(ops->resume_noirq, error); + } + break; +#endif /* CONFIG_SUSPEND */ +#ifdef CONFIG_HIBERNATION + case PM_EVENT_FREEZE: + case PM_EVENT_QUIESCE: + if (ops->freeze_noirq) { + error = ops->freeze_noirq(dev); + suspend_report_result(ops->freeze_noirq, error); + } + break; + case PM_EVENT_HIBERNATE: + if (ops->poweroff_noirq) { + error = ops->poweroff_noirq(dev); + suspend_report_result(ops->poweroff_noirq, error); + } + break; + case PM_EVENT_THAW: + case PM_EVENT_RECOVER: + if (ops->thaw_noirq) { + error = ops->thaw_noirq(dev); + suspend_report_result(ops->thaw_noirq, error); + } + break; + case PM_EVENT_RESTORE: + if (ops->restore_noirq) { + error = ops->restore_noirq(dev); + suspend_report_result(ops->restore_noirq, error); + } + break; +#endif /* CONFIG_HIBERNATION */ + default: + error = -EINVAL; + } + return error; +} + +static char *pm_verb(int event) +{ + switch (event) { + case PM_EVENT_SUSPEND: + return "suspend"; + case PM_EVENT_RESUME: + return "resume"; + case PM_EVENT_FREEZE: + return "freeze"; + case PM_EVENT_QUIESCE: + return "quiesce"; + case PM_EVENT_HIBERNATE: + return "hibernate"; + case PM_EVENT_THAW: + return "thaw"; + case PM_EVENT_RESTORE: + return "restore"; + case PM_EVENT_RECOVER: + return "recover"; + default: + return "(unknown PM event)"; + } +} + +static void pm_dev_dbg(struct device *dev, pm_message_t state, char *info) +{ + dev_dbg(dev, "%s%s%s\n", info, pm_verb(state.event), + ((state.event & PM_EVENT_SLEEP) && device_may_wakeup(dev)) ? + ", may wakeup" : ""); +} + +static void pm_dev_err(struct device *dev, pm_message_t state, char *info, + int error) +{ + printk(KERN_ERR "PM: Device %s failed to %s%s: error %d\n", + kobject_name(&dev->kobj), pm_verb(state.event), info, error); +} + +/*------------------------- Resume routines -------------------------*/ + +/** + * resume_device_noirq - Power on one device (early resume). + * @dev: Device. + * @state: PM transition of the system being carried out. + * + * Must be called with interrupts disabled. + */ +static int resume_device_noirq(struct device *dev, pm_message_t state) +{ + int error = 0; + + TRACE_DEVICE(dev); + TRACE_RESUME(0); + + if (!dev->bus) + goto End; + + if (dev->bus->pm) { + pm_dev_dbg(dev, state, "EARLY "); + error = pm_noirq_op(dev, dev->bus->pm, state); + } else if (dev->bus->resume_early) { + pm_dev_dbg(dev, state, "legacy EARLY "); + error = dev->bus->resume_early(dev); + } + End: + TRACE_RESUME(error); + return error; +} + +/** + * dpm_power_up - Power on all regular (non-sysdev) devices. + * @state: PM transition of the system being carried out. + * + * Execute the appropriate "noirq resume" callback for all devices marked + * as DPM_OFF_IRQ. + * + * Must be called with interrupts disabled and only one CPU running. + */ +static void dpm_power_up(pm_message_t state) +{ + struct device *dev; + + list_for_each_entry(dev, &dpm_list, power.entry) + if (dev->power.status > DPM_OFF) { + int error; + + dev->power.status = DPM_OFF; + error = resume_device_noirq(dev, state); + if (error) + pm_dev_err(dev, state, " early", error); + } +} + +/** + * device_power_up - Turn on all devices that need special attention. + * @state: PM transition of the system being carried out. + * + * Power on system devices, then devices that required we shut them down + * with interrupts disabled. + * + * Must be called with interrupts disabled. + */ +void device_power_up(pm_message_t state) +{ + sysdev_resume(); + dpm_power_up(state); +} +EXPORT_SYMBOL_GPL(device_power_up); + +/** + * resume_device - Restore state for one device. + * @dev: Device. + * @state: PM transition of the system being carried out. + */ +static int resume_device(struct device *dev, pm_message_t state) +{ + int error = 0; + + TRACE_DEVICE(dev); + TRACE_RESUME(0); + + down(&dev->sem); + + if (dev->bus) { + if (dev->bus->pm) { + pm_dev_dbg(dev, state, ""); + error = pm_op(dev, &dev->bus->pm->base, state); + } else if (dev->bus->resume) { + pm_dev_dbg(dev, state, "legacy "); + error = dev->bus->resume(dev); + } + if (error) + goto End; + } + + if (dev->type) { + if (dev->type->pm) { + pm_dev_dbg(dev, state, "type "); + error = pm_op(dev, dev->type->pm, state); + } else if (dev->type->resume) { + pm_dev_dbg(dev, state, "legacy type "); + error = dev->type->resume(dev); + } + if (error) + goto End; + } + + if (dev->class) { + if (dev->class->pm) { + pm_dev_dbg(dev, state, "class "); + error = pm_op(dev, dev->class->pm, state); + } else if (dev->class->resume) { + pm_dev_dbg(dev, state, "legacy class "); + error = dev->class->resume(dev); + } + } + End: + up(&dev->sem); + + TRACE_RESUME(error); + return error; +} + +/** + * dpm_resume - Resume every device. + * @state: PM transition of the system being carried out. + * + * Execute the appropriate "resume" callback for all devices the status of + * which indicates that they are inactive. + */ +static void dpm_resume(pm_message_t state) +{ + struct list_head list; + + INIT_LIST_HEAD(&list); + mutex_lock(&dpm_list_mtx); + transition_started = false; + while (!list_empty(&dpm_list)) { + struct device *dev = to_device(dpm_list.next); + + get_device(dev); + if (dev->power.status >= DPM_OFF) { + int error; + + dev->power.status = DPM_RESUMING; + mutex_unlock(&dpm_list_mtx); + + error = resume_device(dev, state); + + mutex_lock(&dpm_list_mtx); + if (error) + pm_dev_err(dev, state, "", error); + } else if (dev->power.status == DPM_SUSPENDING) { + /* Allow new children of the device to be registered */ + dev->power.status = DPM_RESUMING; + } + if (!list_empty(&dev->power.entry)) + list_move_tail(&dev->power.entry, &list); + put_device(dev); + } + list_splice(&list, &dpm_list); + mutex_unlock(&dpm_list_mtx); +} + +/** + * complete_device - Complete a PM transition for given device + * @dev: Device. + * @state: PM transition of the system being carried out. + */ +static void complete_device(struct device *dev, pm_message_t state) +{ + down(&dev->sem); + + if (dev->class && dev->class->pm && dev->class->pm->complete) { + pm_dev_dbg(dev, state, "completing class "); + dev->class->pm->complete(dev); + } + + if (dev->type && dev->type->pm && dev->type->pm->complete) { + pm_dev_dbg(dev, state, "completing type "); + dev->type->pm->complete(dev); + } + + if (dev->bus && dev->bus->pm && dev->bus->pm->base.complete) { + pm_dev_dbg(dev, state, "completing "); + dev->bus->pm->base.complete(dev); + } + + up(&dev->sem); +} + +/** + * dpm_complete - Complete a PM transition for all devices. + * @state: PM transition of the system being carried out. + * + * Execute the ->complete() callbacks for all devices that are not marked + * as DPM_ON. + */ +static void dpm_complete(pm_message_t state) +{ + struct list_head list; + + INIT_LIST_HEAD(&list); + mutex_lock(&dpm_list_mtx); + while (!list_empty(&dpm_list)) { + struct device *dev = to_device(dpm_list.prev); + + get_device(dev); + if (dev->power.status > DPM_ON) { + dev->power.status = DPM_ON; + mutex_unlock(&dpm_list_mtx); + + complete_device(dev, state); + + mutex_lock(&dpm_list_mtx); + } + if (!list_empty(&dev->power.entry)) + list_move(&dev->power.entry, &list); + put_device(dev); + } + list_splice(&list, &dpm_list); + mutex_unlock(&dpm_list_mtx); +} + +/** + * device_resume - Restore state of each device in system. + * @state: PM transition of the system being carried out. + * + * Resume all the devices, unlock them all, and allow new + * devices to be registered once again. + */ +void device_resume(pm_message_t state) +{ + might_sleep(); + dpm_resume(state); + dpm_complete(state); +} +EXPORT_SYMBOL_GPL(device_resume); + + +/*------------------------- Suspend routines -------------------------*/ + +/** + * resume_event - return a PM message representing the resume event + * corresponding to given sleep state. + * @sleep_state: PM message representing a sleep state. + */ +static pm_message_t resume_event(pm_message_t sleep_state) +{ + switch (sleep_state.event) { + case PM_EVENT_SUSPEND: + return PMSG_RESUME; + case PM_EVENT_FREEZE: + case PM_EVENT_QUIESCE: + return PMSG_RECOVER; + case PM_EVENT_HIBERNATE: + return PMSG_RESTORE; + } + return PMSG_ON; +} + +/** + * suspend_device_noirq - Shut down one device (late suspend). + * @dev: Device. + * @state: PM transition of the system being carried out. + * + * This is called with interrupts off and only a single CPU running. + */ +static int suspend_device_noirq(struct device *dev, pm_message_t state) +{ + int error = 0; + + if (!dev->bus) + return 0; + + if (dev->bus->pm) { + pm_dev_dbg(dev, state, "LATE "); + error = pm_noirq_op(dev, dev->bus->pm, state); + } else if (dev->bus->suspend_late) { + pm_dev_dbg(dev, state, "legacy LATE "); + error = dev->bus->suspend_late(dev, state); + suspend_report_result(dev->bus->suspend_late, error); + } + return error; +} + +/** + * device_power_down - Shut down special devices. + * @state: PM transition of the system being carried out. + * + * Power down devices that require interrupts to be disabled. + * Then power down system devices. + * + * Must be called with interrupts disabled and only one CPU running. + */ +int device_power_down(pm_message_t state) +{ + struct device *dev; + int error = 0; + + list_for_each_entry_reverse(dev, &dpm_list, power.entry) { + error = suspend_device_noirq(dev, state); + if (error) { + pm_dev_err(dev, state, " late", error); + break; + } + dev->power.status = DPM_OFF_IRQ; + } + if (!error) + error = sysdev_suspend(state); + if (error) + dpm_power_up(resume_event(state)); + return error; +} +EXPORT_SYMBOL_GPL(device_power_down); + +/** + * suspend_device - Save state of one device. + * @dev: Device. + * @state: PM transition of the system being carried out. + */ +static int suspend_device(struct device *dev, pm_message_t state) +{ + int error = 0; + + down(&dev->sem); + + if (dev->class) { + if (dev->class->pm) { + pm_dev_dbg(dev, state, "class "); + error = pm_op(dev, dev->class->pm, state); + } else if (dev->class->suspend) { + pm_dev_dbg(dev, state, "legacy class "); + error = dev->class->suspend(dev, state); + suspend_report_result(dev->class->suspend, error); + } + if (error) + goto End; + } + + if (dev->type) { + if (dev->type->pm) { + pm_dev_dbg(dev, state, "type "); + error = pm_op(dev, dev->type->pm, state); + } else if (dev->type->suspend) { + pm_dev_dbg(dev, state, "legacy type "); + error = dev->type->suspend(dev, state); + suspend_report_result(dev->type->suspend, error); + } + if (error) + goto End; + } + + if (dev->bus) { + if (dev->bus->pm) { + pm_dev_dbg(dev, state, ""); + error = pm_op(dev, &dev->bus->pm->base, state); + } else if (dev->bus->suspend) { + pm_dev_dbg(dev, state, "legacy "); + error = dev->bus->suspend(dev, state); + suspend_report_result(dev->bus->suspend, error); + } + } + End: + up(&dev->sem); + + return error; +} + +/** + * dpm_suspend - Suspend every device. + * @state: PM transition of the system being carried out. + * + * Execute the appropriate "suspend" callbacks for all devices. + */ +static int dpm_suspend(pm_message_t state) +{ + struct list_head list; + int error = 0; + + INIT_LIST_HEAD(&list); + mutex_lock(&dpm_list_mtx); + while (!list_empty(&dpm_list)) { + struct device *dev = to_device(dpm_list.prev); + + get_device(dev); + mutex_unlock(&dpm_list_mtx); + + error = suspend_device(dev, state); + + mutex_lock(&dpm_list_mtx); + if (error) { + pm_dev_err(dev, state, "", error); + put_device(dev); + break; + } + dev->power.status = DPM_OFF; + if (!list_empty(&dev->power.entry)) + list_move(&dev->power.entry, &list); + put_device(dev); + } + list_splice(&list, dpm_list.prev); + mutex_unlock(&dpm_list_mtx); + return error; +} + +/** + * prepare_device - Execute the ->prepare() callback(s) for given device. + * @dev: Device. + * @state: PM transition of the system being carried out. + */ +static int prepare_device(struct device *dev, pm_message_t state) +{ + int error = 0; + + down(&dev->sem); + + if (dev->bus && dev->bus->pm && dev->bus->pm->base.prepare) { + pm_dev_dbg(dev, state, "preparing "); + error = dev->bus->pm->base.prepare(dev); + suspend_report_result(dev->bus->pm->base.prepare, error); + if (error) + goto End; + } + + if (dev->type && dev->type->pm && dev->type->pm->prepare) { + pm_dev_dbg(dev, state, "preparing type "); + error = dev->type->pm->prepare(dev); + suspend_report_result(dev->type->pm->prepare, error); + if (error) + goto End; + } + + if (dev->class && dev->class->pm && dev->class->pm->prepare) { + pm_dev_dbg(dev, state, "preparing class "); + error = dev->class->pm->prepare(dev); + suspend_report_result(dev->class->pm->prepare, error); + } + End: + up(&dev->sem); + + return error; +} + +/** + * dpm_prepare - Prepare all devices for a PM transition. + * @state: PM transition of the system being carried out. + * + * Execute the ->prepare() callback for all devices. + */ +static int dpm_prepare(pm_message_t state) +{ + struct list_head list; + int error = 0; + + INIT_LIST_HEAD(&list); + mutex_lock(&dpm_list_mtx); + transition_started = true; + while (!list_empty(&dpm_list)) { + struct device *dev = to_device(dpm_list.next); + + get_device(dev); + dev->power.status = DPM_PREPARING; + mutex_unlock(&dpm_list_mtx); + + error = prepare_device(dev, state); + + mutex_lock(&dpm_list_mtx); + if (error) { + dev->power.status = DPM_ON; + if (error == -EAGAIN) { + put_device(dev); + continue; + } + printk(KERN_ERR "PM: Failed to prepare device %s " + "for power transition: error %d\n", + kobject_name(&dev->kobj), error); + put_device(dev); + break; + } + dev->power.status = DPM_SUSPENDING; + if (!list_empty(&dev->power.entry)) + list_move_tail(&dev->power.entry, &list); + put_device(dev); + } + list_splice(&list, &dpm_list); + mutex_unlock(&dpm_list_mtx); + return error; +} + +/** + * device_suspend - Save state and stop all devices in system. + * @state: PM transition of the system being carried out. + * + * Prepare and suspend all devices. + */ +int device_suspend(pm_message_t state) +{ + int error; + + might_sleep(); + error = dpm_prepare(state); + if (!error) + error = dpm_suspend(state); + return error; +} +EXPORT_SYMBOL_GPL(device_suspend); + +void __suspend_report_result(const char *function, void *fn, int ret) +{ + if (ret) + printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); +} +EXPORT_SYMBOL_GPL(__suspend_report_result); diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h new file mode 100644 index 0000000..41f51fa --- /dev/null +++ b/drivers/base/power/power.h @@ -0,0 +1,49 @@ +static inline void device_pm_init(struct device *dev) +{ + dev->power.status = DPM_ON; +} + +#ifdef CONFIG_PM_SLEEP + +/* + * main.c + */ + +extern struct list_head dpm_list; /* The active device list */ + +static inline struct device *to_device(struct list_head *entry) +{ + return container_of(entry, struct device, power.entry); +} + +extern void device_pm_add(struct device *); +extern void device_pm_remove(struct device *); + +#else /* CONFIG_PM_SLEEP */ + +static inline void device_pm_add(struct device *dev) {} +static inline void device_pm_remove(struct device *dev) {} + +#endif + +#ifdef CONFIG_PM + +/* + * sysfs.c + */ + +extern int dpm_sysfs_add(struct device *); +extern void dpm_sysfs_remove(struct device *); + +#else /* CONFIG_PM */ + +static inline int dpm_sysfs_add(struct device *dev) +{ + return 0; +} + +static inline void dpm_sysfs_remove(struct device *dev) +{ +} + +#endif diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c new file mode 100644 index 0000000..596aeec --- /dev/null +++ b/drivers/base/power/sysfs.c @@ -0,0 +1,98 @@ +/* + * drivers/base/power/sysfs.c - sysfs entries for device PM + */ + +#include <linux/device.h> +#include <linux/string.h> +#include "power.h" + +/* + * wakeup - Report/change current wakeup option for device + * + * Some devices support "wakeup" events, which are hardware signals + * used to activate devices from suspended or low power states. Such + * devices have one of three values for the sysfs power/wakeup file: + * + * + "enabled\n" to issue the events; + * + "disabled\n" not to do so; or + * + "\n" for temporary or permanent inability to issue wakeup. + * + * (For example, unconfigured USB devices can't issue wakeups.) + * + * Familiar examples of devices that can issue wakeup events include + * keyboards and mice (both PS2 and USB styles), power buttons, modems, + * "Wake-On-LAN" Ethernet links, GPIO lines, and more. Some events + * will wake the entire system from a suspend state; others may just + * wake up the device (if the system as a whole is already active). + * Some wakeup events use normal IRQ lines; other use special out + * of band signaling. + * + * It is the responsibility of device drivers to enable (or disable) + * wakeup signaling as part of changing device power states, respecting + * the policy choices provided through the driver model. + * + * Devices may not be able to generate wakeup events from all power + * states. Also, the events may be ignored in some configurations; + * for example, they might need help from other devices that aren't + * active, or which may have wakeup disabled. Some drivers rely on + * wakeup events internally (unless they are disabled), keeping + * their hardware in low power modes whenever they're unused. This + * saves runtime power, without requiring system-wide sleep states. + */ + +static const char enabled[] = "enabled"; +static const char disabled[] = "disabled"; + +static ssize_t +wake_show(struct device * dev, struct device_attribute *attr, char * buf) +{ + return sprintf(buf, "%s\n", device_can_wakeup(dev) + ? (device_may_wakeup(dev) ? enabled : disabled) + : ""); +} + +static ssize_t +wake_store(struct device * dev, struct device_attribute *attr, + const char * buf, size_t n) +{ + char *cp; + int len = n; + + if (!device_can_wakeup(dev)) + return -EINVAL; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof enabled - 1 + && strncmp(buf, enabled, sizeof enabled - 1) == 0) + device_set_wakeup_enable(dev, 1); + else if (len == sizeof disabled - 1 + && strncmp(buf, disabled, sizeof disabled - 1) == 0) + device_set_wakeup_enable(dev, 0); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); + + +static struct attribute * power_attrs[] = { + &dev_attr_wakeup.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/power/trace.c b/drivers/base/power/trace.c new file mode 100644 index 0000000..2aa6e8f --- /dev/null +++ b/drivers/base/power/trace.c @@ -0,0 +1,231 @@ +/* + * drivers/base/power/trace.c + * + * Copyright (C) 2006 Linus Torvalds + * + * Trace facility for suspend/resume problems, when none of the + * devices may be working. + */ + +#include <linux/resume-trace.h> +#include <linux/rtc.h> + +#include <asm/rtc.h> + +#include "power.h" + +/* + * Horrid, horrid, horrid. + * + * It turns out that the _only_ piece of hardware that actually + * keeps its value across a hard boot (and, more importantly, the + * POST init sequence) is literally the realtime clock. + * + * Never mind that an RTC chip has 114 bytes (and often a whole + * other bank of an additional 128 bytes) of nice SRAM that is + * _designed_ to keep data - the POST will clear it. So we literally + * can just use the few bytes of actual time data, which means that + * we're really limited. + * + * It means, for example, that we can't use the seconds at all + * (since the time between the hang and the boot might be more + * than a minute), and we'd better not depend on the low bits of + * the minutes either. + * + * There are the wday fields etc, but I wouldn't guarantee those + * are dependable either. And if the date isn't valid, either the + * hw or POST will do strange things. + * + * So we're left with: + * - year: 0-99 + * - month: 0-11 + * - day-of-month: 1-28 + * - hour: 0-23 + * - min: (0-30)*2 + * + * Giving us a total range of 0-16128000 (0xf61800), ie less + * than 24 bits of actual data we can save across reboots. + * + * And if your box can't boot in less than three minutes, + * you're screwed. + * + * Now, almost 24 bits of data is pitifully small, so we need + * to be pretty dense if we want to use it for anything nice. + * What we do is that instead of saving off nice readable info, + * we save off _hashes_ of information that we can hopefully + * regenerate after the reboot. + * + * In particular, this means that we might be unlucky, and hit + * a case where we have a hash collision, and we end up not + * being able to tell for certain exactly which case happened. + * But that's hopefully unlikely. + * + * What we do is to take the bits we can fit, and split them + * into three parts (16*997*1009 = 16095568), and use the values + * for: + * - 0-15: user-settable + * - 0-996: file + line number + * - 0-1008: device + */ +#define USERHASH (16) +#define FILEHASH (997) +#define DEVHASH (1009) + +#define DEVSEED (7919) + +static unsigned int dev_hash_value; + +static int set_magic_time(unsigned int user, unsigned int file, unsigned int device) +{ + unsigned int n = user + USERHASH*(file + FILEHASH*device); + + // June 7th, 2006 + static struct rtc_time time = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 7, + .tm_mon = 5, // June - counting from zero + .tm_year = 106, + .tm_wday = 3, + .tm_yday = 160, + .tm_isdst = 1 + }; + + time.tm_year = (n % 100); + n /= 100; + time.tm_mon = (n % 12); + n /= 12; + time.tm_mday = (n % 28) + 1; + n /= 28; + time.tm_hour = (n % 24); + n /= 24; + time.tm_min = (n % 20) * 3; + n /= 20; + set_rtc_time(&time); + return n ? -1 : 0; +} + +static unsigned int read_magic_time(void) +{ + struct rtc_time time; + unsigned int val; + + get_rtc_time(&time); + printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n", + time.tm_hour, time.tm_min, time.tm_sec, + time.tm_mon + 1, time.tm_mday, time.tm_year % 100); + val = time.tm_year; /* 100 years */ + if (val > 100) + val -= 100; + val += time.tm_mon * 100; /* 12 months */ + val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */ + val += time.tm_hour * 100 * 12 * 28; /* 24 hours */ + val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */ + return val; +} + +/* + * This is just the sdbm hash function with a user-supplied + * seed and final size parameter. + */ +static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod) +{ + unsigned char c; + while ((c = *data++) != 0) { + seed = (seed << 16) + (seed << 6) - seed + c; + } + return seed % mod; +} + +void set_trace_device(struct device *dev) +{ + dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH); +} +EXPORT_SYMBOL(set_trace_device); + +/* + * We could just take the "tracedata" index into the .tracedata + * section instead. Generating a hash of the data gives us a + * chance to work across kernel versions, and perhaps more + * importantly it also gives us valid/invalid check (ie we will + * likely not give totally bogus reports - if the hash matches, + * it's not any guarantee, but it's a high _likelihood_ that + * the match is valid). + */ +void generate_resume_trace(const void *tracedata, unsigned int user) +{ + unsigned short lineno = *(unsigned short *)tracedata; + const char *file = *(const char **)(tracedata + 2); + unsigned int user_hash_value, file_hash_value; + + user_hash_value = user % USERHASH; + file_hash_value = hash_string(lineno, file, FILEHASH); + set_magic_time(user_hash_value, file_hash_value, dev_hash_value); +} +EXPORT_SYMBOL(generate_resume_trace); + +extern char __tracedata_start, __tracedata_end; +static int show_file_hash(unsigned int value) +{ + int match; + char *tracedata; + + match = 0; + for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; + tracedata += 2 + sizeof(unsigned long)) { + unsigned short lineno = *(unsigned short *)tracedata; + const char *file = *(const char **)(tracedata + 2); + unsigned int hash = hash_string(lineno, file, FILEHASH); + if (hash != value) + continue; + printk(" hash matches %s:%u\n", file, lineno); + match++; + } + return match; +} + +static int show_dev_hash(unsigned int value) +{ + int match = 0; + struct list_head *entry = dpm_list.prev; + + while (entry != &dpm_list) { + struct device * dev = to_device(entry); + unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH); + if (hash == value) { + dev_info(dev, "hash matches\n"); + match++; + } + entry = entry->prev; + } + return match; +} + +static unsigned int hash_value_early_read; + +static int early_resume_init(void) +{ + hash_value_early_read = read_magic_time(); + return 0; +} + +static int late_resume_init(void) +{ + unsigned int val = hash_value_early_read; + unsigned int user, file, dev; + + user = val % USERHASH; + val = val / USERHASH; + file = val % FILEHASH; + val = val / FILEHASH; + dev = val /* % DEVHASH */; + + printk(" Magic number: %d:%d:%d\n", user, file, dev); + show_file_hash(file); + show_dev_hash(dev); + return 0; +} + +core_initcall(early_resume_init); +late_initcall(late_resume_init); diff --git a/drivers/base/sys.c b/drivers/base/sys.c new file mode 100644 index 0000000..c98c31e --- /dev/null +++ b/drivers/base/sys.c @@ -0,0 +1,528 @@ +/* + * 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/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> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/mutex.h> + +#include "base.h" + +#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, sysdev_attr, buffer); + return -EIO; +} + + +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, sysdev_attr, buffer, count); + return -EIO; +} + +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); + +#define to_sysdev_class(k) container_of(k, struct sysdev_class, kset.kobj) +#define to_sysdev_class_attr(a) container_of(a, \ + struct sysdev_class_attribute, attr) + +static ssize_t sysdev_class_show(struct kobject *kobj, struct attribute *attr, + char *buffer) +{ + struct sysdev_class * class = to_sysdev_class(kobj); + struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr); + + if (class_attr->show) + return class_attr->show(class, buffer); + return -EIO; +} + +static ssize_t sysdev_class_store(struct kobject *kobj, struct attribute *attr, + const char *buffer, size_t count) +{ + struct sysdev_class * class = to_sysdev_class(kobj); + struct sysdev_class_attribute * class_attr = to_sysdev_class_attr(attr); + + if (class_attr->store) + return class_attr->store(class, buffer, count); + return -EIO; +} + +static struct sysfs_ops sysfs_class_ops = { + .show = sysdev_class_show, + .store = sysdev_class_store, +}; + +static struct kobj_type ktype_sysdev_class = { + .sysfs_ops = &sysfs_class_ops, +}; + +int sysdev_class_create_file(struct sysdev_class *c, + struct sysdev_class_attribute *a) +{ + return sysfs_create_file(&c->kset.kobj, &a->attr); +} +EXPORT_SYMBOL_GPL(sysdev_class_create_file); + +void sysdev_class_remove_file(struct sysdev_class *c, + struct sysdev_class_attribute *a) +{ + sysfs_remove_file(&c->kset.kobj, &a->attr); +} +EXPORT_SYMBOL_GPL(sysdev_class_remove_file); + +static struct kset *system_kset; + +int sysdev_class_register(struct sysdev_class * cls) +{ + pr_debug("Registering sysdev class '%s'\n", cls->name); + + INIT_LIST_HEAD(&cls->drivers); + memset(&cls->kset.kobj, 0x00, sizeof(struct kobject)); + cls->kset.kobj.parent = &system_kset->kobj; + cls->kset.kobj.ktype = &ktype_sysdev_class; + cls->kset.kobj.kset = system_kset; + kobject_set_name(&cls->kset.kobj, cls->name); + 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 DEFINE_MUTEX(sysdev_drivers_lock); + +/** + * sysdev_driver_register - Register auxillary driver + * @cls: Device class driver belongs to. + * @drv: Driver. + * + * @drv is inserted into @cls->drivers to be + * called on each operation on devices of that class. The refcount + * of @cls is incremented. + */ + +int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv) +{ + int err = 0; + + if (!cls) { + WARN(1, KERN_WARNING "sysdev: invalid class passed to " + "sysdev_driver_register!\n"); + return -EINVAL; + } + + /* Check whether this driver has already been added to a class. */ + if (drv->entry.next && !list_empty(&drv->entry)) + WARN(1, KERN_WARNING "sysdev: class %s: driver (%p) has already" + " been registered to a class, something is wrong, but " + "will forge on!\n", cls->name, drv); + + mutex_lock(&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 { + err = -EINVAL; + WARN(1, KERN_ERR "%s: invalid device class\n", __func__); + } + mutex_unlock(&sysdev_drivers_lock); + return err; +} + + +/** + * 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) +{ + mutex_lock(&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); + } + mutex_unlock(&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; + + pr_debug("Registering sys device of class '%s'\n", + kobject_name(&cls->kset.kobj)); + + /* initialize the kobject to 0, in case it had previously been used */ + memset(&sysdev->kobj, 0x00, sizeof(struct kobject)); + + /* Make sure the kset is set */ + sysdev->kobj.kset = &cls->kset; + + /* Register the object */ + error = kobject_init_and_add(&sysdev->kobj, &ktype_sysdev, NULL, + "%s%d", kobject_name(&cls->kset.kobj), + sysdev->id); + + if (!error) { + struct sysdev_driver * drv; + + pr_debug("Registering sys device '%s'\n", + kobject_name(&sysdev->kobj)); + + mutex_lock(&sysdev_drivers_lock); + /* Generic notification is implicit, because it's that + * code that should have called us. + */ + + /* Notify class auxillary drivers */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->add) + drv->add(sysdev); + } + mutex_unlock(&sysdev_drivers_lock); + } + + kobject_uevent(&sysdev->kobj, KOBJ_ADD); + return error; +} + +void sysdev_unregister(struct sys_device * sysdev) +{ + struct sysdev_driver * drv; + + mutex_lock(&sysdev_drivers_lock); + list_for_each_entry(drv, &sysdev->cls->drivers, entry) { + if (drv->remove) + drv->remove(sysdev); + } + mutex_unlock(&sysdev_drivers_lock); + + kobject_put(&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 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"); + + mutex_lock(&sysdev_drivers_lock); + list_for_each_entry_reverse(cls, &system_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 auxillary drivers first */ + 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); + } + } + mutex_unlock(&sysdev_drivers_lock); +} + +static void __sysdev_resume(struct sys_device *dev) +{ + struct sysdev_class *cls = dev->cls; + struct sysdev_driver *drv; + + /* First, call the class-specific one */ + if (cls->resume) + cls->resume(dev); + + /* Call auxillary drivers next. */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->resume) + drv->resume(dev); + } +} + +/** + * sysdev_suspend - Suspend all system devices. + * @state: Power state to enter. + * + * We perform an almost identical operation as sysdev_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(pm_message_t state) +{ + struct sysdev_class * cls; + struct sys_device *sysdev, *err_dev; + struct sysdev_driver *drv, *err_drv; + int ret; + + pr_debug("Suspending System Devices\n"); + + list_for_each_entry_reverse(cls, &system_kset->list, kset.kobj.entry) { + pr_debug("Suspending type '%s':\n", + kobject_name(&cls->kset.kobj)); + + list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) { + pr_debug(" %s\n", kobject_name(&sysdev->kobj)); + + /* Call auxillary drivers first */ + list_for_each_entry(drv, &cls->drivers, entry) { + if (drv->suspend) { + ret = drv->suspend(sysdev, state); + if (ret) + goto aux_driver; + } + } + + /* Now call the generic one */ + if (cls->suspend) { + ret = cls->suspend(sysdev, state); + if (ret) + goto cls_driver; + } + } + } + return 0; + /* resume current sysdev */ +cls_driver: + drv = NULL; + printk(KERN_ERR "Class suspend failed for %s\n", + kobject_name(&sysdev->kobj)); + +aux_driver: + if (drv) + printk(KERN_ERR "Class driver suspend failed for %s\n", + kobject_name(&sysdev->kobj)); + list_for_each_entry(err_drv, &cls->drivers, entry) { + if (err_drv == drv) + break; + if (err_drv->resume) + err_drv->resume(sysdev); + } + + /* resume other sysdevs in current class */ + list_for_each_entry(err_dev, &cls->kset.list, kobj.entry) { + if (err_dev == sysdev) + break; + pr_debug(" %s\n", kobject_name(&err_dev->kobj)); + __sysdev_resume(err_dev); + } + + /* resume other classes */ + list_for_each_entry_continue(cls, &system_kset->list, kset.kobj.entry) { + list_for_each_entry(err_dev, &cls->kset.list, kobj.entry) { + pr_debug(" %s\n", kobject_name(&err_dev->kobj)); + __sysdev_resume(err_dev); + } + } + return ret; +} + + +/** + * sysdev_resume - Bring system devices back to life. + * + * Similar to sysdev_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_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) { + pr_debug(" %s\n", kobject_name(&sysdev->kobj)); + + __sysdev_resume(sysdev); + } + } + return 0; +} + + +int __init system_bus_init(void) +{ + system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj); + if (!system_kset) + return -ENOMEM; + return 0; +} + +EXPORT_SYMBOL_GPL(sysdev_register); +EXPORT_SYMBOL_GPL(sysdev_unregister); + +#define to_ext_attr(x) container_of(x, struct sysdev_ext_attribute, attr) + +ssize_t sysdev_store_ulong(struct sys_device *sysdev, + struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + char *end; + unsigned long new = simple_strtoul(buf, &end, 0); + if (end == buf) + return -EINVAL; + *(unsigned long *)(ea->var) = new; + /* Always return full write size even if we didn't consume all */ + return size; +} +EXPORT_SYMBOL_GPL(sysdev_store_ulong); + +ssize_t sysdev_show_ulong(struct sys_device *sysdev, + struct sysdev_attribute *attr, + char *buf) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + return snprintf(buf, PAGE_SIZE, "%lx\n", *(unsigned long *)(ea->var)); +} +EXPORT_SYMBOL_GPL(sysdev_show_ulong); + +ssize_t sysdev_store_int(struct sys_device *sysdev, + struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + char *end; + long new = simple_strtol(buf, &end, 0); + if (end == buf || new > INT_MAX || new < INT_MIN) + return -EINVAL; + *(int *)(ea->var) = new; + /* Always return full write size even if we didn't consume all */ + return size; +} +EXPORT_SYMBOL_GPL(sysdev_store_int); + +ssize_t sysdev_show_int(struct sys_device *sysdev, + struct sysdev_attribute *attr, + char *buf) +{ + struct sysdev_ext_attribute *ea = to_ext_attr(attr); + return snprintf(buf, PAGE_SIZE, "%d\n", *(int *)(ea->var)); +} +EXPORT_SYMBOL_GPL(sysdev_show_int); + diff --git a/drivers/base/topology.c b/drivers/base/topology.c new file mode 100644 index 0000000..199cd97 --- /dev/null +++ b/drivers/base/topology.c @@ -0,0 +1,183 @@ +/* + * driver/base/topology.c - Populate sysfs with cpu topology information + * + * Written by: Zhang Yanmin, Intel Corporation + * + * Copyright (C) 2006, Intel Corp. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include <linux/sysdev.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/cpu.h> +#include <linux/module.h> +#include <linux/topology.h> + +#define define_one_ro(_name) \ +static SYSDEV_ATTR(_name, 0444, show_##_name, NULL) + +#define define_id_show_func(name) \ +static ssize_t show_##name(struct sys_device *dev, \ + struct sysdev_attribute *attr, char *buf) \ +{ \ + unsigned int cpu = dev->id; \ + return sprintf(buf, "%d\n", topology_##name(cpu)); \ +} + +#if defined(topology_thread_siblings) || defined(topology_core_siblings) +static ssize_t show_cpumap(int type, cpumask_t *mask, char *buf) +{ + ptrdiff_t len = PTR_ALIGN(buf + PAGE_SIZE - 1, PAGE_SIZE) - buf; + int n = 0; + + if (len > 1) { + n = type? + cpulist_scnprintf(buf, len-2, *mask): + cpumask_scnprintf(buf, len-2, *mask); + buf[n++] = '\n'; + buf[n] = '\0'; + } + return n; +} +#endif + +#ifdef arch_provides_topology_pointers +#define define_siblings_show_map(name) \ +static ssize_t show_##name(struct sys_device *dev, \ + struct sysdev_attribute *attr, char *buf) \ +{ \ + unsigned int cpu = dev->id; \ + return show_cpumap(0, &(topology_##name(cpu)), buf); \ +} + +#define define_siblings_show_list(name) \ +static ssize_t show_##name##_list(struct sys_device *dev, \ + struct sysdev_attribute *attr, \ + char *buf) \ +{ \ + unsigned int cpu = dev->id; \ + return show_cpumap(1, &(topology_##name(cpu)), buf); \ +} + +#else +#define define_siblings_show_map(name) \ +static ssize_t show_##name(struct sys_device *dev, \ + struct sysdev_attribute *attr, char *buf) \ +{ \ + unsigned int cpu = dev->id; \ + cpumask_t mask = topology_##name(cpu); \ + return show_cpumap(0, &mask, buf); \ +} + +#define define_siblings_show_list(name) \ +static ssize_t show_##name##_list(struct sys_device *dev, \ + struct sysdev_attribute *attr, \ + char *buf) \ +{ \ + unsigned int cpu = dev->id; \ + cpumask_t mask = topology_##name(cpu); \ + return show_cpumap(1, &mask, buf); \ +} +#endif + +#define define_siblings_show_func(name) \ + define_siblings_show_map(name); define_siblings_show_list(name) + +define_id_show_func(physical_package_id); +define_one_ro(physical_package_id); + +define_id_show_func(core_id); +define_one_ro(core_id); + +define_siblings_show_func(thread_siblings); +define_one_ro(thread_siblings); +define_one_ro(thread_siblings_list); + +define_siblings_show_func(core_siblings); +define_one_ro(core_siblings); +define_one_ro(core_siblings_list); + +static struct attribute *default_attrs[] = { + &attr_physical_package_id.attr, + &attr_core_id.attr, + &attr_thread_siblings.attr, + &attr_thread_siblings_list.attr, + &attr_core_siblings.attr, + &attr_core_siblings_list.attr, + NULL +}; + +static struct attribute_group topology_attr_group = { + .attrs = default_attrs, + .name = "topology" +}; + +/* Add/Remove cpu_topology interface for CPU device */ +static int __cpuinit topology_add_dev(unsigned int cpu) +{ + struct sys_device *sys_dev = get_cpu_sysdev(cpu); + + return sysfs_create_group(&sys_dev->kobj, &topology_attr_group); +} + +static void __cpuinit topology_remove_dev(unsigned int cpu) +{ + struct sys_device *sys_dev = get_cpu_sysdev(cpu); + + sysfs_remove_group(&sys_dev->kobj, &topology_attr_group); +} + +static int __cpuinit topology_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + int rc = 0; + + switch (action) { + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + rc = topology_add_dev(cpu); + break; + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + case CPU_DEAD: + case CPU_DEAD_FROZEN: + topology_remove_dev(cpu); + break; + } + return rc ? NOTIFY_BAD : NOTIFY_OK; +} + +static int __cpuinit topology_sysfs_init(void) +{ + int cpu; + int rc; + + for_each_online_cpu(cpu) { + rc = topology_add_dev(cpu); + if (rc) + return rc; + } + hotcpu_notifier(topology_cpu_callback, 0); + + return 0; +} + +device_initcall(topology_sysfs_init); diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c new file mode 100644 index 0000000..84997ef --- /dev/null +++ b/drivers/base/transport_class.c @@ -0,0 +1,279 @@ +/* + * 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 transport_container *tc, + struct device *dev, + struct device *cdev) +{ + /* 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) +{ + if (unlikely(attribute_container_unregister(&atc->container))) + BUG(); +} +EXPORT_SYMBOL_GPL(anon_transport_class_unregister); + +static int transport_setup_classdev(struct attribute_container *cont, + struct device *dev, + struct device *classdev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + struct transport_container *tcont = attribute_container_to_transport_container(cont); + + if (tclass->setup) + tclass->setup(tcont, dev, classdev); + + 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 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 device *cdev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + struct transport_container *tcont = attribute_container_to_transport_container(cont); + + if (tclass->configure) + tclass->configure(tcont, dev, cdev); + + 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_device_trigger(dev, transport_configure); +} +EXPORT_SYMBOL_GPL(transport_configure_device); + +static int transport_remove_classdev(struct attribute_container *cont, + struct device *dev, + struct 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(tcont, dev, classdev); + + 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 device *classdev) +{ + struct transport_class *tclass = class_to_transport_class(cont->class); + + if (tclass->remove != anon_transport_dummy_function) + put_device(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); |