diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 51 | ||||
-rw-r--r-- | drivers/base/bus.c | 26 | ||||
-rw-r--r-- | drivers/base/class.c | 18 | ||||
-rw-r--r-- | drivers/base/core.c | 52 | ||||
-rw-r--r-- | drivers/base/cpu.c | 105 | ||||
-rw-r--r-- | drivers/base/dd.c | 38 | ||||
-rw-r--r-- | drivers/base/devtmpfs.c | 13 | ||||
-rw-r--r-- | drivers/base/firmware_class.c | 13 | ||||
-rw-r--r-- | drivers/base/memory.c | 35 | ||||
-rw-r--r-- | drivers/base/node.c | 88 | ||||
-rw-r--r-- | drivers/base/platform.c | 109 | ||||
-rw-r--r-- | drivers/base/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/power/generic_ops.c | 233 | ||||
-rw-r--r-- | drivers/base/power/main.c | 163 | ||||
-rw-r--r-- | drivers/base/power/power.h | 6 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 45 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 100 | ||||
-rw-r--r-- | drivers/base/sys.c | 17 |
18 files changed, 864 insertions, 249 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index ee377270beb..fd52c48ee76 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -3,35 +3,50 @@ menu "Generic Driver Options" config UEVENT_HELPER_PATH string "path to uevent helper" depends on HOTPLUG - default "/sbin/hotplug" + default "" help Path to uevent helper program forked by the kernel for every uevent. + Before the switch to the netlink-based uevent source, this was + used to hook hotplug scripts into kernel device events. It + usually pointed to a shell script at /sbin/hotplug. + This should not be used today, because usual systems create + many events at bootup or device discovery in a very short time + frame. One forked process per event can create so many processes + that it creates a high system load, or on smaller systems + it is known to create out-of-memory situations during bootup. config DEVTMPFS - bool "Create a kernel maintained /dev tmpfs (EXPERIMENTAL)" + bool "Maintain a devtmpfs filesystem to mount at /dev" depends on HOTPLUG && SHMEM && TMPFS help - This creates a tmpfs filesystem, and mounts it at bootup - and mounts it at /dev. The kernel driver core creates device - nodes for all registered devices in that filesystem. All device - nodes are owned by root and have the default mode of 0600. - Userspace can add and delete the nodes as needed. This is - intended to simplify bootup, and make it possible to delay - the initial coldplug at bootup done by udev in userspace. - It should also provide a simpler way for rescue systems - to bring up a kernel with dynamic major/minor numbers. - Meaningful symlinks, permissions and device ownership must - still be handled by userspace. - If unsure, say N here. + This creates a tmpfs filesystem instance early at bootup. + In this filesystem, the kernel driver core maintains device + nodes with their default names and permissions for all + registered devices with an assigned major/minor number. + Userspace can modify the filesystem content as needed, add + symlinks, and apply needed permissions. + It provides a fully functional /dev directory, where usually + udev runs on top, managing permissions and adding meaningful + symlinks. + In very limited environments, it may provide a sufficient + functional /dev without any further help. It also allows simple + rescue systems, and reliably handles dynamic major/minor numbers. config DEVTMPFS_MOUNT - bool "Automount devtmpfs at /dev" + bool "Automount devtmpfs at /dev, after the kernel mounted the rootfs" depends on DEVTMPFS help - This will mount devtmpfs at /dev if the kernel mounts the root - filesystem. It will not affect initramfs based mounting. - If unsure, say N here. + This will instruct the kernel to automatically mount the + devtmpfs filesystem at /dev, directly after the kernel has + mounted the root filesystem. The behavior can be overridden + with the commandline parameter: devtmpfs.mount=0|1. + This option does not affect initramfs based booting, here + the devtmpfs filesystem always needs to be mounted manually + after the roots is mounted. + With this option enabled, it allows to bring up a system in + rescue mode with init=/bin/sh, even when the /dev directory + on the rootfs is completely empty. config STANDALONE bool "Select only drivers that don't need compile-time external firmware" if EXPERIMENTAL diff --git a/drivers/base/bus.c b/drivers/base/bus.c index c0c5a43d9fb..71f6af5c8b0 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -70,7 +70,7 @@ static ssize_t drv_attr_store(struct kobject *kobj, struct attribute *attr, return ret; } -static struct sysfs_ops driver_sysfs_ops = { +static const struct sysfs_ops driver_sysfs_ops = { .show = drv_attr_show, .store = drv_attr_store, }; @@ -115,7 +115,7 @@ static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr, return ret; } -static struct sysfs_ops bus_sysfs_ops = { +static const struct sysfs_ops bus_sysfs_ops = { .show = bus_attr_show, .store = bus_attr_store, }; @@ -154,7 +154,7 @@ static int bus_uevent_filter(struct kset *kset, struct kobject *kobj) return 0; } -static struct kset_uevent_ops bus_uevent_ops = { +static const struct kset_uevent_ops bus_uevent_ops = { .filter = bus_uevent_filter, }; @@ -173,10 +173,10 @@ static ssize_t driver_unbind(struct device_driver *drv, 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_lock(dev->parent); device_release_driver(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); err = count; } put_device(dev); @@ -200,12 +200,12 @@ static ssize_t driver_bind(struct device_driver *drv, dev = bus_find_device_by_name(bus, NULL, buf); if (dev && dev->driver == NULL && driver_match_device(drv, dev)) { if (dev->parent) /* Needed for USB */ - down(&dev->parent->sem); - down(&dev->sem); + device_lock(dev->parent); + device_lock(dev); err = driver_probe_device(drv, dev); - up(&dev->sem); + device_unlock(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); if (err > 0) { /* success */ @@ -744,10 +744,10 @@ static int __must_check bus_rescan_devices_helper(struct device *dev, if (!dev->driver) { if (dev->parent) /* Needed for USB */ - down(&dev->parent->sem); + device_lock(dev->parent); ret = device_attach(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); } return ret < 0 ? ret : 0; } @@ -779,10 +779,10 @@ int device_reprobe(struct device *dev) { if (dev->driver) { if (dev->parent) /* Needed for USB */ - down(&dev->parent->sem); + device_lock(dev->parent); device_release_driver(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); } return bus_rescan_devices_helper(dev, NULL); } diff --git a/drivers/base/class.c b/drivers/base/class.c index 6e2c3b064f5..9c6a0d6408e 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -31,7 +31,7 @@ static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr, ssize_t ret = -EIO; if (class_attr->show) - ret = class_attr->show(cp->class, buf); + ret = class_attr->show(cp->class, class_attr, buf); return ret; } @@ -43,7 +43,7 @@ static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr, ssize_t ret = -EIO; if (class_attr->store) - ret = class_attr->store(cp->class, buf, count); + ret = class_attr->store(cp->class, class_attr, buf, count); return ret; } @@ -63,7 +63,7 @@ static void class_release(struct kobject *kobj) kfree(cp); } -static struct sysfs_ops class_sysfs_ops = { +static const struct sysfs_ops class_sysfs_ops = { .show = class_attr_show, .store = class_attr_store, }; @@ -219,6 +219,8 @@ static void class_create_release(struct class *cls) * This is used to create a struct class pointer that can then be used * in calls to device_create(). * + * Returns &struct class pointer on success, or ERR_PTR() on error. + * * Note, the pointer created here is to be destroyed when finished by * making a call to class_destroy(). */ @@ -490,6 +492,16 @@ void class_interface_unregister(struct class_interface *class_intf) class_put(parent); } +ssize_t show_class_attr_string(struct class *class, struct class_attribute *attr, + char *buf) +{ + struct class_attribute_string *cs; + cs = container_of(attr, struct class_attribute_string, attr); + return snprintf(buf, PAGE_SIZE, "%s\n", cs->str); +} + +EXPORT_SYMBOL_GPL(show_class_attr_string); + struct class_compat { struct kobject *kobj; }; diff --git a/drivers/base/core.c b/drivers/base/core.c index 28202577042..b56a0ba31d4 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -100,7 +100,7 @@ static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr, return ret; } -static struct sysfs_ops dev_sysfs_ops = { +static const struct sysfs_ops dev_sysfs_ops = { .show = dev_attr_show, .store = dev_attr_store, }; @@ -252,7 +252,7 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, return retval; } -static struct kset_uevent_ops device_uevent_ops = { +static const struct kset_uevent_ops device_uevent_ops = { .filter = dev_uevent_filter, .name = dev_uevent_name, .uevent = dev_uevent, @@ -306,15 +306,10 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, { enum kobject_action action; - if (kobject_action_type(buf, count, &action) == 0) { + 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: + else + dev_err(dev, "uevent: unknown action-string\n"); return count; } @@ -607,6 +602,7 @@ static struct kobject *get_device_parent(struct device *dev, int retval; if (dev->class) { + static DEFINE_MUTEX(gdp_mutex); struct kobject *kobj = NULL; struct kobject *parent_kobj; struct kobject *k; @@ -623,6 +619,8 @@ static struct kobject *get_device_parent(struct device *dev, else parent_kobj = &parent->kobj; + mutex_lock(&gdp_mutex); + /* 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) @@ -631,20 +629,26 @@ static struct kobject *get_device_parent(struct device *dev, break; } spin_unlock(&dev->class->p->class_dirs.list_lock); - if (kobj) + if (kobj) { + mutex_unlock(&gdp_mutex); return kobj; + } /* or create a new class-directory at the parent device */ k = kobject_create(); - if (!k) + if (!k) { + mutex_unlock(&gdp_mutex); return NULL; + } k->kset = &dev->class->p->class_dirs; retval = kobject_add(k, parent_kobj, "%s", dev->class->name); if (retval < 0) { + mutex_unlock(&gdp_mutex); kobject_put(k); return NULL; } /* do not emit an uevent for this simple "glue" directory */ + mutex_unlock(&gdp_mutex); return k; } @@ -1341,6 +1345,8 @@ static void root_device_release(struct device *dev) * 'module' symlink which points to the @owner directory * in sysfs. * + * Returns &struct device pointer on success, or ERR_PTR() on error. + * * Note: You probably want to use root_device_register(). */ struct device *__root_device_register(const char *name, struct module *owner) @@ -1428,6 +1434,8 @@ static void device_create_release(struct device *dev) * Any further sysfs files that might be required can be created using this * pointer. * + * Returns &struct device pointer on success, or ERR_PTR() on error. + * * Note: the struct class passed to this function must have previously * been created with a call to class_create(). */ @@ -1488,6 +1496,8 @@ EXPORT_SYMBOL_GPL(device_create_vargs); * Any further sysfs files that might be required can be created using this * pointer. * + * Returns &struct device pointer on success, or ERR_PTR() on error. + * * Note: the struct class passed to this function must have previously * been created with a call to class_create(). */ @@ -1574,22 +1584,16 @@ int device_rename(struct device *dev, char *new_name) 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); + error = sysfs_rename_link(&dev->parent->kobj, + &dev->kobj, + old_class_name, + new_class_name); } } #else if (dev->class) { - error = sysfs_create_link_nowarn(&dev->class->p->class_subsys.kobj, - &dev->kobj, dev_name(dev)); - if (error) - goto out; - sysfs_remove_link(&dev->class->p->class_subsys.kobj, - old_device_name); + error = sysfs_rename_link(&dev->class->p->class_subsys.kobj, + &dev->kobj, old_device_name, new_name); } #endif diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index 958bd1540c3..b5242e1e8bc 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -13,8 +13,11 @@ #include "base.h" +static struct sysdev_class_attribute *cpu_sysdev_class_attrs[]; + struct sysdev_class cpu_sysdev_class = { .name = "cpu", + .attrs = cpu_sysdev_class_attrs, }; EXPORT_SYMBOL(cpu_sysdev_class); @@ -76,34 +79,24 @@ void unregister_cpu(struct cpu *cpu) } #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE -static ssize_t cpu_probe_store(struct class *class, const char *buf, +static ssize_t cpu_probe_store(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) { return arch_cpu_probe(buf, count); } -static ssize_t cpu_release_store(struct class *class, const char *buf, +static ssize_t cpu_release_store(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) { return arch_cpu_release(buf, count); } -static CLASS_ATTR(probe, S_IWUSR, NULL, cpu_probe_store); -static CLASS_ATTR(release, S_IWUSR, NULL, cpu_release_store); - -int __init cpu_probe_release_init(void) -{ - int rc; - - rc = sysfs_create_file(&cpu_sysdev_class.kset.kobj, - &class_attr_probe.attr); - if (!rc) - rc = sysfs_create_file(&cpu_sysdev_class.kset.kobj, - &class_attr_release.attr); - - return rc; -} -device_initcall(cpu_probe_release_init); +static SYSDEV_CLASS_ATTR(probe, S_IWUSR, NULL, cpu_probe_store); +static SYSDEV_CLASS_ATTR(release, S_IWUSR, NULL, cpu_release_store); #endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */ #else /* ... !CONFIG_HOTPLUG_CPU */ @@ -141,31 +134,39 @@ static SYSDEV_ATTR(crash_notes, 0400, show_crash_notes, NULL); /* * Print cpu online, possible, present, and system maps */ -static ssize_t print_cpus_map(char *buf, const struct cpumask *map) + +struct cpu_attr { + struct sysdev_class_attribute attr; + const struct cpumask *const * const map; +}; + +static ssize_t show_cpus_attr(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + char *buf) { - int n = cpulist_scnprintf(buf, PAGE_SIZE-2, map); + struct cpu_attr *ca = container_of(attr, struct cpu_attr, attr); + int n = cpulist_scnprintf(buf, PAGE_SIZE-2, *(ca->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##_mask); \ -} \ -static struct sysdev_class_attribute attr_##type##_map = \ - _SYSDEV_CLASS_ATTR(type, 0444, print_cpus_##type, NULL) +#define _CPU_ATTR(name, map) \ + { _SYSDEV_CLASS_ATTR(name, 0444, show_cpus_attr, NULL), map } -print_cpus_func(online); -print_cpus_func(possible); -print_cpus_func(present); +/* Keep in sync with cpu_sysdev_class_attrs */ +static struct cpu_attr cpu_attrs[] = { + _CPU_ATTR(online, &cpu_online_mask), + _CPU_ATTR(possible, &cpu_possible_mask), + _CPU_ATTR(present, &cpu_present_mask), +}; /* * Print values for NR_CPUS and offlined cpus */ -static ssize_t print_cpus_kernel_max(struct sysdev_class *class, char *buf) +static ssize_t print_cpus_kernel_max(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) { int n = snprintf(buf, PAGE_SIZE-2, "%d\n", NR_CPUS - 1); return n; @@ -175,7 +176,8 @@ static SYSDEV_CLASS_ATTR(kernel_max, 0444, print_cpus_kernel_max, NULL); /* arch-optional setting to enable display of offline cpus >= nr_cpu_ids */ unsigned int total_cpus; -static ssize_t print_cpus_offline(struct sysdev_class *class, char *buf) +static ssize_t print_cpus_offline(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) { int n = 0, len = PAGE_SIZE-2; cpumask_var_t offline; @@ -204,29 +206,6 @@ static ssize_t print_cpus_offline(struct sysdev_class *class, char *buf) } static SYSDEV_CLASS_ATTR(offline, 0444, print_cpus_offline, NULL); -static struct sysdev_class_attribute *cpu_state_attr[] = { - &attr_online_map, - &attr_possible_map, - &attr_present_map, - &attr_kernel_max, - &attr_offline, -}; - -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 @@ -272,9 +251,6 @@ 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); @@ -282,3 +258,16 @@ int __init cpu_dev_init(void) return err; } + +static struct sysdev_class_attribute *cpu_sysdev_class_attrs[] = { +#ifdef CONFIG_ARCH_CPU_PROBE_RELEASE + &attr_probe, + &attr_release, +#endif + &cpu_attrs[0].attr, + &cpu_attrs[1].attr, + &cpu_attrs[2].attr, + &attr_kernel_max, + &attr_offline, + NULL +}; diff --git a/drivers/base/dd.c b/drivers/base/dd.c index ee95c76bfd3..c89291f8a16 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -85,7 +85,7 @@ static void driver_sysfs_remove(struct device *dev) * 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. + * This function must be called with the device lock held. */ int device_bind_driver(struct device *dev) { @@ -190,8 +190,8 @@ EXPORT_SYMBOL_GPL(wait_for_device_probe); * This function returns -ENODEV if the device is not registered, * 1 if the device is bound successfully 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. + * This function must be called with @dev lock held. When called for a + * USB interface, @dev->parent lock must be held as well. */ int driver_probe_device(struct device_driver *drv, struct device *dev) { @@ -233,13 +233,13 @@ static int __device_attach(struct device_driver *drv, void *data) * 0 if no matching driver was found; * -ENODEV if the device is not registered. * - * When called for a USB interface, @dev->parent->sem must be held. + * When called for a USB interface, @dev->parent lock must be held. */ int device_attach(struct device *dev) { int ret = 0; - down(&dev->sem); + device_lock(dev); if (dev->driver) { ret = device_bind_driver(dev); if (ret == 0) @@ -253,7 +253,7 @@ int device_attach(struct device *dev) ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); pm_runtime_put_sync(dev); } - up(&dev->sem); + device_unlock(dev); return ret; } EXPORT_SYMBOL_GPL(device_attach); @@ -276,13 +276,13 @@ static int __driver_attach(struct device *dev, void *data) return 0; if (dev->parent) /* Needed for USB */ - down(&dev->parent->sem); - down(&dev->sem); + device_lock(dev->parent); + device_lock(dev); if (!dev->driver) driver_probe_device(drv, dev); - up(&dev->sem); + device_unlock(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); return 0; } @@ -303,8 +303,8 @@ int driver_attach(struct device_driver *drv) 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. + * __device_release_driver() must be called with @dev lock held. + * When called for a USB interface, @dev->parent lock must be held as well. */ static void __device_release_driver(struct device *dev) { @@ -343,7 +343,7 @@ static void __device_release_driver(struct device *dev) * @dev: device. * * Manually detach device from driver. - * When called for a USB interface, @dev->parent->sem must be held. + * When called for a USB interface, @dev->parent lock must be held. */ void device_release_driver(struct device *dev) { @@ -352,9 +352,9 @@ void device_release_driver(struct device *dev) * within their ->remove callback for the same device, they * will deadlock right here. */ - down(&dev->sem); + device_lock(dev); __device_release_driver(dev); - up(&dev->sem); + device_unlock(dev); } EXPORT_SYMBOL_GPL(device_release_driver); @@ -381,13 +381,13 @@ void driver_detach(struct device_driver *drv) spin_unlock(&drv->p->klist_devices.k_lock); if (dev->parent) /* Needed for USB */ - down(&dev->parent->sem); - down(&dev->sem); + device_lock(dev->parent); + device_lock(dev); if (dev->driver == drv) __device_release_driver(dev); - up(&dev->sem); + device_unlock(dev); if (dev->parent) - up(&dev->parent->sem); + device_unlock(dev->parent); put_device(dev); } } diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 42ae452b36b..dac478c6e46 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -301,6 +301,19 @@ int devtmpfs_delete_node(struct device *dev) if (dentry->d_inode) { err = vfs_getattr(nd.path.mnt, dentry, &stat); if (!err && dev_mynode(dev, dentry->d_inode, &stat)) { + struct iattr newattrs; + /* + * before unlinking this node, reset permissions + * of possible references like hardlinks + */ + newattrs.ia_uid = 0; + newattrs.ia_gid = 0; + newattrs.ia_mode = stat.mode & ~0777; + newattrs.ia_valid = + ATTR_UID|ATTR_GID|ATTR_MODE; + mutex_lock(&dentry->d_inode->i_mutex); + notify_change(dentry, &newattrs); + mutex_unlock(&dentry->d_inode->i_mutex); err = vfs_unlink(nd.path.dentry->d_inode, dentry); if (!err || err == -ENOENT) diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index a95024166b6..18518ba13c8 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -19,7 +19,6 @@ #include <linux/kthread.h> #include <linux/highmem.h> #include <linux/firmware.h> -#include "base.h" #define to_dev(obj) container_of(obj, struct device, kobj) @@ -69,7 +68,9 @@ fw_load_abort(struct firmware_priv *fw_priv) } static ssize_t -firmware_timeout_show(struct class *class, char *buf) +firmware_timeout_show(struct class *class, + struct class_attribute *attr, + char *buf) { return sprintf(buf, "%d\n", loading_timeout); } @@ -77,6 +78,7 @@ firmware_timeout_show(struct class *class, char *buf) /** * firmware_timeout_store - set number of seconds to wait for firmware * @class: device class pointer + * @attr: device attribute pointer * @buf: buffer to scan for timeout value * @count: number of bytes in @buf * @@ -87,7 +89,9 @@ firmware_timeout_show(struct class *class, char *buf) * Note: zero means 'wait forever'. **/ static ssize_t -firmware_timeout_store(struct class *class, const char *buf, size_t count) +firmware_timeout_store(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) { loading_timeout = simple_strtol(buf, NULL, 10); if (loading_timeout < 0) @@ -439,6 +443,7 @@ static int fw_setup_device(struct firmware *fw, struct device **dev_p, fw_priv = dev_get_drvdata(f_dev); fw_priv->fw = fw; + sysfs_bin_attr_init(&fw_priv->attr_data); 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__); @@ -610,7 +615,7 @@ request_firmware_work_func(void *arg) } /** - * request_firmware_nowait: asynchronous version of request_firmware + * 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. diff --git a/drivers/base/memory.c b/drivers/base/memory.c index bd025059711..db0848e54cc 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -44,7 +44,7 @@ static int memory_uevent(struct kset *kset, struct kobject *obj, struct kobj_uev return retval; } -static struct kset_uevent_ops memory_uevent_ops = { +static const struct kset_uevent_ops memory_uevent_ops = { .name = memory_uevent_name, .uevent = memory_uevent, }; @@ -309,17 +309,18 @@ static SYSDEV_ATTR(removable, 0444, show_mem_removable, NULL); * Block size attribute stuff */ static ssize_t -print_block_size(struct class *class, char *buf) +print_block_size(struct sysdev_class *class, struct sysdev_class_attribute *attr, + 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 SYSDEV_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); + &attr_block_size_bytes.attr); } /* @@ -330,7 +331,8 @@ static int block_size_init(void) */ #ifdef CONFIG_ARCH_MEMORY_PROBE static ssize_t -memory_probe_store(struct class *class, const char *buf, size_t count) +memory_probe_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) { u64 phys_addr; int nid; @@ -367,7 +369,9 @@ static inline int memory_probe_init(void) /* Soft offline a page */ static ssize_t -store_soft_offline_page(struct class *class, const char *buf, size_t count) +store_soft_offline_page(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) { int ret; u64 pfn; @@ -384,7 +388,9 @@ store_soft_offline_page(struct class *class, const char *buf, size_t count) /* Forcibly offline a page, including killing processes. */ static ssize_t -store_hard_offline_page(struct class *class, const char *buf, size_t count) +store_hard_offline_page(struct class *class, + struct class_attribute *attr, + const char *buf, size_t count) { int ret; u64 pfn; @@ -423,12 +429,16 @@ static inline int memory_fail_init(void) * differentiation between which *physical* devices each * section belongs to... */ +int __weak arch_get_memory_phys_device(unsigned long start_pfn) +{ + return 0; +} static int add_memory_block(int nid, struct mem_section *section, - unsigned long state, int phys_device, - enum mem_add_context context) + unsigned long state, enum mem_add_context context) { struct memory_block *mem = kzalloc(sizeof(*mem), GFP_KERNEL); + unsigned long start_pfn; int ret = 0; if (!mem) @@ -437,7 +447,8 @@ static int add_memory_block(int nid, struct mem_section *section, mem->phys_index = __section_nr(section); mem->state = state; mutex_init(&mem->state_mutex); - mem->phys_device = phys_device; + start_pfn = section_nr_to_pfn(mem->phys_index); + mem->phys_device = arch_get_memory_phys_device(start_pfn); ret = register_memory(mem, section); if (!ret) @@ -509,7 +520,7 @@ int remove_memory_block(unsigned long node_id, struct mem_section *section, */ int register_new_memory(int nid, struct mem_section *section) { - return add_memory_block(nid, section, MEM_OFFLINE, 0, HOTPLUG); + return add_memory_block(nid, section, MEM_OFFLINE, HOTPLUG); } int unregister_memory_section(struct mem_section *section) @@ -542,7 +553,7 @@ int __init memory_dev_init(void) if (!present_section_nr(i)) continue; err = add_memory_block(0, __nr_to_section(i), MEM_ONLINE, - 0, BOOT); + BOOT); if (!ret) ret = err; } diff --git a/drivers/base/node.c b/drivers/base/node.c index 70122791683..93b3ac65c2d 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -16,8 +16,11 @@ #include <linux/device.h> #include <linux/swap.h> +static struct sysdev_class_attribute *node_state_attrs[]; + static struct sysdev_class node_class = { .name = "node", + .attrs = node_state_attrs, }; @@ -162,8 +165,11 @@ static ssize_t node_read_distance(struct sys_device * dev, int len = 0; int i; - /* buf currently PAGE_SIZE, need ~4 chars per node */ - BUILD_BUG_ON(MAX_NUMNODES*4 > PAGE_SIZE/2); + /* + * buf is currently PAGE_SIZE in length and each node needs 4 chars + * at the most (distance + space or newline). + */ + BUILD_BUG_ON(MAX_NUMNODES * 4 > PAGE_SIZE); for_each_online_node(i) len += sprintf(buf + len, "%s%d", i ? " " : "", node_distance(nid, i)); @@ -544,76 +550,52 @@ static ssize_t print_nodes_state(enum node_states state, char *buf) 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); -} +struct node_attr { + struct sysdev_class_attribute attr; + enum node_states state; +}; -static ssize_t print_nodes_has_cpu(struct sysdev_class *class, char *buf) +static ssize_t show_node_state(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) { - return print_nodes_state(N_CPU, buf); + struct node_attr *na = container_of(attr, struct node_attr, attr); + return print_nodes_state(na->state, 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); +#define _NODE_ATTR(name, state) \ + { _SYSDEV_CLASS_ATTR(name, 0444, show_node_state, NULL), state } +static struct node_attr node_state_attr[] = { + _NODE_ATTR(possible, N_POSSIBLE), + _NODE_ATTR(online, N_ONLINE), + _NODE_ATTR(has_normal_memory, N_NORMAL_MEMORY), + _NODE_ATTR(has_cpu, N_CPU), #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); + _NODE_ATTR(has_high_memory, N_HIGH_MEMORY), #endif +}; -struct sysdev_class_attribute *node_state_attr[] = { - &attr_possible, - &attr_online, - &attr_has_normal_memory, +static struct sysdev_class_attribute *node_state_attrs[] = { + &node_state_attr[0].attr, + &node_state_attr[1].attr, + &node_state_attr[2].attr, + &node_state_attr[3].attr, #ifdef CONFIG_HIGHMEM - &attr_has_high_memory, + &node_state_attr[4].attr, #endif - &attr_has_cpu, + NULL }; -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; -} - #define NODE_CALLBACK_PRI 2 /* lower than SLAB */ static int __init register_node_type(void) { int ret; + BUILD_BUG_ON(ARRAY_SIZE(node_state_attr) != NR_NODE_STATES); + BUILD_BUG_ON(ARRAY_SIZE(node_state_attrs)-1 != NR_NODE_STATES); + ret = sysdev_class_register(&node_class); if (!ret) { - ret = node_states_init(); hotplug_memory_notifier(node_memory_callback, NODE_CALLBACK_PRI); } diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 58efaf2f125..4b4b565c835 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -128,7 +128,7 @@ struct platform_object { }; /** - * platform_device_put + * platform_device_put - destroy a platform device * @pdev: platform device to free * * Free all memory associated with a platform device. This function must @@ -152,7 +152,7 @@ static void platform_device_release(struct device *dev) } /** - * platform_device_alloc + * platform_device_alloc - create a platform device * @name: base name of the device we're adding * @id: instance id * @@ -177,7 +177,7 @@ struct platform_device *platform_device_alloc(const char *name, int id) EXPORT_SYMBOL_GPL(platform_device_alloc); /** - * platform_device_add_resources + * platform_device_add_resources - add resources to a platform device * @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 @@ -202,7 +202,7 @@ int platform_device_add_resources(struct platform_device *pdev, EXPORT_SYMBOL_GPL(platform_device_add_resources); /** - * platform_device_add_data + * platform_device_add_data - add platform-specific data to a platform device * @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 @@ -344,7 +344,7 @@ void platform_device_unregister(struct platform_device *pdev) EXPORT_SYMBOL_GPL(platform_device_unregister); /** - * platform_device_register_simple + * platform_device_register_simple - add a platform-level device and its resources * @name: base name of the device we're adding * @id: instance id * @res: set of resources that needs to be allocated for the device @@ -362,6 +362,8 @@ EXPORT_SYMBOL_GPL(platform_device_unregister); * 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". + * + * Returns &struct platform_device pointer on success, or ERR_PTR() on error. */ struct platform_device *platform_device_register_simple(const char *name, int id, @@ -396,7 +398,7 @@ error: EXPORT_SYMBOL_GPL(platform_device_register_simple); /** - * platform_device_register_data + * platform_device_register_data - add a platform-level device with platform-specific data * @parent: parent device for the device we're adding * @name: base name of the device we're adding * @id: instance id @@ -408,6 +410,8 @@ EXPORT_SYMBOL_GPL(platform_device_register_simple); * allocated for the device allows drivers using such devices to be * unloaded without waiting for the last reference to the device to be * dropped. + * + * Returns &struct platform_device pointer on success, or ERR_PTR() on error. */ struct platform_device *platform_device_register_data( struct device *parent, @@ -473,7 +477,7 @@ static void platform_drv_shutdown(struct device *_dev) } /** - * platform_driver_register + * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */ int platform_driver_register(struct platform_driver *drv) @@ -491,7 +495,7 @@ int platform_driver_register(struct platform_driver *drv) EXPORT_SYMBOL_GPL(platform_driver_register); /** - * platform_driver_unregister + * platform_driver_unregister - unregister a driver for platform-level devices * @drv: platform driver structure */ void platform_driver_unregister(struct platform_driver *drv) @@ -548,6 +552,66 @@ int __init_or_module platform_driver_probe(struct platform_driver *drv, } EXPORT_SYMBOL_GPL(platform_driver_probe); +/** + * platform_create_bundle - register driver and create corresponding device + * @driver: platform driver structure + * @probe: the driver probe routine, probably from an __init section + * @res: set of resources that needs to be allocated for the device + * @n_res: number of resources + * @data: platform specific data for this platform device + * @size: size of platform specific data + * + * Use this in legacy-style modules that probe hardware directly and + * register a single platform device and corresponding platform driver. + * + * Returns &struct platform_device pointer on success, or ERR_PTR() on error. + */ +struct platform_device * __init_or_module platform_create_bundle( + struct platform_driver *driver, + int (*probe)(struct platform_device *), + struct resource *res, unsigned int n_res, + const void *data, size_t size) +{ + struct platform_device *pdev; + int error; + + pdev = platform_device_alloc(driver->driver.name, -1); + if (!pdev) { + error = -ENOMEM; + goto err_out; + } + + if (res) { + error = platform_device_add_resources(pdev, res, n_res); + if (error) + goto err_pdev_put; + } + + if (data) { + error = platform_device_add_data(pdev, data, size); + if (error) + goto err_pdev_put; + } + + error = platform_device_add(pdev); + if (error) + goto err_pdev_put; + + error = platform_driver_probe(driver, probe); + if (error) + goto err_pdev_del; + + return pdev; + +err_pdev_del: + platform_device_del(pdev); +err_pdev_put: + platform_device_put(pdev); +err_out: + return ERR_PTR(error); +} +EXPORT_SYMBOL_GPL(platform_create_bundle); + /* modalias support enables more hands-off userspace setup: * (a) environment variable lets new-style hotplug events work once system is * fully running: "modprobe $MODALIAS" @@ -578,7 +642,7 @@ static int platform_uevent(struct device *dev, struct kobj_uevent_env *env) } static const struct platform_device_id *platform_match_id( - struct platform_device_id *id, + const struct platform_device_id *id, struct platform_device *pdev) { while (id->name[0]) { @@ -994,9 +1058,11 @@ static __initdata LIST_HEAD(early_platform_driver_list); static __initdata LIST_HEAD(early_platform_device_list); /** - * early_platform_driver_register + * early_platform_driver_register - register early platform driver * @epdrv: early_platform driver structure * @buf: string passed from early_param() + * + * Helper function for early_platform_init() / early_platform_init_buffer() */ int __init early_platform_driver_register(struct early_platform_driver *epdrv, char *buf) @@ -1048,9 +1114,12 @@ int __init early_platform_driver_register(struct early_platform_driver *epdrv, } /** - * early_platform_add_devices - add a numbers of early platform devices + * early_platform_add_devices - adds a number of early platform devices * @devs: array of early platform devices to add * @num: number of early platform devices in array + * + * Used by early architecture code to register early platform devices and + * their platform data. */ void __init early_platform_add_devices(struct platform_device **devs, int num) { @@ -1070,8 +1139,12 @@ void __init early_platform_add_devices(struct platform_device **devs, int num) } /** - * early_platform_driver_register_all + * early_platform_driver_register_all - register early platform drivers * @class_str: string to identify early platform driver class + * + * Used by architecture code to register all early platform drivers + * for a certain class. If omitted then only early platform drivers + * with matching kernel command line class parameters will be registered. */ void __init early_platform_driver_register_all(char *class_str) { @@ -1093,7 +1166,7 @@ void __init early_platform_driver_register_all(char *class_str) } /** - * early_platform_match + * early_platform_match - find early platform device matching driver * @epdrv: early platform driver structure * @id: id to match against */ @@ -1111,7 +1184,7 @@ early_platform_match(struct early_platform_driver *epdrv, int id) } /** - * early_platform_left + * early_platform_left - check if early platform driver has matching devices * @epdrv: early platform driver structure * @id: return true if id or above exists */ @@ -1129,7 +1202,7 @@ static __init int early_platform_left(struct early_platform_driver *epdrv, } /** - * early_platform_driver_probe_id + * early_platform_driver_probe_id - probe drivers matching class_str and id * @class_str: string to identify early platform driver class * @id: id to match against * @nr_probe: number of platform devices to successfully probe before exiting @@ -1199,10 +1272,14 @@ static int __init early_platform_driver_probe_id(char *class_str, } /** - * early_platform_driver_probe + * early_platform_driver_probe - probe a class of registered drivers * @class_str: string to identify early platform driver class * @nr_probe: number of platform devices to successfully probe before exiting * @user_only: only probe user specified early platform devices + * + * Used by architecture code to probe registered early platform drivers + * within a certain class. For probe to happen a registered early platform + * device matching a registered early platform driver is needed. */ int __init early_platform_driver_probe(char *class_str, int nr_probe, diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 3ce3519e8f3..89de75325ce 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_PM) += sysfs.o obj-$(CONFIG_PM_SLEEP) += main.o obj-$(CONFIG_PM_RUNTIME) += runtime.o +obj-$(CONFIG_PM_OPS) += generic_ops.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c new file mode 100644 index 00000000000..4b29d498125 --- /dev/null +++ b/drivers/base/power/generic_ops.c @@ -0,0 +1,233 @@ +/* + * drivers/base/power/generic_ops.c - Generic PM callbacks for subsystems + * + * Copyright (c) 2010 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. + * + * This file is released under the GPLv2. + */ + +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#ifdef CONFIG_PM_RUNTIME +/** + * pm_generic_runtime_idle - Generic runtime idle callback for subsystems. + * @dev: Device to handle. + * + * If PM operations are defined for the @dev's driver and they include + * ->runtime_idle(), execute it and return its error code, if nonzero. + * Otherwise, execute pm_runtime_suspend() for the device and return 0. + */ +int pm_generic_runtime_idle(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm && pm->runtime_idle) { + int ret = pm->runtime_idle(dev); + if (ret) + return ret; + } + + pm_runtime_suspend(dev); + return 0; +} +EXPORT_SYMBOL_GPL(pm_generic_runtime_idle); + +/** + * pm_generic_runtime_suspend - Generic runtime suspend callback for subsystems. + * @dev: Device to suspend. + * + * If PM operations are defined for the @dev's driver and they include + * ->runtime_suspend(), execute it and return its error code. Otherwise, + * return -EINVAL. + */ +int pm_generic_runtime_suspend(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + int ret; + + ret = pm && pm->runtime_suspend ? pm->runtime_suspend(dev) : -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(pm_generic_runtime_suspend); + +/** + * pm_generic_runtime_resume - Generic runtime resume callback for subsystems. + * @dev: Device to resume. + * + * If PM operations are defined for the @dev's driver and they include + * ->runtime_resume(), execute it and return its error code. Otherwise, + * return -EINVAL. + */ +int pm_generic_runtime_resume(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + int ret; + + ret = pm && pm->runtime_resume ? pm->runtime_resume(dev) : -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(pm_generic_runtime_resume); +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM_SLEEP +/** + * __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback. + * @dev: Device to handle. + * @event: PM transition of the system under way. + * + * If the device has not been suspended at run time, execute the + * suspend/freeze/poweroff/thaw callback provided by its driver, if defined, and + * return its error code. Otherwise, return zero. + */ +static int __pm_generic_call(struct device *dev, int event) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + int (*callback)(struct device *); + + if (!pm || pm_runtime_suspended(dev)) + return 0; + + switch (event) { + case PM_EVENT_SUSPEND: + callback = pm->suspend; + break; + case PM_EVENT_FREEZE: + callback = pm->freeze; + break; + case PM_EVENT_HIBERNATE: + callback = pm->poweroff; + break; + case PM_EVENT_THAW: + callback = pm->thaw; + break; + default: + callback = NULL; + break; + } + + return callback ? callback(dev) : 0; +} + +/** + * pm_generic_suspend - Generic suspend callback for subsystems. + * @dev: Device to suspend. + */ +int pm_generic_suspend(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_SUSPEND); +} +EXPORT_SYMBOL_GPL(pm_generic_suspend); + +/** + * pm_generic_freeze - Generic freeze callback for subsystems. + * @dev: Device to freeze. + */ +int pm_generic_freeze(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_FREEZE); +} +EXPORT_SYMBOL_GPL(pm_generic_freeze); + +/** + * pm_generic_poweroff - Generic poweroff callback for subsystems. + * @dev: Device to handle. + */ +int pm_generic_poweroff(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_HIBERNATE); +} +EXPORT_SYMBOL_GPL(pm_generic_poweroff); + +/** + * pm_generic_thaw - Generic thaw callback for subsystems. + * @dev: Device to thaw. + */ +int pm_generic_thaw(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_THAW); +} +EXPORT_SYMBOL_GPL(pm_generic_thaw); + +/** + * __pm_generic_resume - Generic resume/restore callback for subsystems. + * @dev: Device to handle. + * @event: PM transition of the system under way. + * + * Execute the resume/resotre callback provided by the @dev's driver, if + * defined. If it returns 0, change the device's runtime PM status to 'active'. + * Return the callback's error code. + */ +static int __pm_generic_resume(struct device *dev, int event) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + int (*callback)(struct device *); + int ret; + + if (!pm) + return 0; + + switch (event) { + case PM_EVENT_RESUME: + callback = pm->resume; + break; + case PM_EVENT_RESTORE: + callback = pm->restore; + break; + default: + callback = NULL; + break; + } + + if (!callback) + return 0; + + ret = callback(dev); + if (!ret) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; +} + +/** + * pm_generic_resume - Generic resume callback for subsystems. + * @dev: Device to resume. + */ +int pm_generic_resume(struct device *dev) +{ + return __pm_generic_resume(dev, PM_EVENT_RESUME); +} +EXPORT_SYMBOL_GPL(pm_generic_resume); + +/** + * pm_generic_restore - Generic restore callback for subsystems. + * @dev: Device to restore. + */ +int pm_generic_restore(struct device *dev) +{ + return __pm_generic_resume(dev, PM_EVENT_RESTORE); +} +EXPORT_SYMBOL_GPL(pm_generic_restore); +#endif /* CONFIG_PM_SLEEP */ + +struct dev_pm_ops generic_subsys_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +#endif +#ifdef CONFIG_PM_RUNTIME + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .runtime_idle = pm_generic_runtime_idle, +#endif +}; +EXPORT_SYMBOL_GPL(generic_subsys_pm_ops); diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index a5142bddef4..d477f4dc5e5 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -25,6 +25,7 @@ #include <linux/resume-trace.h> #include <linux/interrupt.h> #include <linux/sched.h> +#include <linux/async.h> #include "../base.h" #include "power.h" @@ -34,14 +35,15 @@ * 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 + * Since device_pm_add() may be called with a device lock held, + * we must never try to acquire a device lock while holding * dpm_list_mutex. */ LIST_HEAD(dpm_list); static DEFINE_MUTEX(dpm_list_mtx); +static pm_message_t pm_transition; /* * Set once the preparation of devices for a PM transition has started, reset @@ -56,6 +58,7 @@ static bool transition_started; void device_pm_init(struct device *dev) { dev->power.status = DPM_ON; + init_completion(&dev->power.completion); pm_runtime_init(dev); } @@ -111,6 +114,7 @@ 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)); + complete_all(&dev->power.completion); mutex_lock(&dpm_list_mtx); list_del_init(&dev->power.entry); mutex_unlock(&dpm_list_mtx); @@ -188,6 +192,31 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime, } /** + * dpm_wait - Wait for a PM operation to complete. + * @dev: Device to wait for. + * @async: If unset, wait only if the device's power.async_suspend flag is set. + */ +static void dpm_wait(struct device *dev, bool async) +{ + if (!dev) + return; + + if (async || (pm_async_enabled && dev->power.async_suspend)) + wait_for_completion(&dev->power.completion); +} + +static int dpm_wait_fn(struct device *dev, void *async_ptr) +{ + dpm_wait(dev, *((bool *)async_ptr)); + return 0; +} + +static void dpm_wait_for_children(struct device *dev, bool async) +{ + device_for_each_child(dev, &async, dpm_wait_fn); +} + +/** * pm_op - Execute the PM operation appropriate for given PM event. * @dev: Device to handle. * @ops: PM operations to choose from. @@ -271,8 +300,9 @@ static int pm_noirq_op(struct device *dev, ktime_t calltime, delta, rettime; if (initcall_debug) { - pr_info("calling %s_i+ @ %i\n", - dev_name(dev), task_pid_nr(current)); + pr_info("calling %s+ @ %i, parent: %s\n", + dev_name(dev), task_pid_nr(current), + dev->parent ? dev_name(dev->parent) : "none"); calltime = ktime_get(); } @@ -468,15 +498,19 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) * device_resume - Execute "resume" callbacks for given device. * @dev: Device to handle. * @state: PM transition of the system being carried out. + * @async: If true, the device is being resumed asynchronously. */ -static int device_resume(struct device *dev, pm_message_t state) +static int device_resume(struct device *dev, pm_message_t state, bool async) { int error = 0; TRACE_DEVICE(dev); TRACE_RESUME(0); - down(&dev->sem); + dpm_wait(dev->parent, async); + device_lock(dev); + + dev->power.status = DPM_RESUMING; if (dev->bus) { if (dev->bus->pm) { @@ -509,12 +543,30 @@ static int device_resume(struct device *dev, pm_message_t state) } } End: - up(&dev->sem); + device_unlock(dev); + complete_all(&dev->power.completion); TRACE_RESUME(error); return error; } +static void async_resume(void *data, async_cookie_t cookie) +{ + struct device *dev = (struct device *)data; + int error; + + error = device_resume(dev, pm_transition, true); + if (error) + pm_dev_err(dev, pm_transition, " async", error); + put_device(dev); +} + +static bool is_async(struct device *dev) +{ + return dev->power.async_suspend && pm_async_enabled + && !pm_trace_is_enabled(); +} + /** * dpm_resume - Execute "resume" callbacks for non-sysdev devices. * @state: PM transition of the system being carried out. @@ -525,21 +577,33 @@ static int device_resume(struct device *dev, pm_message_t state) static void dpm_resume(pm_message_t state) { struct list_head list; + struct device *dev; ktime_t starttime = ktime_get(); INIT_LIST_HEAD(&list); mutex_lock(&dpm_list_mtx); - while (!list_empty(&dpm_list)) { - struct device *dev = to_device(dpm_list.next); + pm_transition = state; + + list_for_each_entry(dev, &dpm_list, power.entry) { + if (dev->power.status < DPM_OFF) + continue; + + INIT_COMPLETION(dev->power.completion); + if (is_async(dev)) { + get_device(dev); + async_schedule(async_resume, dev); + } + } + while (!list_empty(&dpm_list)) { + dev = to_device(dpm_list.next); get_device(dev); - if (dev->power.status >= DPM_OFF) { + if (dev->power.status >= DPM_OFF && !is_async(dev)) { int error; - dev->power.status = DPM_RESUMING; mutex_unlock(&dpm_list_mtx); - error = device_resume(dev, state); + error = device_resume(dev, state, false); mutex_lock(&dpm_list_mtx); if (error) @@ -554,6 +618,7 @@ static void dpm_resume(pm_message_t state) } list_splice(&list, &dpm_list); mutex_unlock(&dpm_list_mtx); + async_synchronize_full(); dpm_show_time(starttime, state, NULL); } @@ -564,7 +629,7 @@ static void dpm_resume(pm_message_t state) */ static void device_complete(struct device *dev, pm_message_t state) { - down(&dev->sem); + device_lock(dev); if (dev->class && dev->class->pm && dev->class->pm->complete) { pm_dev_dbg(dev, state, "completing class "); @@ -581,7 +646,7 @@ static void device_complete(struct device *dev, pm_message_t state) dev->bus->pm->complete(dev); } - up(&dev->sem); + device_unlock(dev); } /** @@ -731,16 +796,23 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } +static int async_error; + /** * device_suspend - Execute "suspend" callbacks for given device. * @dev: Device to handle. * @state: PM transition of the system being carried out. + * @async: If true, the device is being suspended asynchronously. */ -static int device_suspend(struct device *dev, pm_message_t state) +static int __device_suspend(struct device *dev, pm_message_t state, bool async) { int error = 0; - down(&dev->sem); + dpm_wait_for_children(dev, async); + device_lock(dev); + + if (async_error) + goto End; if (dev->class) { if (dev->class->pm) { @@ -772,12 +844,44 @@ static int device_suspend(struct device *dev, pm_message_t state) error = legacy_suspend(dev, state, dev->bus->suspend); } } + + if (!error) + dev->power.status = DPM_OFF; + End: - up(&dev->sem); + device_unlock(dev); + complete_all(&dev->power.completion); return error; } +static void async_suspend(void *data, async_cookie_t cookie) +{ + struct device *dev = (struct device *)data; + int error; + + error = __device_suspend(dev, pm_transition, true); + if (error) { + pm_dev_err(dev, pm_transition, " async", error); + async_error = error; + } + + put_device(dev); +} + +static int device_suspend(struct device *dev) +{ + INIT_COMPLETION(dev->power.completion); + + if (pm_async_enabled && dev->power.async_suspend) { + get_device(dev); + async_schedule(async_suspend, dev); + return 0; + } + + return __device_suspend(dev, pm_transition, false); +} + /** * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. * @state: PM transition of the system being carried out. @@ -790,13 +894,15 @@ static int dpm_suspend(pm_message_t state) INIT_LIST_HEAD(&list); mutex_lock(&dpm_list_mtx); + pm_transition = state; + async_error = 0; while (!list_empty(&dpm_list)) { struct device *dev = to_device(dpm_list.prev); get_device(dev); mutex_unlock(&dpm_list_mtx); - error = device_suspend(dev, state); + error = device_suspend(dev); mutex_lock(&dpm_list_mtx); if (error) { @@ -804,13 +910,17 @@ static int dpm_suspend(pm_message_t state) put_device(dev); break; } - dev->power.status = DPM_OFF; if (!list_empty(&dev->power.entry)) list_move(&dev->power.entry, &list); put_device(dev); + if (async_error) + break; } list_splice(&list, dpm_list.prev); mutex_unlock(&dpm_list_mtx); + async_synchronize_full(); + if (!error) + error = async_error; if (!error) dpm_show_time(starttime, state, NULL); return error; @@ -828,7 +938,7 @@ static int device_prepare(struct device *dev, pm_message_t state) { int error = 0; - down(&dev->sem); + device_lock(dev); if (dev->bus && dev->bus->pm && dev->bus->pm->prepare) { pm_dev_dbg(dev, state, "preparing "); @@ -852,7 +962,7 @@ static int device_prepare(struct device *dev, pm_message_t state) suspend_report_result(dev->class->pm->prepare, error); } End: - up(&dev->sem); + device_unlock(dev); return error; } @@ -936,3 +1046,14 @@ void __suspend_report_result(const char *function, void *fn, int ret) printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); } EXPORT_SYMBOL_GPL(__suspend_report_result); + +/** + * device_pm_wait_for_dev - Wait for suspend/resume of a device to complete. + * @dev: Device to wait for. + * @subordinate: Device that needs to wait for @dev. + */ +void device_pm_wait_for_dev(struct device *subordinate, struct device *dev) +{ + dpm_wait(dev, subordinate->power.async_suspend); +} +EXPORT_SYMBOL_GPL(device_pm_wait_for_dev); diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index b8fa1aa5225..c0bd03c83b9 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -12,10 +12,10 @@ static inline void pm_runtime_remove(struct device *dev) {} #ifdef CONFIG_PM_SLEEP -/* - * main.c - */ +/* kernel/power/main.c */ +extern int pm_async_enabled; +/* drivers/base/power/main.c */ extern struct list_head dpm_list; /* The active device list */ static inline struct device *to_device(struct list_head *entry) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index f8b044e8aef..626dd147b75 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1011,6 +1011,50 @@ void pm_runtime_enable(struct device *dev) EXPORT_SYMBOL_GPL(pm_runtime_enable); /** + * pm_runtime_forbid - Block run-time PM of a device. + * @dev: Device to handle. + * + * Increase the device's usage count and clear its power.runtime_auto flag, + * so that it cannot be suspended at run time until pm_runtime_allow() is called + * for it. + */ +void pm_runtime_forbid(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + if (!dev->power.runtime_auto) + goto out; + + dev->power.runtime_auto = false; + atomic_inc(&dev->power.usage_count); + __pm_runtime_resume(dev, false); + + out: + spin_unlock_irq(&dev->power.lock); +} +EXPORT_SYMBOL_GPL(pm_runtime_forbid); + +/** + * pm_runtime_allow - Unblock run-time PM of a device. + * @dev: Device to handle. + * + * Decrease the device's usage count and set its power.runtime_auto flag. + */ +void pm_runtime_allow(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + if (dev->power.runtime_auto) + goto out; + + dev->power.runtime_auto = true; + if (atomic_dec_and_test(&dev->power.usage_count)) + __pm_runtime_idle(dev); + + out: + spin_unlock_irq(&dev->power.lock); +} +EXPORT_SYMBOL_GPL(pm_runtime_allow); + +/** * pm_runtime_init - Initialize run-time PM fields in given device object. * @dev: Device object to initialize. */ @@ -1028,6 +1072,7 @@ void pm_runtime_init(struct device *dev) atomic_set(&dev->power.child_count, 0); pm_suspend_ignore_children(dev, false); + dev->power.runtime_auto = true; dev->power.request_pending = false; dev->power.request = RPM_REQ_NONE; diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 596aeecfdff..86fd9373447 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -4,9 +4,25 @@ #include <linux/device.h> #include <linux/string.h> +#include <linux/pm_runtime.h> #include "power.h" /* + * control - Report/change current runtime PM setting of the device + * + * Runtime power management of a device can be blocked with the help of + * this attribute. All devices have one of the following two values for + * the power/control file: + * + * + "auto\n" to allow the device to be power managed at run time; + * + "on\n" to prevent the device from being power managed at run time; + * + * The default for all devices is "auto", which means that devices may be + * subject to automatic power management, depending on their drivers. + * Changing this attribute to "on" prevents the driver from power managing + * the device at run time. Doing that while the device is suspended causes + * it to be woken up. + * * wakeup - Report/change current wakeup option for device * * Some devices support "wakeup" events, which are hardware signals @@ -38,11 +54,61 @@ * 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. + * + * async - Report/change current async suspend setting for the device + * + * Asynchronous suspend and resume of the device during system-wide power + * state transitions can be enabled by writing "enabled" to this file. + * Analogously, if "disabled" is written to this file, the device will be + * suspended and resumed synchronously. + * + * All devices have one of the following two values for power/async: + * + * + "enabled\n" to permit the asynchronous suspend/resume of the device; + * + "disabled\n" to forbid it; + * + * NOTE: It generally is unsafe to permit the asynchronous suspend/resume + * of a device unless it is certain that all of the PM dependencies of the + * device are known to the PM core. However, for some devices this + * attribute is set to "enabled" by bus type code or device drivers and in + * that cases it should be safe to leave the default value. */ static const char enabled[] = "enabled"; static const char disabled[] = "disabled"; +#ifdef CONFIG_PM_RUNTIME +static const char ctrl_auto[] = "auto"; +static const char ctrl_on[] = "on"; + +static ssize_t control_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", + dev->power.runtime_auto ? ctrl_auto : ctrl_on); +} + +static ssize_t control_store(struct device * dev, struct device_attribute *attr, + const char * buf, size_t n) +{ + char *cp; + int len = n; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof ctrl_auto - 1 && strncmp(buf, ctrl_auto, len) == 0) + pm_runtime_allow(dev); + else if (len == sizeof ctrl_on - 1 && strncmp(buf, ctrl_on, len) == 0) + pm_runtime_forbid(dev); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(control, 0644, control_show, control_store); +#endif + static ssize_t wake_show(struct device * dev, struct device_attribute *attr, char * buf) { @@ -77,9 +143,43 @@ wake_store(struct device * dev, struct device_attribute *attr, static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG +static ssize_t async_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", + device_async_suspend_enabled(dev) ? enabled : disabled); +} + +static ssize_t async_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + char *cp; + int len = n; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof enabled - 1 && strncmp(buf, enabled, len) == 0) + device_enable_async_suspend(dev); + else if (len == sizeof disabled - 1 && strncmp(buf, disabled, len) == 0) + device_disable_async_suspend(dev); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(async, 0644, async_show, async_store); +#endif /* CONFIG_PM_SLEEP_ADVANCED_DEBUG */ static struct attribute * power_attrs[] = { +#ifdef CONFIG_PM_RUNTIME + &dev_attr_control.attr, +#endif &dev_attr_wakeup.attr, +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG + &dev_attr_async.attr, +#endif NULL, }; static struct attribute_group pm_attr_group = { diff --git a/drivers/base/sys.c b/drivers/base/sys.c index 0d903909af7..8980feec5d1 100644 --- a/drivers/base/sys.c +++ b/drivers/base/sys.c @@ -54,7 +54,7 @@ sysdev_store(struct kobject *kobj, struct attribute *attr, return -EIO; } -static struct sysfs_ops sysfs_ops = { +static const struct sysfs_ops sysfs_ops = { .show = sysdev_show, .store = sysdev_store, }; @@ -89,7 +89,7 @@ static ssize_t sysdev_class_show(struct kobject *kobj, struct attribute *attr, struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr); if (class_attr->show) - return class_attr->show(class, buffer); + return class_attr->show(class, class_attr, buffer); return -EIO; } @@ -100,11 +100,11 @@ static ssize_t sysdev_class_store(struct kobject *kobj, struct attribute *attr, struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr); if (class_attr->store) - return class_attr->store(class, buffer, count); + return class_attr->store(class, class_attr, buffer, count); return -EIO; } -static struct sysfs_ops sysfs_class_ops = { +static const struct sysfs_ops sysfs_class_ops = { .show = sysdev_class_show, .store = sysdev_class_store, }; @@ -145,13 +145,20 @@ int sysdev_class_register(struct sysdev_class *cls) if (retval) return retval; - return kset_register(&cls->kset); + retval = kset_register(&cls->kset); + if (!retval && cls->attrs) + retval = sysfs_create_files(&cls->kset.kobj, + (const struct attribute **)cls->attrs); + return retval; } void sysdev_class_unregister(struct sysdev_class *cls) { pr_debug("Unregistering sysdev class '%s'\n", kobject_name(&cls->kset.kobj)); + if (cls->attrs) + sysfs_remove_files(&cls->kset.kobj, + (const struct attribute **)cls->attrs); kset_unregister(&cls->kset); } |