diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 12 | ||||
-rw-r--r-- | drivers/base/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/base.h | 1 | ||||
-rw-r--r-- | drivers/base/class.c | 23 | ||||
-rw-r--r-- | drivers/base/core.c | 205 | ||||
-rw-r--r-- | drivers/base/dd.c | 24 | ||||
-rw-r--r-- | drivers/base/devres.c | 644 | ||||
-rw-r--r-- | drivers/base/dma-mapping.c | 218 | ||||
-rw-r--r-- | drivers/base/dmapool.c | 61 | ||||
-rw-r--r-- | drivers/base/firmware_class.c | 3 | ||||
-rw-r--r-- | drivers/base/platform.c | 15 |
11 files changed, 1115 insertions, 92 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 1429f3a2629..5d6312e3349 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -37,6 +37,18 @@ config DEBUG_DRIVER 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 diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 7bbb9eeda23..e9eb7382ac3 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -3,6 +3,7 @@ obj-y := core.o sys.o bus.o dd.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o dmapool.o \ + dma-mapping.o devres.o \ attribute_container.o transport_class.o obj-y += power/ obj-$(CONFIG_ISA) += isa.o diff --git a/drivers/base/base.h b/drivers/base/base.h index d26644a5953..de7e1442ce6 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -44,3 +44,4 @@ struct class_device_attribute *to_class_dev_attr(struct attribute *_attr) extern char *make_class_name(const char *name, struct kobject *kobj); +extern void devres_release_all(struct device *dev); diff --git a/drivers/base/class.c b/drivers/base/class.c index f098881f45b..96def1ddba1 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -163,6 +163,8 @@ int class_register(struct class * cls) void class_unregister(struct class * cls) { pr_debug("device class '%s': unregistering\n", cls->name); + if (cls->virtual_dir) + kobject_unregister(cls->virtual_dir); remove_class_attrs(cls); subsystem_unregister(&cls->subsys); } @@ -362,7 +364,7 @@ char *make_class_name(const char *name, struct kobject *kobj) class_name = kmalloc(size, GFP_KERNEL); if (!class_name) - return ERR_PTR(-ENOMEM); + return NULL; strcpy(class_name, name); strcat(class_name, ":"); @@ -409,8 +411,11 @@ static int make_deprecated_class_device_links(struct class_device *class_dev) return 0; class_name = make_class_name(class_dev->class->name, &class_dev->kobj); - error = sysfs_create_link(&class_dev->dev->kobj, &class_dev->kobj, - class_name); + if (class_name) + error = sysfs_create_link(&class_dev->dev->kobj, + &class_dev->kobj, class_name); + else + error = -ENOMEM; kfree(class_name); return error; } @@ -423,7 +428,8 @@ static void remove_deprecated_class_device_links(struct class_device *class_dev) return; class_name = make_class_name(class_dev->class->name, &class_dev->kobj); - sysfs_remove_link(&class_dev->dev->kobj, class_name); + if (class_name) + sysfs_remove_link(&class_dev->dev->kobj, class_name); kfree(class_name); } #else @@ -861,9 +867,12 @@ int class_device_rename(struct class_device *class_dev, char *new_name) if (class_dev->dev) { new_class_name = make_class_name(class_dev->class->name, &class_dev->kobj); - sysfs_create_link(&class_dev->dev->kobj, &class_dev->kobj, - new_class_name); - sysfs_remove_link(&class_dev->dev->kobj, old_class_name); + if (new_class_name) + sysfs_create_link(&class_dev->dev->kobj, + &class_dev->kobj, new_class_name); + if (old_class_name) + sysfs_remove_link(&class_dev->dev->kobj, + old_class_name); } #endif class_device_put(class_dev); diff --git a/drivers/base/core.c b/drivers/base/core.c index 67b79a7592a..a8ac34ba610 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -95,6 +95,8 @@ static void device_release(struct kobject * 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 { @@ -154,25 +156,47 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp, "MINOR=%u", MINOR(dev->devt)); } -#ifdef CONFIG_SYSFS_DEPRECATED - /* add bus name (same as SUBSYSTEM, deprecated) */ - if (dev->bus) - add_uevent_var(envp, num_envp, &i, - buffer, buffer_size, &length, - "PHYSDEVBUS=%s", dev->bus->name); -#endif - - /* add driver name (PHYSDEV* values are deprecated)*/ - if (dev->driver) { + if (dev->driver) add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "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); + add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVPATH=%s", path); + kfree(path); + + add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVBUS=%s", parent->bus->name); + + if (parent->driver) + add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVDRIVER=%s", parent->driver->name); + } + } else if (dev->bus) { add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, - "PHYSDEVDRIVER=%s", dev->driver->name); -#endif + "PHYSDEVBUS=%s", dev->bus->name); + + if (dev->driver) + add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "PHYSDEVDRIVER=%s", dev->driver->name); } +#endif /* terminate, set to next free slot, shrink available space */ envp[i] = NULL; @@ -184,19 +208,25 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp, if (dev->bus && dev->bus->uevent) { /* have the bus specific function add its stuff */ retval = dev->bus->uevent(dev, envp, num_envp, buffer, buffer_size); - if (retval) { - pr_debug ("%s - uevent() returned %d\n", + if (retval) + pr_debug ("%s: bus uevent() returned %d\n", __FUNCTION__, retval); - } } if (dev->class && dev->class->dev_uevent) { /* have the class specific function add its stuff */ retval = dev->class->dev_uevent(dev, envp, num_envp, buffer, buffer_size); - if (retval) { - pr_debug("%s - dev_uevent() returned %d\n", - __FUNCTION__, retval); - } + if (retval) + pr_debug("%s: class uevent() returned %d\n", + __FUNCTION__, retval); + } + + if (dev->type && dev->type->uevent) { + /* have the device type specific fuction add its stuff */ + retval = dev->type->uevent(dev, envp, num_envp, buffer, buffer_size); + if (retval) + pr_debug("%s: dev_type uevent() returned %d\n", + __FUNCTION__, retval); } return retval; @@ -247,37 +277,50 @@ static void device_remove_groups(struct device *dev) static int device_add_attrs(struct device *dev) { struct class *class = dev->class; + struct device_type *type = dev->type; int error = 0; int i; - if (!class) - return 0; - - if (class->dev_attrs) { + if (class && class->dev_attrs) { for (i = 0; attr_name(class->dev_attrs[i]); i++) { error = device_create_file(dev, &class->dev_attrs[i]); if (error) break; } + if (error) + while (--i >= 0) + device_remove_file(dev, &class->dev_attrs[i]); } - if (error) - while (--i >= 0) - device_remove_file(dev, &class->dev_attrs[i]); + + if (type && type->attrs) { + for (i = 0; attr_name(type->attrs[i]); i++) { + error = device_create_file(dev, &type->attrs[i]); + if (error) + break; + } + if (error) + while (--i >= 0) + device_remove_file(dev, &type->attrs[i]); + } + return error; } static void device_remove_attrs(struct device *dev) { struct class *class = dev->class; + struct device_type *type = dev->type; int i; - if (!class) - return; - - if (class->dev_attrs) { + if (class && class->dev_attrs) { for (i = 0; attr_name(class->dev_attrs[i]); i++) device_remove_file(dev, &class->dev_attrs[i]); } + + if (type && type->attrs) { + for (i = 0; attr_name(type->attrs[i]); i++) + device_remove_file(dev, &type->attrs[i]); + } } @@ -385,27 +428,30 @@ void device_initialize(struct device *dev) INIT_LIST_HEAD(&dev->dma_pools); INIT_LIST_HEAD(&dev->node); init_MUTEX(&dev->sem); + spin_lock_init(&dev->devres_lock); + INIT_LIST_HEAD(&dev->devres_head); device_init_wakeup(dev, 0); set_dev_node(dev, -1); } #ifdef CONFIG_SYSFS_DEPRECATED -static int setup_parent(struct device *dev, struct device *parent) +static struct kobject * get_device_parent(struct device *dev, + struct device *parent) { /* Set the parent to the class, not the parent device */ /* this keeps sysfs from having a symlink to make old udevs happy */ if (dev->class) - dev->kobj.parent = &dev->class->subsys.kset.kobj; + return &dev->class->subsys.kset.kobj; else if (parent) - dev->kobj.parent = &parent->kobj; + return &parent->kobj; - return 0; + return NULL; } #else -static int virtual_device_parent(struct device *dev) +static struct kobject * virtual_device_parent(struct device *dev) { if (!dev->class) - return -ENODEV; + return ERR_PTR(-ENODEV); if (!dev->class->virtual_dir) { static struct kobject *virtual_dir = NULL; @@ -415,25 +461,31 @@ static int virtual_device_parent(struct device *dev) dev->class->virtual_dir = kobject_add_dir(virtual_dir, dev->class->name); } - dev->kobj.parent = dev->class->virtual_dir; - return 0; + return dev->class->virtual_dir; } -static int setup_parent(struct device *dev, struct device *parent) +static struct kobject * get_device_parent(struct device *dev, + struct device *parent) { - int error; - /* if this is a class device, and has no parent, create one */ if ((dev->class) && (parent == NULL)) { - error = virtual_device_parent(dev); - if (error) - return error; + return virtual_device_parent(dev); } else if (parent) - dev->kobj.parent = &parent->kobj; + return &parent->kobj; + return NULL; +} +#endif +static int setup_parent(struct device *dev, struct device *parent) +{ + struct kobject *kobj; + kobj = get_device_parent(dev, parent); + if (IS_ERR(kobj)) + return PTR_ERR(kobj); + if (kobj) + dev->kobj.parent = kobj; return 0; } -#endif /** * device_add - add device to device hierarchy. @@ -520,9 +572,13 @@ int device_add(struct device *dev) &dev->kobj, dev->bus_id); #ifdef CONFIG_SYSFS_DEPRECATED if (parent) { - sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device"); - class_name = make_class_name(dev->class->name, &dev->kobj); - sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name); + sysfs_create_link(&dev->kobj, &dev->parent->kobj, + "device"); + class_name = make_class_name(dev->class->name, + &dev->kobj); + if (class_name) + sysfs_create_link(&dev->parent->kobj, + &dev->kobj, class_name); } #endif } @@ -535,7 +591,8 @@ int device_add(struct device *dev) goto PMError; if ((error = bus_add_device(dev))) goto BusError; - kobject_uevent(&dev->kobj, KOBJ_ADD); + if (!dev->uevent_suppress) + kobject_uevent(&dev->kobj, KOBJ_ADD); if ((error = bus_attach_device(dev))) goto AttachError; if (parent) @@ -665,7 +722,9 @@ void device_del(struct device * dev) if (parent) { char *class_name = make_class_name(dev->class->name, &dev->kobj); - sysfs_remove_link(&dev->parent->kobj, class_name); + if (class_name) + sysfs_remove_link(&dev->parent->kobj, + class_name); kfree(class_name); sysfs_remove_link(&dev->kobj, "device"); } @@ -968,20 +1027,25 @@ static int device_move_class_links(struct device *dev, class_name = make_class_name(dev->class->name, &dev->kobj); if (!class_name) { - error = PTR_ERR(class_name); - class_name = NULL; + error = -ENOMEM; goto out; } if (old_parent) { sysfs_remove_link(&dev->kobj, "device"); sysfs_remove_link(&old_parent->kobj, class_name); } - 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"); + 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; @@ -993,29 +1057,28 @@ out: /** * 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 + * @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; - if (!device_is_registered(dev)) { - error = -EINVAL; - goto out; - } new_parent = get_device(new_parent); - if (!new_parent) { - error = -EINVAL; + new_parent_kobj = get_device_parent (dev, new_parent); + if (IS_ERR(new_parent_kobj)) { + error = PTR_ERR(new_parent_kobj); + put_device(new_parent); goto out; } pr_debug("DEVICE: moving '%s' to '%s'\n", dev->bus_id, - new_parent->bus_id); - error = kobject_move(&dev->kobj, &new_parent->kobj); + new_parent ? new_parent->bus_id : "<NULL>"); + error = kobject_move(&dev->kobj, new_parent_kobj); if (error) { put_device(new_parent); goto out; @@ -1024,7 +1087,8 @@ int device_move(struct device *dev, struct device *new_parent) dev->parent = new_parent; if (old_parent) klist_remove(&dev->knode_parent); - klist_add_tail(&dev->knode_parent, &new_parent->klist_children); + if (new_parent) + klist_add_tail(&dev->knode_parent, &new_parent->klist_children); if (!dev->class) goto out_put; error = device_move_class_links(dev, old_parent, new_parent); @@ -1032,7 +1096,8 @@ int device_move(struct device *dev, struct device *new_parent) /* 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)) { - klist_remove(&dev->knode_parent); + if (new_parent) + klist_remove(&dev->knode_parent); if (old_parent) klist_add_tail(&dev->knode_parent, &old_parent->klist_children); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 510e7884975..6a48824e43f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -86,8 +86,12 @@ static void driver_sysfs_remove(struct device *dev) */ int device_bind_driver(struct device *dev) { - driver_bound(dev); - return driver_sysfs_add(dev); + int ret; + + ret = driver_sysfs_add(dev); + if (!ret) + driver_bound(dev); + return ret; } struct stupid_thread_structure { @@ -108,6 +112,7 @@ static int really_probe(void *void_data) atomic_inc(&probe_count); pr_debug("%s: Probing driver %s with device %s\n", drv->bus->name, drv->name, dev->bus_id); + WARN_ON(!list_empty(&dev->devres_head)); dev->driver = drv; if (driver_sysfs_add(dev)) { @@ -133,21 +138,21 @@ static int really_probe(void *void_data) goto done; probe_failed: + devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; - if (ret == -ENODEV || ret == -ENXIO) { - /* Driver matched, but didn't support device - * or device not found. - * Not an error; keep going. - */ - ret = 0; - } else { + 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: kfree(data); atomic_dec(&probe_count); @@ -324,6 +329,7 @@ static void __device_release_driver(struct device * dev) dev->bus->remove(dev); else if (drv->remove) drv->remove(dev); + devres_release_all(dev); dev->driver = NULL; put_driver(drv); } diff --git a/drivers/base/devres.c b/drivers/base/devres.c new file mode 100644 index 00000000000..e177c9533b6 --- /dev/null +++ b/drivers/base/devres.c @@ -0,0 +1,644 @@ +/* + * 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> + +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); +} + +/** + * 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. + */ +#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 +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 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 - 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 - 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 00000000000..ca9186f70a6 --- /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/dmapool.c b/drivers/base/dmapool.c index dbe0735f8c9..cd467c9f33b 100644 --- a/drivers/base/dmapool.c +++ b/drivers/base/dmapool.c @@ -173,7 +173,7 @@ pool_alloc_page (struct dma_pool *pool, gfp_t mem_flags) mapsize = (mapsize + BITS_PER_LONG - 1) / BITS_PER_LONG; mapsize *= sizeof (long); - page = (struct dma_page *) kmalloc (mapsize + sizeof *page, mem_flags); + page = kmalloc(mapsize + sizeof *page, mem_flags); if (!page) return NULL; page->vaddr = dma_alloc_coherent (pool->dev, @@ -415,8 +415,67 @@ dma_pool_free (struct dma_pool *pool, void *vaddr, dma_addr_t dma) spin_unlock_irqrestore (&pool->lock, flags); } +/* + * Managed DMA pool + */ +static void dmam_pool_release(struct device *dev, void *res) +{ + struct dma_pool *pool = *(struct dma_pool **)res; + + dma_pool_destroy(pool); +} + +static int dmam_pool_match(struct device *dev, void *res, void *match_data) +{ + return *(struct dma_pool **)res == match_data; +} + +/** + * dmam_pool_create - Managed dma_pool_create() + * @name: name of pool, for diagnostics + * @dev: device that will be doing the DMA + * @size: size of the blocks in this pool. + * @align: alignment requirement for blocks; must be a power of two + * @allocation: returned blocks won't cross this boundary (or zero) + * + * Managed dma_pool_create(). DMA pool created with this function is + * automatically destroyed on driver detach. + */ +struct dma_pool *dmam_pool_create(const char *name, struct device *dev, + size_t size, size_t align, size_t allocation) +{ + struct dma_pool **ptr, *pool; + + ptr = devres_alloc(dmam_pool_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + pool = *ptr = dma_pool_create(name, dev, size, align, allocation); + if (pool) + devres_add(dev, ptr); + else + devres_free(ptr); + + return pool; +} + +/** + * dmam_pool_destroy - Managed dma_pool_destroy() + * @pool: dma pool that will be destroyed + * + * Managed dma_pool_destroy(). + */ +void dmam_pool_destroy(struct dma_pool *pool) +{ + struct device *dev = pool->dev; + + dma_pool_destroy(pool); + WARN_ON(devres_destroy(dev, dmam_pool_release, dmam_pool_match, pool)); +} EXPORT_SYMBOL (dma_pool_create); EXPORT_SYMBOL (dma_pool_destroy); EXPORT_SYMBOL (dma_pool_alloc); EXPORT_SYMBOL (dma_pool_free); +EXPORT_SYMBOL (dmam_pool_create); +EXPORT_SYMBOL (dmam_pool_destroy); diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 4bad2870c48..c0a979a5074 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -35,7 +35,7 @@ enum { FW_STATUS_READY_NOHOTPLUG, }; -static int loading_timeout = 10; /* In seconds */ +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 */ @@ -127,6 +127,7 @@ static ssize_t firmware_loading_show(struct device *dev, /** * 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 * diff --git a/drivers/base/platform.c b/drivers/base/platform.c index d1df4a08792..30480f6f2af 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -212,7 +212,7 @@ EXPORT_SYMBOL_GPL(platform_device_add_resources); * 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, void *data, size_t size) +int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size) { void *d; @@ -473,7 +473,7 @@ EXPORT_SYMBOL_GPL(platform_driver_unregister); * Returns zero if the driver registered and bound to a device, else returns * a negative error code and with the driver not registered. */ -int platform_driver_probe(struct platform_driver *drv, +int __init_or_module platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *)) { int retval, code; @@ -611,8 +611,15 @@ EXPORT_SYMBOL_GPL(platform_bus_type); int __init platform_bus_init(void) { - device_register(&platform_bus); - return bus_register(&platform_bus_type); + 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 |