From 4f3549d72d1b5c90ecc7e673402f38f4486d22c2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 2 May 2013 22:15:29 +0200 Subject: Driver core: Add offline/online device operations In some cases, graceful hot-removal of devices is not possible, although in principle the devices in question support hotplug. For example, that may happen for the last CPU in the system or for memory modules holding kernel memory. In those cases it is nice to be able to check if the given device can be gracefully hot-removed before triggering a removal procedure that cannot be aborted or reversed. Unfortunately, however, the kernel currently doesn't provide any support for that. To address that deficiency, introduce support for offline and online operations that can be performed on devices, respectively, before a hot-removal and in case when it is necessary (or convenient) to put a device back online after a successful offline (that has not been followed by removal). The idea is that the offline will fail whenever the given device cannot be gracefully removed from the system and it will not be allowed to use the device after a successful offline (until a subsequent online) in analogy with the existing CPU offline/online mechanism. For now, the offline and online operations are introduced at the bus type level, as that should be sufficient for the most urgent use cases (CPUs and memory modules). In the future, however, the approach may be extended to cover some more complicated device offline/online scenarios involving device drivers etc. The lock_device_hotplug() and unlock_device_hotplug() functions are introduced because subsequent patches need to put larger pieces of code under device_hotplug_lock to prevent race conditions between device offline and removal from happening. Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Toshi Kani --- drivers/base/core.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) (limited to 'drivers/base/core.c') diff --git a/drivers/base/core.c b/drivers/base/core.c index 01631243757..60c97568608 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -403,6 +403,36 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, static struct device_attribute uevent_attr = __ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent); +static ssize_t show_online(struct device *dev, struct device_attribute *attr, + char *buf) +{ + bool val; + + lock_device_hotplug(); + val = !dev->offline; + unlock_device_hotplug(); + return sprintf(buf, "%u\n", val); +} + +static ssize_t store_online(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + + lock_device_hotplug(); + ret = val ? device_online(dev) : device_offline(dev); + unlock_device_hotplug(); + return ret < 0 ? ret : count; +} + +static struct device_attribute online_attr = + __ATTR(online, S_IRUGO | S_IWUSR, show_online, store_online); + static int device_add_attributes(struct device *dev, struct device_attribute *attrs) { @@ -516,6 +546,12 @@ static int device_add_attrs(struct device *dev) if (error) goto err_remove_type_groups; + if (device_supports_offline(dev) && !dev->offline_disabled) { + error = device_create_file(dev, &online_attr); + if (error) + goto err_remove_type_groups; + } + return 0; err_remove_type_groups: @@ -536,6 +572,7 @@ static void device_remove_attrs(struct device *dev) struct class *class = dev->class; const struct device_type *type = dev->type; + device_remove_file(dev, &online_attr); device_remove_groups(dev, dev->groups); if (type) @@ -1431,6 +1468,99 @@ EXPORT_SYMBOL_GPL(put_device); EXPORT_SYMBOL_GPL(device_create_file); EXPORT_SYMBOL_GPL(device_remove_file); +static DEFINE_MUTEX(device_hotplug_lock); + +void lock_device_hotplug(void) +{ + mutex_lock(&device_hotplug_lock); +} + +void unlock_device_hotplug(void) +{ + mutex_unlock(&device_hotplug_lock); +} + +static int device_check_offline(struct device *dev, void *not_used) +{ + int ret; + + ret = device_for_each_child(dev, NULL, device_check_offline); + if (ret) + return ret; + + return device_supports_offline(dev) && !dev->offline ? -EBUSY : 0; +} + +/** + * device_offline - Prepare the device for hot-removal. + * @dev: Device to be put offline. + * + * Execute the device bus type's .offline() callback, if present, to prepare + * the device for a subsequent hot-removal. If that succeeds, the device must + * not be used until either it is removed or its bus type's .online() callback + * is executed. + * + * Call under device_hotplug_lock. + */ +int device_offline(struct device *dev) +{ + int ret; + + if (dev->offline_disabled) + return -EPERM; + + ret = device_for_each_child(dev, NULL, device_check_offline); + if (ret) + return ret; + + device_lock(dev); + if (device_supports_offline(dev)) { + if (dev->offline) { + ret = 1; + } else { + ret = dev->bus->offline(dev); + if (!ret) { + kobject_uevent(&dev->kobj, KOBJ_OFFLINE); + dev->offline = true; + } + } + } + device_unlock(dev); + + return ret; +} + +/** + * device_online - Put the device back online after successful device_offline(). + * @dev: Device to be put back online. + * + * If device_offline() has been successfully executed for @dev, but the device + * has not been removed subsequently, execute its bus type's .online() callback + * to indicate that the device can be used again. + * + * Call under device_hotplug_lock. + */ +int device_online(struct device *dev) +{ + int ret = 0; + + device_lock(dev); + if (device_supports_offline(dev)) { + if (dev->offline) { + ret = dev->bus->online(dev); + if (!ret) { + kobject_uevent(&dev->kobj, KOBJ_ONLINE); + dev->offline = false; + } + } else { + ret = 1; + } + } + device_unlock(dev); + + return ret; +} + struct root_device { struct device dev; struct module *owner; -- cgit v1.2.3-70-g09d2 From a4e2400a63f0e4fb6af5083a24d80b6fa4c41ccd Mon Sep 17 00:00:00 2001 From: Federico Vaga Date: Mon, 15 Apr 2013 11:18:11 +0200 Subject: base/core.c: improve comment of the function device_find_child() Signed-off-by: Federico Vaga Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/base/core.c') diff --git a/drivers/base/core.c b/drivers/base/core.c index 01631243757..3c8512f7b64 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1372,6 +1372,8 @@ int device_for_each_child(struct device *parent, void *data, * if it does. If the callback returns non-zero and a reference to the * current device can be obtained, this function will return to the caller * and not iterate over any more devices. + * + * NOTE: you will need to drop the reference with put_device() after use. */ struct device *device_find_child(struct device *parent, void *data, int (*match)(struct device *dev, void *data)) -- cgit v1.2.3-70-g09d2 From f8878dcb84f7eb85b11c6be9d7d2581a88675615 Mon Sep 17 00:00:00 2001 From: "Robert P. J. Day" Date: Sat, 1 Jun 2013 20:17:34 -0400 Subject: Documentation: Tidy up some drivers/base/core.c kerneldoc content. Standardize the indentation, and switch the order of a couple kerneldoc entries to match the parameter order. No functional change. Signed-off-by: Robert P. J. Day Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/base/core.c') diff --git a/drivers/base/core.c b/drivers/base/core.c index 0dbe02b3ce9..6fdc53d46fa 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -193,12 +193,12 @@ ssize_t device_show_bool(struct device *dev, struct device_attribute *attr, EXPORT_SYMBOL_GPL(device_show_bool); /** - * device_release - free device structure. - * @kobj: device's kobject. + * device_release - free device structure. + * @kobj: device's kobject. * - * This is called once the reference count for the object - * reaches 0. We forward the call to the device's release - * method, which should handle actually freeing the structure. + * This is called once the reference count for the object + * reaches 0. We forward the call to the device's release + * method, which should handle actually freeing the structure. */ static void device_release(struct kobject *kobj) { @@ -1334,8 +1334,8 @@ const char *device_get_devnode(struct device *dev, /** * device_for_each_child - device child iterator. * @parent: parent struct device. - * @data: data for the callback. * @fn: function to be called for each device. + * @data: data for the callback. * * Iterate over @parent's child devices, and call @fn for each, * passing it @data. @@ -1363,8 +1363,8 @@ int device_for_each_child(struct device *parent, void *data, /** * device_find_child - device iterator for locating a particular device. * @parent: parent struct device - * @data: Data to pass to match function * @match: Callback function to check device + * @data: Data to pass to match function * * This is similar to the device_for_each_child() function above, but it * returns a reference to a device that is 'found' for later use, as -- cgit v1.2.3-70-g09d2 From 39ef311204941ddd01ea2950d6220c8ccc710d15 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 14 Jul 2013 16:05:57 -0700 Subject: driver core: Introduce device_create_groups device_create_groups lets callers create devices as well as associated sysfs attributes with a single call. This avoids race conditions seen if sysfs attributes on new devices are created later. [fixed up comment block placement and add checks for printk buffer formats - gregkh] Signed-off-by: Guenter Roeck Cc: Jean Delvare Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 111 ++++++++++++++++++++++++++++++++++++------------- include/linux/device.h | 5 +++ 2 files changed, 88 insertions(+), 28 deletions(-) (limited to 'drivers/base/core.c') diff --git a/drivers/base/core.c b/drivers/base/core.c index dc3ea237f08..a8aae1823f7 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1667,34 +1667,11 @@ static void device_create_release(struct device *dev) kfree(dev); } -/** - * device_create_vargs - creates a device and registers it with sysfs - * @class: pointer to the struct class that this device should be registered to - * @parent: pointer to the parent struct device of this new device, if any - * @devt: the dev_t for the char device to be added - * @drvdata: the data to be added to the device for callbacks - * @fmt: string for the device's name - * @args: va_list for the device's name - * - * This function can be used by char device classes. A struct device - * will be created in sysfs, registered to the specified class. - * - * A "dev" file will be created, showing the dev_t for the device, if - * the dev_t is not 0,0. - * If a pointer to a parent struct device is passed in, the newly created - * struct device will be a child of that device in sysfs. - * The pointer to the struct device will be returned from the call. - * Any further sysfs files that might be required can be created using this - * pointer. - * - * 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(). - */ -struct device *device_create_vargs(struct class *class, struct device *parent, - dev_t devt, void *drvdata, const char *fmt, - va_list args) +static struct device * +device_create_groups_vargs(struct class *class, struct device *parent, + dev_t devt, void *drvdata, + const struct attribute_group **groups, + const char *fmt, va_list args) { struct device *dev = NULL; int retval = -ENODEV; @@ -1711,6 +1688,7 @@ struct device *device_create_vargs(struct class *class, struct device *parent, dev->devt = devt; dev->class = class; dev->parent = parent; + dev->groups = groups; dev->release = device_create_release; dev_set_drvdata(dev, drvdata); @@ -1728,6 +1706,39 @@ error: put_device(dev); return ERR_PTR(retval); } + +/** + * device_create_vargs - creates a device and registers it with sysfs + * @class: pointer to the struct class that this device should be registered to + * @parent: pointer to the parent struct device of this new device, if any + * @devt: the dev_t for the char device to be added + * @drvdata: the data to be added to the device for callbacks + * @fmt: string for the device's name + * @args: va_list for the device's name + * + * This function can be used by char device classes. A struct device + * will be created in sysfs, registered to the specified class. + * + * A "dev" file will be created, showing the dev_t for the device, if + * the dev_t is not 0,0. + * If a pointer to a parent struct device is passed in, the newly created + * struct device will be a child of that device in sysfs. + * The pointer to the struct device will be returned from the call. + * Any further sysfs files that might be required can be created using this + * pointer. + * + * 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(). + */ +struct device *device_create_vargs(struct class *class, struct device *parent, + dev_t devt, void *drvdata, const char *fmt, + va_list args) +{ + return device_create_groups_vargs(class, parent, devt, drvdata, NULL, + fmt, args); +} EXPORT_SYMBOL_GPL(device_create_vargs); /** @@ -1767,6 +1778,50 @@ struct device *device_create(struct class *class, struct device *parent, } EXPORT_SYMBOL_GPL(device_create); +/** + * device_create_with_groups - creates a device and registers it with sysfs + * @class: pointer to the struct class that this device should be registered to + * @parent: pointer to the parent struct device of this new device, if any + * @devt: the dev_t for the char device to be added + * @drvdata: the data to be added to the device for callbacks + * @groups: NULL-terminated list of attribute groups to be created + * @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. + * Additional attributes specified in the groups parameter will also + * be created automatically. + * + * A "dev" file will be created, showing the dev_t for the device, if + * the dev_t is not 0,0. + * If a pointer to a parent struct device is passed in, the newly created + * struct device will be a child of that device in sysfs. + * The pointer to the struct device will be returned from the call. + * Any further sysfs files that might be required can be created using this + * pointer. + * + * 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(). + */ +struct device *device_create_with_groups(struct class *class, + struct device *parent, dev_t devt, + void *drvdata, + const struct attribute_group **groups, + const char *fmt, ...) +{ + va_list vargs; + struct device *dev; + + va_start(vargs, fmt); + dev = device_create_groups_vargs(class, parent, devt, drvdata, groups, + fmt, vargs); + va_end(vargs); + return dev; +} +EXPORT_SYMBOL_GPL(device_create_with_groups); + static int __match_devt(struct device *dev, const void *data) { const dev_t *devt = data; diff --git a/include/linux/device.h b/include/linux/device.h index f207a8f49f8..bd5931e89f7 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -938,6 +938,11 @@ extern __printf(5, 6) struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...); +extern __printf(6, 7) +struct device *device_create_with_groups(struct class *cls, + struct device *parent, dev_t devt, void *drvdata, + const struct attribute_group **groups, + const char *fmt, ...); extern void device_destroy(struct class *cls, dev_t devt); /* -- cgit v1.2.3-70-g09d2 From d05a6f96c76062b5f25858ac02cf677602076f7e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sun, 14 Jul 2013 16:05:58 -0700 Subject: driver core: add default groups to struct class We should be using groups, not attribute lists, for classes to allow subdirectories, and soon, binary files. Groups are just more flexible overall, so add them. The dev_attrs list will go away after all in-kernel users are converted to use dev_groups. Reviewed-by: Guenter Roeck Tested-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 9 ++++++++- include/linux/device.h | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers/base/core.c') diff --git a/drivers/base/core.c b/drivers/base/core.c index a8aae1823f7..8856d74545d 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -528,9 +528,12 @@ static int device_add_attrs(struct device *dev) int error; if (class) { - error = device_add_attributes(dev, class->dev_attrs); + error = device_add_groups(dev, class->dev_groups); if (error) return error; + error = device_add_attributes(dev, class->dev_attrs); + if (error) + goto err_remove_class_groups; error = device_add_bin_attributes(dev, class->dev_bin_attrs); if (error) goto err_remove_class_attrs; @@ -563,6 +566,9 @@ static int device_add_attrs(struct device *dev) err_remove_class_attrs: if (class) device_remove_attributes(dev, class->dev_attrs); + err_remove_class_groups: + if (class) + device_remove_groups(dev, class->dev_groups); return error; } @@ -581,6 +587,7 @@ static void device_remove_attrs(struct device *dev) if (class) { device_remove_attributes(dev, class->dev_attrs); device_remove_bin_attributes(dev, class->dev_bin_attrs); + device_remove_groups(dev, class->dev_groups); } } diff --git a/include/linux/device.h b/include/linux/device.h index bd5931e89f7..22b546a5859 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -320,6 +320,7 @@ int subsys_virtual_register(struct bus_type *subsys, * @name: Name of the class. * @owner: The module owner. * @class_attrs: Default attributes of this class. + * @dev_groups: Default attributes of the devices that belong to the class. * @dev_attrs: Default attributes of the devices belong to the class. * @dev_bin_attrs: Default binary attributes of the devices belong to the class. * @dev_kobj: The kobject that represents this class and links it into the hierarchy. @@ -349,7 +350,8 @@ struct class { struct module *owner; struct class_attribute *class_attrs; - struct device_attribute *dev_attrs; + struct device_attribute *dev_attrs; /* use dev_groups instead */ + const struct attribute_group **dev_groups; struct bin_attribute *dev_bin_attrs; struct kobject *dev_kobj; -- cgit v1.2.3-70-g09d2