diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 2 | ||||
-rw-r--r-- | drivers/base/base.h | 2 | ||||
-rw-r--r-- | drivers/base/bus.c | 139 | ||||
-rw-r--r-- | drivers/base/class.c | 43 | ||||
-rw-r--r-- | drivers/base/core.c | 261 | ||||
-rw-r--r-- | drivers/base/cpu.c | 28 | ||||
-rw-r--r-- | drivers/base/dd.c | 147 | ||||
-rw-r--r-- | drivers/base/dmapool.c | 3 | ||||
-rw-r--r-- | drivers/base/driver.c | 17 | ||||
-rw-r--r-- | drivers/base/firmware_class.c | 14 | ||||
-rw-r--r-- | drivers/base/hypervisor.c | 3 | ||||
-rw-r--r-- | drivers/base/memory.c | 4 | ||||
-rw-r--r-- | drivers/base/node.c | 135 | ||||
-rw-r--r-- | drivers/base/platform.c | 30 | ||||
-rw-r--r-- | drivers/base/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/power/main.c | 1 | ||||
-rw-r--r-- | drivers/base/power/resume.c | 45 | ||||
-rw-r--r-- | drivers/base/power/shutdown.c | 1 | ||||
-rw-r--r-- | drivers/base/power/suspend.c | 99 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 35 | ||||
-rw-r--r-- | drivers/base/power/trace.c | 228 | ||||
-rw-r--r-- | drivers/base/sys.c | 1 | ||||
-rw-r--r-- | drivers/base/topology.c | 6 |
23 files changed, 960 insertions, 285 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 80502dc6ed6..0b4e2243693 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -20,7 +20,7 @@ config PREVENT_FIRMWARE_BUILD config FW_LOADER tristate "Userspace firmware loading support" - select HOTPLUG + depends on HOTPLUG ---help--- This option is provided for the case where no in-kernel-tree modules require userspace firmware loading support, but a module built outside diff --git a/drivers/base/base.h b/drivers/base/base.h index c3b8dc98b8a..d26644a5953 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -16,7 +16,7 @@ extern int cpu_dev_init(void); extern int attribute_container_init(void); extern int bus_add_device(struct device * dev); -extern void bus_attach_device(struct device * dev); +extern int bus_attach_device(struct device * dev); extern void bus_remove_device(struct device * dev); extern struct bus_type *get_bus(struct bus_type * bus); extern void put_bus(struct bus_type * bus); diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 050d86d0b87..12173d16bea 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -8,7 +8,6 @@ * */ -#include <linux/config.h> #include <linux/device.h> #include <linux/module.h> #include <linux/errno.h> @@ -130,7 +129,7 @@ static struct kobj_type ktype_bus = { }; -decl_subsys(bus, &ktype_bus, NULL); +static decl_subsys(bus, &ktype_bus, NULL); #ifdef CONFIG_HOTPLUG @@ -372,12 +371,20 @@ int bus_add_device(struct device * dev) if (bus) { pr_debug("bus %s: add device %s\n", bus->name, dev->bus_id); error = device_add_attrs(bus, dev); - if (!error) { - sysfs_create_link(&bus->devices.kobj, &dev->kobj, dev->bus_id); - sysfs_create_link(&dev->kobj, &dev->bus->subsys.kset.kobj, "subsystem"); - sysfs_create_link(&dev->kobj, &dev->bus->subsys.kset.kobj, "bus"); - } + if (error) + goto out; + error = sysfs_create_link(&bus->devices.kobj, + &dev->kobj, dev->bus_id); + if (error) + goto out; + error = sysfs_create_link(&dev->kobj, + &dev->bus->subsys.kset.kobj, "subsystem"); + if (error) + goto out; + error = sysfs_create_link(&dev->kobj, + &dev->bus->subsys.kset.kobj, "bus"); } +out: return error; } @@ -385,16 +392,24 @@ int bus_add_device(struct device * dev) * bus_attach_device - add device to bus * @dev: device tried to attach to a driver * + * - Add device to bus's list of devices. * - Try to attach to driver. */ -void bus_attach_device(struct device * dev) +int bus_attach_device(struct device * dev) { - struct bus_type * bus = dev->bus; + struct bus_type *bus = dev->bus; + int ret = 0; if (bus) { - device_attach(dev); - klist_add_tail(&dev->knode_bus, &bus->klist_devices); + dev->is_registered = 1; + ret = device_attach(dev); + if (ret >= 0) { + klist_add_tail(&dev->knode_bus, &bus->klist_devices); + ret = 0; + } else + dev->is_registered = 0; } + return ret; } /** @@ -413,7 +428,8 @@ void bus_remove_device(struct device * dev) sysfs_remove_link(&dev->kobj, "bus"); sysfs_remove_link(&dev->bus->devices.kobj, dev->bus_id); device_remove_attrs(dev->bus, dev); - klist_remove(&dev->knode_bus); + dev->is_registered = 0; + klist_del(&dev->knode_bus); pr_debug("bus %s: remove device %s\n", dev->bus->name, dev->bus_id); device_release_driver(dev); put_bus(dev->bus); @@ -456,10 +472,17 @@ static void driver_remove_attrs(struct bus_type * bus, struct device_driver * dr * Thanks to drivers making their tables __devinit, we can't allow manual * bind and unbind from userspace unless CONFIG_HOTPLUG is enabled. */ -static void add_bind_files(struct device_driver *drv) +static int __must_check add_bind_files(struct device_driver *drv) { - driver_create_file(drv, &driver_attr_unbind); - driver_create_file(drv, &driver_attr_bind); + int ret; + + ret = driver_create_file(drv, &driver_attr_unbind); + if (ret == 0) { + ret = driver_create_file(drv, &driver_attr_bind); + if (ret) + driver_remove_file(drv, &driver_attr_unbind); + } + return ret; } static void remove_bind_files(struct device_driver *drv) @@ -468,7 +491,7 @@ static void remove_bind_files(struct device_driver *drv) driver_remove_file(drv, &driver_attr_unbind); } #else -static inline void add_bind_files(struct device_driver *drv) {} +static inline int add_bind_files(struct device_driver *drv) { return 0; } static inline void remove_bind_files(struct device_driver *drv) {} #endif @@ -477,7 +500,7 @@ static inline void remove_bind_files(struct device_driver *drv) {} * @drv: driver. * */ -int bus_add_driver(struct device_driver * drv) +int bus_add_driver(struct device_driver *drv) { struct bus_type * bus = get_bus(drv->bus); int error = 0; @@ -485,27 +508,39 @@ int bus_add_driver(struct device_driver * drv) if (bus) { pr_debug("bus %s: add driver %s\n", bus->name, drv->name); error = kobject_set_name(&drv->kobj, "%s", drv->name); - if (error) { - put_bus(bus); - return error; - } + if (error) + goto out_put_bus; drv->kobj.kset = &bus->drivers; - if ((error = kobject_register(&drv->kobj))) { - put_bus(bus); - return error; - } + if ((error = kobject_register(&drv->kobj))) + goto out_put_bus; - driver_attach(drv); + error = driver_attach(drv); + if (error) + goto out_unregister; klist_add_tail(&drv->knode_bus, &bus->klist_drivers); module_add_driver(drv->owner, drv); - driver_add_attrs(bus, drv); - add_bind_files(drv); + error = driver_add_attrs(bus, drv); + if (error) { + /* How the hell do we get out of this pickle? Give up */ + printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n", + __FUNCTION__, drv->name); + } + error = add_bind_files(drv); + if (error) { + /* Ditto */ + printk(KERN_ERR "%s: add_bind_files(%s) failed\n", + __FUNCTION__, drv->name); + } } return error; +out_unregister: + kobject_unregister(&drv->kobj); +out_put_bus: + put_bus(bus); + return error; } - /** * bus_remove_driver - delete driver from bus's knowledge. * @drv: driver. @@ -531,16 +566,21 @@ void bus_remove_driver(struct device_driver * drv) /* Helper for bus_rescan_devices's iter */ -static int bus_rescan_devices_helper(struct device *dev, void *data) +static int __must_check bus_rescan_devices_helper(struct device *dev, + void *data) { + int ret = 0; + if (!dev->driver) { if (dev->parent) /* Needed for USB */ down(&dev->parent->sem); - device_attach(dev); + ret = device_attach(dev); if (dev->parent) up(&dev->parent->sem); + if (ret > 0) + ret = 0; } - return 0; + return ret < 0 ? ret : 0; } /** @@ -551,9 +591,9 @@ static int bus_rescan_devices_helper(struct device *dev, void *data) * attached and rescan it against existing drivers to see if it matches * any by calling device_attach() for the unbound devices. */ -void bus_rescan_devices(struct bus_type * bus) +int bus_rescan_devices(struct bus_type * bus) { - bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper); + return bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper); } /** @@ -565,7 +605,7 @@ void bus_rescan_devices(struct bus_type * bus) * to use if probing criteria changed during a devices lifetime and * driver attachment should change accordingly. */ -void device_reprobe(struct device *dev) +int device_reprobe(struct device *dev) { if (dev->driver) { if (dev->parent) /* Needed for USB */ @@ -574,14 +614,14 @@ void device_reprobe(struct device *dev) if (dev->parent) up(&dev->parent->sem); } - - bus_rescan_devices_helper(dev, NULL); + return bus_rescan_devices_helper(dev, NULL); } EXPORT_SYMBOL_GPL(device_reprobe); -struct bus_type * get_bus(struct bus_type * bus) +struct bus_type *get_bus(struct bus_type *bus) { - return bus ? container_of(subsys_get(&bus->subsys), struct bus_type, subsys) : NULL; + return bus ? container_of(subsys_get(&bus->subsys), + struct bus_type, subsys) : NULL; } void put_bus(struct bus_type * bus) @@ -599,12 +639,13 @@ void put_bus(struct bus_type * bus) * * Note that kset_find_obj increments bus' reference count. */ - +#if 0 struct bus_type * find_bus(char * name) { struct kobject * k = kset_find_obj(&bus_subsys.kset, name); return k ? to_bus(k) : NULL; } +#endif /* 0 */ /** @@ -655,22 +696,6 @@ static void klist_devices_put(struct klist_node *n) put_device(dev); } -static void klist_drivers_get(struct klist_node *n) -{ - struct device_driver *drv = container_of(n, struct device_driver, - knode_bus); - - get_driver(drv); -} - -static void klist_drivers_put(struct klist_node *n) -{ - struct device_driver *drv = container_of(n, struct device_driver, - knode_bus); - - put_driver(drv); -} - /** * bus_register - register a bus with the system. * @bus: bus. @@ -706,7 +731,7 @@ int bus_register(struct bus_type * bus) goto bus_drivers_fail; klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put); - klist_init(&bus->klist_drivers, klist_drivers_get, klist_drivers_put); + klist_init(&bus->klist_drivers, NULL, NULL); bus_add_attrs(bus); pr_debug("bus type '%s' registered\n", bus->name); diff --git a/drivers/base/class.c b/drivers/base/class.c index 9aa12746026..b32b77ff2dc 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -10,7 +10,6 @@ * */ -#include <linux/config.h> #include <linux/device.h> #include <linux/module.h> #include <linux/init.h> @@ -20,6 +19,8 @@ #include <linux/slab.h> #include "base.h" +extern struct subsystem devices_subsys; + #define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr) #define to_class(obj) container_of(obj, struct class, subsys.kset.kobj) @@ -198,7 +199,7 @@ static int class_device_create_uevent(struct class_device *class_dev, * Note, the pointer created here is to be destroyed when finished by * making a call to class_destroy(). */ -struct class *class_create(struct module *owner, char *name) +struct class *class_create(struct module *owner, const char *name) { struct class *cls; int retval; @@ -227,7 +228,7 @@ error: /** * class_destroy - destroys a struct class structure - * @cs: pointer to the struct class that is to be destroyed + * @cls: pointer to the struct class that is to be destroyed * * Note, the pointer to be destroyed must have been created with a call * to class_create(). @@ -362,7 +363,7 @@ static int class_uevent(struct kset *kset, struct kobject *kobj, char **envp, pr_debug("%s - name = %s\n", __FUNCTION__, class_dev->class_id); if (class_dev->dev) { - /* add physical device, backing this device */ + /* add device, backing this class device (deprecated) */ struct device *dev = class_dev->dev; char *path = kobject_get_path(&dev->kobj, GFP_KERNEL); @@ -657,9 +658,9 @@ int class_device_register(struct class_device *class_dev) /** * class_device_create - creates a class device and registers it with sysfs - * @cs: pointer to the struct class that this device should be registered to. + * @cls: pointer to the struct class that this device should be registered to. * @parent: pointer to the parent struct class_device of this new device, if any. - * @dev: the dev_t for the char device to be added. + * @devt: the dev_t for the char device to be added. * @device: a pointer to a struct device that is assiociated with this class device. * @fmt: string for the class device's name * @@ -680,7 +681,8 @@ int class_device_register(struct class_device *class_dev) struct class_device *class_device_create(struct class *cls, struct class_device *parent, dev_t devt, - struct device *device, char *fmt, ...) + struct device *device, + const char *fmt, ...) { va_list args; struct class_device *class_dev = NULL; @@ -764,7 +766,7 @@ void class_device_unregister(struct class_device *class_dev) /** * class_device_destroy - removes a class device that was created with class_device_create() * @cls: the pointer to the struct class that this device was registered * with. - * @dev: the dev_t of the device that was previously registered. + * @devt: the dev_t of the device that was previously registered. * * This call unregisters and cleans up a class device that was created with a * call to class_device_create() @@ -840,6 +842,7 @@ int class_interface_register(struct class_interface *class_intf) { struct class *parent; struct class_device *class_dev; + struct device *dev; if (!class_intf || !class_intf->class) return -ENODEV; @@ -854,6 +857,10 @@ int class_interface_register(struct class_interface *class_intf) list_for_each_entry(class_dev, &parent->children, node) class_intf->add(class_dev, class_intf); } + if (class_intf->add_dev) { + list_for_each_entry(dev, &parent->devices, node) + class_intf->add_dev(dev, class_intf); + } up(&parent->sem); return 0; @@ -863,6 +870,7 @@ void class_interface_unregister(struct class_interface *class_intf) { struct class * parent = class_intf->class; struct class_device *class_dev; + struct device *dev; if (!parent) return; @@ -873,12 +881,31 @@ void class_interface_unregister(struct class_interface *class_intf) list_for_each_entry(class_dev, &parent->children, node) class_intf->remove(class_dev, class_intf); } + if (class_intf->remove_dev) { + list_for_each_entry(dev, &parent->devices, node) + class_intf->remove_dev(dev, class_intf); + } up(&parent->sem); class_put(parent); } +int virtual_device_parent(struct device *dev) +{ + if (!dev->class) + return -ENODEV; + + if (!dev->class->virtual_dir) { + static struct kobject *virtual_dir = NULL; + + if (!virtual_dir) + virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual"); + dev->class->virtual_dir = kobject_add_dir(virtual_dir, dev->class->name); + } + dev->kobj.parent = dev->class->virtual_dir; + return 0; +} int __init classes_init(void) { diff --git a/drivers/base/core.c b/drivers/base/core.c index 27c2176895d..b224bb43ff6 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -3,12 +3,13 @@ * * Copyright (c) 2002-3 Patrick Mochel * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2006 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2006 Novell, Inc. * * This file is released under the GPLv2 * */ -#include <linux/config.h> #include <linux/device.h> #include <linux/err.h> #include <linux/init.h> @@ -93,6 +94,8 @@ static void device_release(struct kobject * kobj) if (dev->release) dev->release(dev); + else if (dev->class && dev->class->dev_release) + dev->class->dev_release(dev); else { printk(KERN_ERR "Device '%s' does not have a release() function, " "it is broken and must be fixed.\n", @@ -150,17 +153,21 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp, "MINOR=%u", MINOR(dev->devt)); } - /* add bus name of physical device */ + /* 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); - /* add driver name of physical device */ - if (dev->driver) + /* add driver name (PHYSDEV* values are deprecated)*/ + if (dev->driver) { + add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "DRIVER=%s", dev->driver->name); add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, "PHYSDEVDRIVER=%s", dev->driver->name); + } /* terminate, set to next free slot, shrink available space */ envp[i] = NULL; @@ -178,6 +185,15 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp, } } + 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); + } + } + return retval; } @@ -194,6 +210,72 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, return count; } +static int device_add_groups(struct device *dev) +{ + int i; + int error = 0; + + if (dev->groups) { + for (i = 0; dev->groups[i]; i++) { + error = sysfs_create_group(&dev->kobj, dev->groups[i]); + if (error) { + while (--i >= 0) + sysfs_remove_group(&dev->kobj, dev->groups[i]); + goto out; + } + } + } +out: + return error; +} + +static void device_remove_groups(struct device *dev) +{ + int i; + if (dev->groups) { + for (i = 0; dev->groups[i]; i++) { + sysfs_remove_group(&dev->kobj, dev->groups[i]); + } + } +} + +static int device_add_attrs(struct device *dev) +{ + struct class *class = dev->class; + int error = 0; + int i; + + if (!class) + return 0; + + if (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]); + return error; +} + +static void device_remove_attrs(struct device *dev) +{ + struct class *class = dev->class; + int i; + + if (!class) + return; + + if (class->dev_attrs) { + for (i = 0; attr_name(class->dev_attrs[i]); i++) + device_remove_file(dev, &class->dev_attrs[i]); + } +} + + static ssize_t show_dev(struct device *dev, struct device_attribute *attr, char *buf) { @@ -237,6 +319,32 @@ void device_remove_file(struct device * dev, struct device_attribute * attr) } } +/** + * device_create_bin_file - create sysfs binary attribute file for device. + * @dev: device. + * @attr: device binary attribute descriptor. + */ +int device_create_bin_file(struct device *dev, struct bin_attribute *attr) +{ + int error = -EINVAL; + if (dev) + error = sysfs_create_bin_file(&dev->kobj, attr); + return error; +} +EXPORT_SYMBOL_GPL(device_create_bin_file); + +/** + * device_remove_bin_file - remove sysfs binary attribute file + * @dev: device. + * @attr: device binary attribute descriptor. + */ +void device_remove_bin_file(struct device *dev, struct bin_attribute *attr) +{ + if (dev) + sysfs_remove_bin_file(&dev->kobj, attr); +} +EXPORT_SYMBOL_GPL(device_remove_bin_file); + static void klist_children_get(struct klist_node *n) { struct device *dev = container_of(n, struct device, knode_parent); @@ -290,12 +398,20 @@ int device_add(struct device *dev) { struct device *parent = NULL; char *class_name = NULL; + struct class_interface *class_intf; int error = -EINVAL; dev = get_device(dev); if (!dev || !strlen(dev->bus_id)) goto Error; + /* if this is a class device, and has no parent, create one */ + if ((dev->class) && (dev->parent == NULL)) { + error = virtual_device_parent(dev); + if (error) + goto Error; + } + parent = get_device(dev->parent); pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); @@ -308,6 +424,10 @@ int device_add(struct device *dev) if ((error = kobject_add(&dev->kobj))) goto Error; + /* notify platform of device entry */ + if (platform_notify) + platform_notify(dev); + dev->uevent_attr.attr.name = "uevent"; dev->uevent_attr.attr.mode = S_IWUSR; if (dev->driver) @@ -341,12 +461,17 @@ int device_add(struct device *dev) "subsystem"); sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj, dev->bus_id); - - 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); + 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); + } } + if ((error = device_add_attrs(dev))) + goto AttrsError; + if ((error = device_add_groups(dev))) + goto GroupError; if ((error = device_pm_add(dev))) goto PMError; if ((error = bus_add_device(dev))) @@ -357,15 +482,16 @@ int device_add(struct device *dev) klist_add_tail(&dev->knode_parent, &parent->klist_children); if (dev->class) { - /* tie the class to the device */ down(&dev->class->sem); + /* tie the class to the device */ list_add_tail(&dev->node, &dev->class->devices); + + /* notify any interfaces that the device is here */ + list_for_each_entry(class_intf, &dev->class->interfaces, node) + if (class_intf->add_dev) + class_intf->add_dev(dev, class_intf); up(&dev->class->sem); } - - /* notify platform of device entry */ - if (platform_notify) - platform_notify(dev); Done: kfree(class_name); put_device(dev); @@ -373,6 +499,10 @@ int device_add(struct device *dev) BusError: device_pm_remove(dev); PMError: + device_remove_groups(dev); + GroupError: + device_remove_attrs(dev); + AttrsError: if (dev->devt_attr) { device_remove_file(dev, dev->devt_attr); kfree(dev->devt_attr); @@ -450,6 +580,7 @@ void device_del(struct device * dev) { struct device * parent = dev->parent; char *class_name = NULL; + struct class_interface *class_intf; if (parent) klist_del(&dev->knode_parent); @@ -459,14 +590,23 @@ void device_del(struct device * dev) sysfs_remove_link(&dev->kobj, "subsystem"); sysfs_remove_link(&dev->class->subsys.kset.kobj, dev->bus_id); class_name = make_class_name(dev->class->name, &dev->kobj); - sysfs_remove_link(&dev->kobj, "device"); - sysfs_remove_link(&dev->parent->kobj, class_name); + if (parent) { + sysfs_remove_link(&dev->kobj, "device"); + sysfs_remove_link(&dev->parent->kobj, class_name); + } kfree(class_name); down(&dev->class->sem); + /* notify any interfaces that the device is now gone */ + list_for_each_entry(class_intf, &dev->class->interfaces, node) + if (class_intf->remove_dev) + class_intf->remove_dev(dev, class_intf); + /* remove the device from the class list */ list_del_init(&dev->node); up(&dev->class->sem); } device_remove_file(dev, &dev->uevent_attr); + device_remove_groups(dev); + device_remove_attrs(dev); /* Notify the platform of the removal, in case they * need to do anything... @@ -560,27 +700,27 @@ static void device_create_release(struct device *dev) /** * device_create - creates a device and registers it with sysfs - * @cs: pointer to the struct class that this device should be registered to. - * @parent: pointer to the parent struct device of this new device, if any. - * @dev: the dev_t for the char device to be added. - * @fmt: string for the class device's name + * @class: pointer to the struct class that this device should be registered to + * @parent: pointer to the parent struct device of this new device, if any + * @devt: the dev_t for the char device to be added + * @fmt: string for the device's name + * + * This function can be used by char device classes. A struct device + * will be created in sysfs, registered to the specified class. * - * This function can be used by char device classes. A struct - * device will be created in sysfs, registered to the specified - * class. * A "dev" file will be created, showing the dev_t for the device, if * the dev_t is not 0,0. - * If a pointer to a parent struct device is passed in, the newly - * created struct device will be a child of that device in sysfs. The - * pointer to the struct device will be returned from the call. Any - * further sysfs files that might be required can be created using this + * If a pointer to a parent struct device is passed in, the newly created + * struct device will be a child of that device in sysfs. + * The pointer to the struct device will be returned from the call. + * Any further sysfs files that might be required can be created using this * pointer. * * Note: the struct class passed to this function must have previously * been created with a call to class_create(). */ struct device *device_create(struct class *class, struct device *parent, - dev_t devt, char *fmt, ...) + dev_t devt, const char *fmt, ...) { va_list args; struct device *dev = NULL; @@ -588,10 +728,6 @@ struct device *device_create(struct class *class, struct device *parent, if (class == NULL || IS_ERR(class)) goto error; - if (parent == NULL) { - printk(KERN_WARNING "%s does not work yet for NULL parents\n", __FUNCTION__); - goto error; - } dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { @@ -621,11 +757,11 @@ EXPORT_SYMBOL_GPL(device_create); /** * device_destroy - removes a device that was created with device_create() - * @class: the pointer to the struct class that this device was registered * with. - * @dev: the dev_t of the device that was previously registered. + * @class: pointer to the struct class that this device was registered with + * @devt: the dev_t of the device that was previously registered * - * This call unregisters and cleans up a class device that was created with a - * call to class_device_create() + * This call unregisters and cleans up a device that was created with a + * call to device_create(). */ void device_destroy(struct class *class, dev_t devt) { @@ -645,3 +781,58 @@ void device_destroy(struct class *class, dev_t devt) device_unregister(dev); } EXPORT_SYMBOL_GPL(device_destroy); + +/** + * device_rename - renames a device + * @dev: the pointer to the struct device to be renamed + * @new_name: the new name of the device + */ +int device_rename(struct device *dev, char *new_name) +{ + char *old_class_name = NULL; + char *new_class_name = NULL; + char *old_symlink_name = NULL; + int error; + + dev = get_device(dev); + if (!dev) + return -EINVAL; + + pr_debug("DEVICE: renaming '%s' to '%s'\n", dev->bus_id, new_name); + + if ((dev->class) && (dev->parent)) + old_class_name = make_class_name(dev->class->name, &dev->kobj); + + if (dev->class) { + old_symlink_name = kmalloc(BUS_ID_SIZE, GFP_KERNEL); + if (!old_symlink_name) + return -ENOMEM; + strlcpy(old_symlink_name, dev->bus_id, BUS_ID_SIZE); + } + + strlcpy(dev->bus_id, new_name, BUS_ID_SIZE); + + error = kobject_rename(&dev->kobj, new_name); + + if (old_class_name) { + new_class_name = make_class_name(dev->class->name, &dev->kobj); + if (new_class_name) { + sysfs_create_link(&dev->parent->kobj, &dev->kobj, + new_class_name); + sysfs_remove_link(&dev->parent->kobj, old_class_name); + } + } + if (dev->class) { + sysfs_remove_link(&dev->class->subsys.kset.kobj, + old_symlink_name); + sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj, + dev->bus_id); + } + put_device(dev); + + kfree(old_class_name); + kfree(new_class_name); + kfree(old_symlink_name); + + return error; +} diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index dd712b24ec9..4bef76a2f3f 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -8,6 +8,7 @@ #include <linux/cpu.h> #include <linux/topology.h> #include <linux/device.h> +#include <linux/node.h> #include "base.h" @@ -57,13 +58,12 @@ static void __devinit register_cpu_control(struct cpu *cpu) { sysdev_create_file(&cpu->sysdev, &attr_online); } -void unregister_cpu(struct cpu *cpu, struct node *root) +void unregister_cpu(struct cpu *cpu) { int logical_cpu = cpu->sysdev.id; - if (root) - sysfs_remove_link(&root->sysdev.kobj, - kobject_name(&cpu->sysdev.kobj)); + unregister_cpu_under_node(logical_cpu, cpu_to_node(logical_cpu)); + sysdev_remove_file(&cpu->sysdev, &attr_online); sysdev_unregister(&cpu->sysdev); @@ -109,23 +109,21 @@ static SYSDEV_ATTR(crash_notes, 0400, show_crash_notes, NULL); * * Initialize and register the CPU device. */ -int __devinit register_cpu(struct cpu *cpu, int num, struct node *root) +int __devinit register_cpu(struct cpu *cpu, int num) { int error; - cpu->node_id = cpu_to_node(num); cpu->sysdev.id = num; cpu->sysdev.cls = &cpu_sysdev_class; error = sysdev_register(&cpu->sysdev); - if (!error && root) - error = sysfs_create_link(&root->sysdev.kobj, - &cpu->sysdev.kobj, - kobject_name(&cpu->sysdev.kobj)); + if (!error && !cpu->no_control) register_cpu_control(cpu); if (!error) cpu_sys_devices[num] = &cpu->sysdev; + if (!error) + register_cpu_under_node(num, cpu_to_node(num)); #ifdef CONFIG_KEXEC if (!error) @@ -145,5 +143,13 @@ EXPORT_SYMBOL_GPL(get_cpu_sysdev); int __init cpu_dev_init(void) { - return sysdev_class_register(&cpu_sysdev_class); + int err; + + err = sysdev_class_register(&cpu_sysdev_class); +#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT) + if (!err) + err = sched_create_sysfs_power_savings_entries(&cpu_sysdev_class); +#endif + + return err; } diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 889c7111123..b5f43c3e44f 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -17,6 +17,7 @@ #include <linux/device.h> #include <linux/module.h> +#include <linux/kthread.h> #include "base.h" #include "power/power.h" @@ -38,66 +39,73 @@ * * This function must be called with @dev->sem held. */ -void device_bind_driver(struct device * dev) +int device_bind_driver(struct device *dev) { - if (klist_node_attached(&dev->knode_driver)) - return; + int ret; + + if (klist_node_attached(&dev->knode_driver)) { + printk(KERN_WARNING "%s: device %s already bound\n", + __FUNCTION__, kobject_name(&dev->kobj)); + return 0; + } pr_debug("bound device '%s' to driver '%s'\n", dev->bus_id, dev->driver->name); klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices); - sysfs_create_link(&dev->driver->kobj, &dev->kobj, + ret = sysfs_create_link(&dev->driver->kobj, &dev->kobj, kobject_name(&dev->kobj)); - sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver"); + if (ret == 0) { + ret = sysfs_create_link(&dev->kobj, &dev->driver->kobj, + "driver"); + if (ret) + sysfs_remove_link(&dev->driver->kobj, + kobject_name(&dev->kobj)); + } + return ret; } -/** - * driver_probe_device - attempt to bind device & driver. - * @drv: driver. - * @dev: device. - * - * First, we call the bus's match function, if one present, which - * should compare the device IDs the driver supports with the - * device IDs of the device. Note we don't do this ourselves - * because we don't know the format of the ID structures, nor what - * is to be considered a match and what is not. - * - * This function returns 1 if a match is found, an error if one - * occurs (that is not -ENODEV or -ENXIO), and 0 otherwise. - * - * This function must be called with @dev->sem held. When called - * for a USB interface, @dev->parent->sem must be held as well. - */ -int driver_probe_device(struct device_driver * drv, struct device * dev) +struct stupid_thread_structure { + struct device_driver *drv; + struct device *dev; +}; + +static atomic_t probe_count = ATOMIC_INIT(0); +static int really_probe(void *void_data) { + struct stupid_thread_structure *data = void_data; + struct device_driver *drv = data->drv; + struct device *dev = data->dev; int ret = 0; - if (drv->bus->match && !drv->bus->match(dev, drv)) - goto Done; + atomic_inc(&probe_count); + pr_debug("%s: Probing driver %s with device %s\n", + drv->bus->name, drv->name, dev->bus_id); - pr_debug("%s: Matched Device %s with Driver %s\n", - drv->bus->name, dev->bus_id, drv->name); dev->driver = drv; if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) { dev->driver = NULL; - goto ProbeFailed; + goto probe_failed; } } else if (drv->probe) { ret = drv->probe(dev); if (ret) { dev->driver = NULL; - goto ProbeFailed; + goto probe_failed; } } - device_bind_driver(dev); + if (device_bind_driver(dev)) { + printk(KERN_ERR "%s: device_bind_driver(%s) failed\n", + __FUNCTION__, dev->bus_id); + /* How does undo a ->probe? We're screwed. */ + } ret = 1; pr_debug("%s: Bound Device %s to Driver %s\n", drv->bus->name, dev->bus_id, drv->name); - goto Done; + goto done; - ProbeFailed: +probe_failed: if (ret == -ENODEV || ret == -ENXIO) { /* Driver matched, but didn't support device * or device not found. @@ -110,7 +118,71 @@ int driver_probe_device(struct device_driver * drv, struct device * dev) "%s: probe of %s failed with error %d\n", drv->name, dev->bus_id, ret); } - Done: +done: + kfree(data); + atomic_dec(&probe_count); + return ret; +} + +/** + * driver_probe_done + * Determine if the probe sequence is finished or not. + * + * Should somehow figure out how to use a semaphore, not an atomic variable... + */ +int driver_probe_done(void) +{ + pr_debug("%s: probe_count = %d\n", __FUNCTION__, + atomic_read(&probe_count)); + if (atomic_read(&probe_count)) + return -EBUSY; + return 0; +} + +/** + * driver_probe_device - attempt to bind device & driver together + * @drv: driver to bind a device to + * @dev: device to try to bind to the driver + * + * First, we call the bus's match function, if one present, which should + * compare the device IDs the driver supports with the device IDs of the + * device. Note we don't do this ourselves because we don't know the + * format of the ID structures, nor what is to be considered a match and + * what is not. + * + * This function returns 1 if a match is found, an error if one occurs + * (that is not -ENODEV or -ENXIO), and 0 otherwise. + * + * This function must be called with @dev->sem held. When called for a + * USB interface, @dev->parent->sem must be held as well. + */ +int driver_probe_device(struct device_driver * drv, struct device * dev) +{ + struct stupid_thread_structure *data; + struct task_struct *probe_task; + int ret = 0; + + if (!device_is_registered(dev)) + return -ENODEV; + if (drv->bus->match && !drv->bus->match(dev, drv)) + goto done; + + pr_debug("%s: Matched Device %s with Driver %s\n", + drv->bus->name, dev->bus_id, drv->name); + + data = kmalloc(sizeof(*data), GFP_KERNEL); + data->drv = drv; + data->dev = dev; + + if (drv->multithread_probe) { + probe_task = kthread_run(really_probe, data, + "probe-%s", dev->bus_id); + if (IS_ERR(probe_task)) + ret = PTR_ERR(probe_task); + } else + ret = really_probe(data); + +done: return ret; } @@ -139,8 +211,9 @@ int device_attach(struct device * dev) down(&dev->sem); if (dev->driver) { - device_bind_driver(dev); - ret = 1; + ret = device_bind_driver(dev); + if (ret == 0) + ret = 1; } else ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); up(&dev->sem); @@ -182,9 +255,9 @@ static int __driver_attach(struct device * dev, void * data) * returns 0 and the @dev->driver is set, we've found a * compatible pair. */ -void driver_attach(struct device_driver * drv) +int driver_attach(struct device_driver * drv) { - bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); + return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } /** diff --git a/drivers/base/dmapool.c b/drivers/base/dmapool.c index e2f64f91ed0..33c5cce1560 100644 --- a/drivers/base/dmapool.c +++ b/drivers/base/dmapool.c @@ -7,6 +7,7 @@ #include <linux/dmapool.h> #include <linux/slab.h> #include <linux/module.h> +#include <linux/poison.h> /* * Pool allocator ... wraps the dma_alloc_coherent page allocator, so @@ -35,8 +36,6 @@ struct dma_page { /* cacheable header for 'allocation' bytes */ }; #define POOL_TIMEOUT_JIFFIES ((100 /* msec */ * HZ) / 1000) -#define POOL_POISON_FREED 0xa7 /* !inuse */ -#define POOL_POISON_ALLOCATED 0xa9 /* !initted */ static DECLARE_MUTEX (pools_lock); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index b400314e1c6..1214cbd17d8 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -8,7 +8,6 @@ * */ -#include <linux/config.h> #include <linux/device.h> #include <linux/module.h> #include <linux/errno.h> @@ -143,20 +142,6 @@ void put_driver(struct device_driver * drv) kobject_put(&drv->kobj); } -static void klist_devices_get(struct klist_node *n) -{ - struct device *dev = container_of(n, struct device, knode_driver); - - get_device(dev); -} - -static void klist_devices_put(struct klist_node *n) -{ - struct device *dev = container_of(n, struct device, knode_driver); - - put_device(dev); -} - /** * driver_register - register driver with bus * @drv: driver to register @@ -176,7 +161,7 @@ int driver_register(struct device_driver * drv) (drv->bus->shutdown && drv->shutdown)) { printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name); } - klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put); + klist_init(&drv->klist_devices, NULL, NULL); init_completion(&drv->unloaded); return bus_add_driver(drv); } diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5d6c011183f..14615694ae9 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -16,6 +16,7 @@ #include <linux/interrupt.h> #include <linux/bitops.h> #include <linux/mutex.h> +#include <linux/kthread.h> #include <linux/firmware.h> #include "base.h" @@ -511,7 +512,6 @@ request_firmware_work_func(void *arg) WARN_ON(1); return 0; } - daemonize("%s/%s", "firmware", fw_work->name); ret = _request_firmware(&fw, fw_work->name, fw_work->device, fw_work->uevent); if (ret < 0) @@ -546,9 +546,9 @@ request_firmware_nowait( const char *name, struct device *device, void *context, void (*cont)(const struct firmware *fw, void *context)) { + struct task_struct *task; struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work), GFP_ATOMIC); - int ret; if (!fw_work) return -ENOMEM; @@ -566,14 +566,14 @@ request_firmware_nowait( .uevent = uevent, }; - ret = kernel_thread(request_firmware_work_func, fw_work, - CLONE_FS | CLONE_FILES); + task = kthread_run(request_firmware_work_func, fw_work, + "firmware/%s", name); - if (ret < 0) { + if (IS_ERR(task)) { fw_work->cont(NULL, fw_work->context); module_put(fw_work->module); kfree(fw_work); - return ret; + return PTR_ERR(task); } return 0; } @@ -602,7 +602,7 @@ firmware_class_exit(void) class_unregister(&firmware_class); } -module_init(firmware_class_init); +fs_initcall(firmware_class_init); module_exit(firmware_class_exit); EXPORT_SYMBOL(release_firmware); diff --git a/drivers/base/hypervisor.c b/drivers/base/hypervisor.c index 0c85e9d6a44..7080b413ddc 100644 --- a/drivers/base/hypervisor.c +++ b/drivers/base/hypervisor.c @@ -1,8 +1,9 @@ /* * hypervisor.c - /sys/hypervisor subsystem. * - * This file is released under the GPLv2 + * Copyright (C) IBM Corp. 2006 * + * This file is released under the GPLv2 */ #include <linux/kobject.h> diff --git a/drivers/base/memory.c b/drivers/base/memory.c index dd547af4681..c6b7d9c4b65 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -306,11 +306,13 @@ static ssize_t memory_probe_store(struct class *class, const char *buf, size_t count) { u64 phys_addr; + int nid; int ret; phys_addr = simple_strtoull(buf, NULL, 0); - ret = add_memory(phys_addr, PAGES_PER_SECTION << PAGE_SHIFT); + nid = memory_add_physaddr_to_nid(phys_addr); + ret = add_memory(nid, phys_addr, PAGES_PER_SECTION << PAGE_SHIFT); if (ret) count = ret; diff --git a/drivers/base/node.c b/drivers/base/node.c index c80c3aeed00..001e6f6b9c1 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -11,6 +11,7 @@ #include <linux/cpumask.h> #include <linux/topology.h> #include <linux/nodemask.h> +#include <linux/cpu.h> static struct sysdev_class node_class = { set_kset_name("node"), @@ -39,24 +40,13 @@ static ssize_t node_read_meminfo(struct sys_device * dev, char * buf) int n; int nid = dev->id; struct sysinfo i; - struct page_state ps; unsigned long inactive; unsigned long active; unsigned long free; si_meminfo_node(&i, nid); - get_page_state_node(&ps, nid); __get_zone_counts(&active, &inactive, &free, NODE_DATA(nid)); - /* Check for negative values in these approximate counters */ - if ((long)ps.nr_dirty < 0) - ps.nr_dirty = 0; - if ((long)ps.nr_writeback < 0) - ps.nr_writeback = 0; - if ((long)ps.nr_mapped < 0) - ps.nr_mapped = 0; - if ((long)ps.nr_slab < 0) - ps.nr_slab = 0; n = sprintf(buf, "\n" "Node %d MemTotal: %8lu kB\n" @@ -64,27 +54,46 @@ static ssize_t node_read_meminfo(struct sys_device * dev, char * buf) "Node %d MemUsed: %8lu kB\n" "Node %d Active: %8lu kB\n" "Node %d Inactive: %8lu kB\n" +#ifdef CONFIG_HIGHMEM "Node %d HighTotal: %8lu kB\n" "Node %d HighFree: %8lu kB\n" "Node %d LowTotal: %8lu kB\n" "Node %d LowFree: %8lu kB\n" +#endif "Node %d Dirty: %8lu kB\n" "Node %d Writeback: %8lu kB\n" + "Node %d FilePages: %8lu kB\n" "Node %d Mapped: %8lu kB\n" - "Node %d Slab: %8lu kB\n", + "Node %d AnonPages: %8lu kB\n" + "Node %d PageTables: %8lu kB\n" + "Node %d NFS_Unstable: %8lu kB\n" + "Node %d Bounce: %8lu kB\n" + "Node %d Slab: %8lu kB\n" + "Node %d SReclaimable: %8lu kB\n" + "Node %d SUnreclaim: %8lu kB\n", nid, K(i.totalram), nid, K(i.freeram), nid, K(i.totalram - i.freeram), nid, K(active), nid, K(inactive), +#ifdef CONFIG_HIGHMEM nid, K(i.totalhigh), nid, K(i.freehigh), nid, K(i.totalram - i.totalhigh), nid, K(i.freeram - i.freehigh), - nid, K(ps.nr_dirty), - nid, K(ps.nr_writeback), - nid, K(ps.nr_mapped), - nid, K(ps.nr_slab)); +#endif + nid, K(node_page_state(nid, NR_FILE_DIRTY)), + nid, K(node_page_state(nid, NR_WRITEBACK)), + nid, K(node_page_state(nid, NR_FILE_PAGES)), + nid, K(node_page_state(nid, NR_FILE_MAPPED)), + nid, K(node_page_state(nid, NR_ANON_PAGES)), + nid, K(node_page_state(nid, NR_PAGETABLE)), + nid, K(node_page_state(nid, NR_UNSTABLE_NFS)), + nid, K(node_page_state(nid, NR_BOUNCE)), + nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE) + + node_page_state(nid, NR_SLAB_UNRECLAIMABLE)), + nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE)), + nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE))); n += hugetlb_report_node_meminfo(nid, buf + n); return n; } @@ -94,28 +103,6 @@ static SYSDEV_ATTR(meminfo, S_IRUGO, node_read_meminfo, NULL); static ssize_t node_read_numastat(struct sys_device * dev, char * buf) { - unsigned long numa_hit, numa_miss, interleave_hit, numa_foreign; - unsigned long local_node, other_node; - int i, cpu; - pg_data_t *pg = NODE_DATA(dev->id); - numa_hit = 0; - numa_miss = 0; - interleave_hit = 0; - numa_foreign = 0; - local_node = 0; - other_node = 0; - for (i = 0; i < MAX_NR_ZONES; i++) { - struct zone *z = &pg->node_zones[i]; - for_each_online_cpu(cpu) { - struct per_cpu_pageset *ps = zone_pcp(z,cpu); - numa_hit += ps->numa_hit; - numa_miss += ps->numa_miss; - numa_foreign += ps->numa_foreign; - interleave_hit += ps->interleave_hit; - local_node += ps->local_node; - other_node += ps->other_node; - } - } return sprintf(buf, "numa_hit %lu\n" "numa_miss %lu\n" @@ -123,12 +110,12 @@ static ssize_t node_read_numastat(struct sys_device * dev, char * buf) "interleave_hit %lu\n" "local_node %lu\n" "other_node %lu\n", - numa_hit, - numa_miss, - numa_foreign, - interleave_hit, - local_node, - other_node); + node_page_state(dev->id, NUMA_HIT), + node_page_state(dev->id, NUMA_MISS), + node_page_state(dev->id, NUMA_FOREIGN), + node_page_state(dev->id, NUMA_INTERLEAVE_HIT), + node_page_state(dev->id, NUMA_LOCAL), + node_page_state(dev->id, NUMA_OTHER)); } static SYSDEV_ATTR(numastat, S_IRUGO, node_read_numastat, NULL); @@ -190,6 +177,66 @@ void unregister_node(struct node *node) sysdev_unregister(&node->sysdev); } +struct node node_devices[MAX_NUMNODES]; + +/* + * register cpu under node + */ +int register_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (!obj) + return 0; + return sysfs_create_link(&node_devices[nid].sysdev.kobj, + &obj->kobj, + kobject_name(&obj->kobj)); + } + + return 0; +} + +int unregister_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (obj) + sysfs_remove_link(&node_devices[nid].sysdev.kobj, + kobject_name(&obj->kobj)); + } + return 0; +} + +int register_one_node(int nid) +{ + int error = 0; + int cpu; + + if (node_online(nid)) { + int p_node = parent_node(nid); + struct node *parent = NULL; + + if (p_node != nid) + parent = &node_devices[p_node]; + + error = register_node(&node_devices[nid], nid, parent); + + /* link cpu under this node */ + for_each_present_cpu(cpu) { + if (cpu_to_node(cpu) == nid) + register_cpu_under_node(cpu, nid); + } + } + + return error; + +} + +void unregister_one_node(int nid) +{ + unregister_node(&node_devices[nid]); +} + static int __init register_node_type(void) { return sysdev_class_register(&node_class); diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 2b8755db76c..940ce41f188 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -505,12 +505,36 @@ static int platform_match(struct device * dev, struct device_driver * drv) return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); } -static int platform_suspend(struct device * dev, pm_message_t state) +static int platform_suspend(struct device *dev, pm_message_t mesg) { int ret = 0; if (dev->driver && dev->driver->suspend) - ret = dev->driver->suspend(dev, state); + ret = dev->driver->suspend(dev, mesg); + + return ret; +} + +static int platform_suspend_late(struct device *dev, pm_message_t mesg) +{ + struct platform_driver *drv = to_platform_driver(dev->driver); + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + int ret = 0; + + if (dev->driver && drv->suspend_late) + ret = drv->suspend_late(pdev, mesg); + + return ret; +} + +static int platform_resume_early(struct device *dev) +{ + struct platform_driver *drv = to_platform_driver(dev->driver); + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + int ret = 0; + + if (dev->driver && drv->resume_early) + ret = drv->resume_early(pdev); return ret; } @@ -531,6 +555,8 @@ struct bus_type platform_bus_type = { .match = platform_match, .uevent = platform_uevent, .suspend = platform_suspend, + .suspend_late = platform_suspend_late, + .resume_early = platform_resume_early, .resume = platform_resume, }; EXPORT_SYMBOL_GPL(platform_bus_type); diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index ceeeba2c56c..91f230939c1 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,5 +1,6 @@ obj-y := shutdown.o obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o +obj-$(CONFIG_PM_TRACE) += trace.o ifeq ($(CONFIG_DEBUG_DRIVER),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 0d2e101e4f1..fdfa3d0cf6a 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -19,7 +19,6 @@ * ancestral dependencies that the subsystem list maintains. */ -#include <linux/config.h> #include <linux/device.h> #include "power.h" diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c index 317edbf0fec..020be36705a 100644 --- a/drivers/base/power/resume.c +++ b/drivers/base/power/resume.c @@ -9,6 +9,7 @@ */ #include <linux/device.h> +#include <linux/resume-trace.h> #include "../base.h" #include "power.h" @@ -23,6 +24,8 @@ int resume_device(struct device * dev) { int error = 0; + TRACE_DEVICE(dev); + TRACE_RESUME(0); down(&dev->sem); if (dev->power.pm_parent && dev->power.pm_parent->power.power_state.event) { @@ -35,12 +38,35 @@ int resume_device(struct device * dev) dev_dbg(dev,"resuming\n"); error = dev->bus->resume(dev); } + if (dev->class && dev->class->resume) { + dev_dbg(dev,"class resume\n"); + error = dev->class->resume(dev); + } up(&dev->sem); + TRACE_RESUME(error); return error; } +static int resume_device_early(struct device * dev) +{ + int error = 0; + TRACE_DEVICE(dev); + TRACE_RESUME(0); + if (dev->bus && dev->bus->resume_early) { + dev_dbg(dev,"EARLY resume\n"); + error = dev->bus->resume_early(dev); + } + TRACE_RESUME(error); + return error; +} + +/* + * Resume the devices that have either not gone through + * the late suspend, or that did go through it but also + * went through the early resume + */ void dpm_resume(void) { down(&dpm_list_sem); @@ -49,8 +75,7 @@ void dpm_resume(void) struct device * dev = to_device(entry); get_device(dev); - list_del_init(entry); - list_add_tail(entry, &dpm_active); + list_move_tail(entry, &dpm_active); up(&dpm_list_sem); if (!dev->power.prev_state.event) @@ -71,6 +96,7 @@ void dpm_resume(void) void device_resume(void) { + might_sleep(); down(&dpm_sem); dpm_resume(); up(&dpm_sem); @@ -80,12 +106,12 @@ EXPORT_SYMBOL_GPL(device_resume); /** - * device_power_up_irq - Power on some devices. + * dpm_power_up - Power on some devices. * * Walk the dpm_off_irq list and power each device up. This * is used for devices that required they be powered down with - * interrupts disabled. As devices are powered on, they are moved to - * the dpm_suspended list. + * interrupts disabled. As devices are powered on, they are moved + * to the dpm_active list. * * Interrupts must be disabled when calling this. */ @@ -96,17 +122,14 @@ void dpm_power_up(void) struct list_head * entry = dpm_off_irq.next; struct device * dev = to_device(entry); - get_device(dev); - list_del_init(entry); - list_add_tail(entry, &dpm_active); - resume_device(dev); - put_device(dev); + list_move_tail(entry, &dpm_off); + resume_device_early(dev); } } /** - * device_pm_power_up - Turn on all devices that need special attention. + * device_power_up - Turn on all devices that need special attention. * * Power on system devices then devices that required we shut them down * with interrupts disabled. diff --git a/drivers/base/power/shutdown.c b/drivers/base/power/shutdown.c index 8826a5b6673..3483ae4d57f 100644 --- a/drivers/base/power/shutdown.c +++ b/drivers/base/power/shutdown.c @@ -8,7 +8,6 @@ * */ -#include <linux/config.h> #include <linux/device.h> #include <asm/semaphore.h> diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c index 1a1fe43a305..ece136bf97e 100644 --- a/drivers/base/power/suspend.c +++ b/drivers/base/power/suspend.c @@ -34,6 +34,7 @@ static inline char *suspend_verb(u32 event) switch (event) { case PM_EVENT_SUSPEND: return "suspend"; case PM_EVENT_FREEZE: return "freeze"; + case PM_EVENT_PRETHAW: return "prethaw"; default: return "(unknown suspend event)"; } } @@ -65,7 +66,19 @@ int suspend_device(struct device * dev, pm_message_t state) dev->power.prev_state = dev->power.power_state; - if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) { + if (dev->class && dev->class->suspend && !dev->power.power_state.event) { + dev_dbg(dev, "class %s%s\n", + suspend_verb(state.event), + ((state.event == PM_EVENT_SUSPEND) + && device_may_wakeup(dev)) + ? ", may wakeup" + : "" + ); + error = dev->class->suspend(dev, state); + suspend_report_result(dev->class->suspend, error); + } + + if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) { dev_dbg(dev, "%s%s\n", suspend_verb(state.event), ((state.event == PM_EVENT_SUSPEND) @@ -81,15 +94,42 @@ int suspend_device(struct device * dev, pm_message_t state) } +/* + * This is called with interrupts off, only a single CPU + * running. We can't do down() on a semaphore (and we don't + * need the protection) + */ +static int suspend_device_late(struct device *dev, pm_message_t state) +{ + int error = 0; + + if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) { + dev_dbg(dev, "LATE %s%s\n", + suspend_verb(state.event), + ((state.event == PM_EVENT_SUSPEND) + && device_may_wakeup(dev)) + ? ", may wakeup" + : "" + ); + error = dev->bus->suspend_late(dev, state); + suspend_report_result(dev->bus->suspend_late, error); + } + return error; +} + /** * device_suspend - Save state and stop all devices in system. * @state: Power state to put each device in. * * Walk the dpm_active list, call ->suspend() for each device, and move - * it to dpm_off. - * Check the return value for each. If it returns 0, then we move the - * the device to the dpm_off list. If it returns -EAGAIN, we move it to - * the dpm_off_irq list. If we get a different error, try and back out. + * it to the dpm_off list. + * + * (For historical reasons, if it returns -EAGAIN, that used to mean + * that the device would be called again with interrupts disabled. + * These days, we use the "suspend_late()" callback for that, so we + * print a warning and consider it an error). + * + * If we get a different error, try and back out. * * If we hit a failure with any of the devices, call device_resume() * above to bring the suspended devices back to life. @@ -100,6 +140,7 @@ int device_suspend(pm_message_t state) { int error = 0; + might_sleep(); down(&dpm_sem); down(&dpm_list_sem); while (!list_empty(&dpm_active) && error == 0) { @@ -115,42 +156,27 @@ int device_suspend(pm_message_t state) /* Check if the device got removed */ if (!list_empty(&dev->power.entry)) { - /* Move it to the dpm_off or dpm_off_irq list */ - if (!error) { - list_del(&dev->power.entry); - list_add(&dev->power.entry, &dpm_off); - } else if (error == -EAGAIN) { - list_del(&dev->power.entry); - list_add(&dev->power.entry, &dpm_off_irq); - error = 0; - } + /* Move it to the dpm_off list */ + if (!error) + list_move(&dev->power.entry, &dpm_off); } if (error) printk(KERN_ERR "Could not suspend device %s: " - "error %d\n", kobject_name(&dev->kobj), error); + "error %d%s\n", + kobject_name(&dev->kobj), error, + error == -EAGAIN ? " (please convert to suspend_late)" : ""); put_device(dev); } up(&dpm_list_sem); - if (error) { - /* we failed... before resuming, bring back devices from - * dpm_off_irq list back to main dpm_off list, we do want - * to call resume() on them, in case they partially suspended - * despite returning -EAGAIN - */ - while (!list_empty(&dpm_off_irq)) { - struct list_head * entry = dpm_off_irq.next; - list_del(entry); - list_add(entry, &dpm_off); - } + if (error) dpm_resume(); - } + up(&dpm_sem); return error; } EXPORT_SYMBOL_GPL(device_suspend); - /** * device_power_down - Shut down special devices. * @state: Power state to enter. @@ -165,14 +191,17 @@ int device_power_down(pm_message_t state) int error = 0; struct device * dev; - list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { - if ((error = suspend_device(dev, state))) - break; + while (!list_empty(&dpm_off)) { + struct list_head * entry = dpm_off.prev; + + dev = to_device(entry); + error = suspend_device_late(dev, state); + if (error) + goto Error; + list_move(&dev->power.entry, &dpm_off_irq); } - if (error) - goto Error; - if ((error = sysdev_suspend(state))) - goto Error; + + error = sysdev_suspend(state); Done: return error; Error: diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 40d7242a07c..2d47517dbe3 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -7,22 +7,29 @@ #include "power.h" +#ifdef CONFIG_PM_SYSFS_DEPRECATED + /** * state - Control current power state of device * * show() returns the current power state of the device. '0' indicates - * the device is on. Other values (1-3) indicate the device is in a low + * the device is on. Other values (2) indicate the device is in some low * power state. * - * store() sets the current power state, which is an integer value - * between 0-3. If the device is on ('0'), and the value written is - * greater than 0, then the device is placed directly into the low-power - * state (via its driver's ->suspend() method). - * If the device is currently in a low-power state, and the value is 0, - * the device is powered back on (via the ->resume() method). - * If the device is in a low-power state, and a different low-power state - * is requested, the device is first resumed, then suspended into the new - * low-power state. + * store() sets the current power state, which is an integer valued + * 0, 2, or 3. Devices with bus.suspend_late(), or bus.resume_early() + * methods fail this operation; those methods couldn't be called. + * Otherwise, + * + * - If the recorded dev->power.power_state.event matches the + * target value, nothing is done. + * - If the recorded event code is nonzero, the device is reactivated + * by calling bus.resume() and/or class.resume(). + * - If the target value is nonzero, the device is suspended by + * calling class.suspend() and/or bus.suspend() with event code + * PM_EVENT_SUSPEND. + * + * This mechanism is DEPRECATED and should only be used for testing. */ static ssize_t state_show(struct device * dev, struct device_attribute *attr, char * buf) @@ -38,6 +45,10 @@ static ssize_t state_store(struct device * dev, struct device_attribute *attr, c pm_message_t state; int error = -EINVAL; + /* disallow incomplete suspend sequences */ + if (dev->bus && (dev->bus->suspend_late || dev->bus->resume_early)) + return error; + state.event = PM_EVENT_SUSPEND; /* Older apps expected to write "3" here - confused with PCI D3 */ if ((n == 1) && !strcmp(buf, "3")) @@ -57,6 +68,8 @@ static ssize_t state_store(struct device * dev, struct device_attribute *attr, c static DEVICE_ATTR(state, 0644, state_show, state_store); +#endif /* CONFIG_PM_SYSFS_DEPRECATED */ + /* * wakeup - Report/change current wakeup option for device * @@ -130,7 +143,9 @@ static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); static struct attribute * power_attrs[] = { +#ifdef CONFIG_PM_SYSFS_DEPRECATED &dev_attr_state.attr, +#endif &dev_attr_wakeup.attr, NULL, }; diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c new file mode 100644 index 00000000000..a9ab30fefff --- /dev/null +++ b/drivers/base/power/trace.c @@ -0,0 +1,228 @@ +/* + * drivers/base/power/trace.c + * + * Copyright (C) 2006 Linus Torvalds + * + * Trace facility for suspend/resume problems, when none of the + * devices may be working. + */ + +#include <linux/resume-trace.h> +#include <linux/rtc.h> + +#include <asm/rtc.h> + +#include "power.h" + +/* + * Horrid, horrid, horrid. + * + * It turns out that the _only_ piece of hardware that actually + * keeps its value across a hard boot (and, more importantly, the + * POST init sequence) is literally the realtime clock. + * + * Never mind that an RTC chip has 114 bytes (and often a whole + * other bank of an additional 128 bytes) of nice SRAM that is + * _designed_ to keep data - the POST will clear it. So we literally + * can just use the few bytes of actual time data, which means that + * we're really limited. + * + * It means, for example, that we can't use the seconds at all + * (since the time between the hang and the boot might be more + * than a minute), and we'd better not depend on the low bits of + * the minutes either. + * + * There are the wday fields etc, but I wouldn't guarantee those + * are dependable either. And if the date isn't valid, either the + * hw or POST will do strange things. + * + * So we're left with: + * - year: 0-99 + * - month: 0-11 + * - day-of-month: 1-28 + * - hour: 0-23 + * - min: (0-30)*2 + * + * Giving us a total range of 0-16128000 (0xf61800), ie less + * than 24 bits of actual data we can save across reboots. + * + * And if your box can't boot in less than three minutes, + * you're screwed. + * + * Now, almost 24 bits of data is pitifully small, so we need + * to be pretty dense if we want to use it for anything nice. + * What we do is that instead of saving off nice readable info, + * we save off _hashes_ of information that we can hopefully + * regenerate after the reboot. + * + * In particular, this means that we might be unlucky, and hit + * a case where we have a hash collision, and we end up not + * being able to tell for certain exactly which case happened. + * But that's hopefully unlikely. + * + * What we do is to take the bits we can fit, and split them + * into three parts (16*997*1009 = 16095568), and use the values + * for: + * - 0-15: user-settable + * - 0-996: file + line number + * - 0-1008: device + */ +#define USERHASH (16) +#define FILEHASH (997) +#define DEVHASH (1009) + +#define DEVSEED (7919) + +static unsigned int dev_hash_value; + +static int set_magic_time(unsigned int user, unsigned int file, unsigned int device) +{ + unsigned int n = user + USERHASH*(file + FILEHASH*device); + + // June 7th, 2006 + static struct rtc_time time = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 7, + .tm_mon = 5, // June - counting from zero + .tm_year = 106, + .tm_wday = 3, + .tm_yday = 160, + .tm_isdst = 1 + }; + + time.tm_year = (n % 100); + n /= 100; + time.tm_mon = (n % 12); + n /= 12; + time.tm_mday = (n % 28) + 1; + n /= 28; + time.tm_hour = (n % 24); + n /= 24; + time.tm_min = (n % 20) * 3; + n /= 20; + set_rtc_time(&time); + return n ? -1 : 0; +} + +static unsigned int read_magic_time(void) +{ + struct rtc_time time; + unsigned int val; + + get_rtc_time(&time); + printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n", + time.tm_hour, time.tm_min, time.tm_sec, + time.tm_mon, time.tm_mday, time.tm_year); + val = time.tm_year; /* 100 years */ + if (val > 100) + val -= 100; + val += time.tm_mon * 100; /* 12 months */ + val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */ + val += time.tm_hour * 100 * 12 * 28; /* 24 hours */ + val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */ + return val; +} + +/* + * This is just the sdbm hash function with a user-supplied + * seed and final size parameter. + */ +static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod) +{ + unsigned char c; + while ((c = *data++) != 0) { + seed = (seed << 16) + (seed << 6) - seed + c; + } + return seed % mod; +} + +void set_trace_device(struct device *dev) +{ + dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH); +} + +/* + * We could just take the "tracedata" index into the .tracedata + * section instead. Generating a hash of the data gives us a + * chance to work across kernel versions, and perhaps more + * importantly it also gives us valid/invalid check (ie we will + * likely not give totally bogus reports - if the hash matches, + * it's not any guarantee, but it's a high _likelihood_ that + * the match is valid). + */ +void generate_resume_trace(void *tracedata, unsigned int user) +{ + unsigned short lineno = *(unsigned short *)tracedata; + const char *file = *(const char **)(tracedata + 2); + unsigned int user_hash_value, file_hash_value; + + user_hash_value = user % USERHASH; + file_hash_value = hash_string(lineno, file, FILEHASH); + set_magic_time(user_hash_value, file_hash_value, dev_hash_value); +} + +extern char __tracedata_start, __tracedata_end; +static int show_file_hash(unsigned int value) +{ + int match; + char *tracedata; + + match = 0; + for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; tracedata += 6) { + unsigned short lineno = *(unsigned short *)tracedata; + const char *file = *(const char **)(tracedata + 2); + unsigned int hash = hash_string(lineno, file, FILEHASH); + if (hash != value) + continue; + printk(" hash matches %s:%u\n", file, lineno); + match++; + } + return match; +} + +static int show_dev_hash(unsigned int value) +{ + int match = 0; + struct list_head * entry = dpm_active.prev; + + while (entry != &dpm_active) { + struct device * dev = to_device(entry); + unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH); + if (hash == value) { + printk(" hash matches device %s\n", dev->bus_id); + match++; + } + entry = entry->prev; + } + return match; +} + +static unsigned int hash_value_early_read; + +static int early_resume_init(void) +{ + hash_value_early_read = read_magic_time(); + return 0; +} + +static int late_resume_init(void) +{ + unsigned int val = hash_value_early_read; + unsigned int user, file, dev; + + user = val % USERHASH; + val = val / USERHASH; + file = val % FILEHASH; + val = val / FILEHASH; + dev = val /* % DEVHASH */; + + printk(" Magic number: %d:%d:%d\n", user, file, dev); + show_file_hash(file); + show_dev_hash(dev); + return 0; +} + +core_initcall(early_resume_init); +late_initcall(late_resume_init); diff --git a/drivers/base/sys.c b/drivers/base/sys.c index 6858178b3af..04e5db445c7 100644 --- a/drivers/base/sys.c +++ b/drivers/base/sys.c @@ -12,7 +12,6 @@ * add themselves as children of the system bus. */ -#include <linux/config.h> #include <linux/sysdev.h> #include <linux/err.h> #include <linux/module.h> diff --git a/drivers/base/topology.c b/drivers/base/topology.c index 8c52421cbc5..3ef9d514b91 100644 --- a/drivers/base/topology.c +++ b/drivers/base/topology.c @@ -107,7 +107,7 @@ static int __cpuinit topology_remove_dev(struct sys_device * sys_dev) return 0; } -static int topology_cpu_callback(struct notifier_block *nfb, +static int __cpuinit topology_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu) { unsigned int cpu = (unsigned long)hcpu; @@ -125,7 +125,7 @@ static int topology_cpu_callback(struct notifier_block *nfb, return NOTIFY_OK; } -static struct notifier_block topology_cpu_notifier = +static struct notifier_block __cpuinitdata topology_cpu_notifier = { .notifier_call = topology_cpu_callback, }; @@ -139,7 +139,7 @@ static int __cpuinit topology_sysfs_init(void) (void *)(long)i); } - register_cpu_notifier(&topology_cpu_notifier); + register_hotcpu_notifier(&topology_cpu_notifier); return 0; } |