684 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Enclosure Services4 *5 * Copyright (C) 2008 James Bottomley <James.Bottomley@HansenPartnership.com>6 *7**-----------------------------------------------------------------------------8**9**10**-----------------------------------------------------------------------------11*/12#include <linux/device.h>13#include <linux/enclosure.h>14#include <linux/err.h>15#include <linux/list.h>16#include <linux/kernel.h>17#include <linux/module.h>18#include <linux/mutex.h>19#include <linux/slab.h>20 21static LIST_HEAD(container_list);22static DEFINE_MUTEX(container_list_lock);23static struct class enclosure_class;24 25/**26 * enclosure_find - find an enclosure given a parent device27 * @dev: the parent to match against28 * @start: Optional enclosure device to start from (NULL if none)29 *30 * Looks through the list of registered enclosures to find all those31 * with @dev as a parent. Returns NULL if no enclosure is32 * found. @start can be used as a starting point to obtain multiple33 * enclosures per parent (should begin with NULL and then be set to34 * each returned enclosure device). Obtains a reference to the35 * enclosure class device which must be released with put_device().36 * If @start is not NULL, a reference must be taken on it which is37 * released before returning (this allows a loop through all38 * enclosures to exit with only the reference on the enclosure of39 * interest held). Note that the @dev may correspond to the actual40 * device housing the enclosure, in which case no iteration via @start41 * is required.42 */43struct enclosure_device *enclosure_find(struct device *dev,44 struct enclosure_device *start)45{46 struct enclosure_device *edev;47 48 mutex_lock(&container_list_lock);49 edev = list_prepare_entry(start, &container_list, node);50 if (start)51 put_device(&start->edev);52 53 list_for_each_entry_continue(edev, &container_list, node) {54 struct device *parent = edev->edev.parent;55 /* parent might not be immediate, so iterate up to56 * the root of the tree if necessary */57 while (parent) {58 if (parent == dev) {59 get_device(&edev->edev);60 mutex_unlock(&container_list_lock);61 return edev;62 }63 parent = parent->parent;64 }65 }66 mutex_unlock(&container_list_lock);67 68 return NULL;69}70EXPORT_SYMBOL_GPL(enclosure_find);71 72/**73 * enclosure_for_each_device - calls a function for each enclosure74 * @fn: the function to call75 * @data: the data to pass to each call76 *77 * Loops over all the enclosures calling the function.78 *79 * Note, this function uses a mutex which will be held across calls to80 * @fn, so it must have non atomic context, and @fn may (although it81 * should not) sleep or otherwise cause the mutex to be held for82 * indefinite periods83 */84int enclosure_for_each_device(int (*fn)(struct enclosure_device *, void *),85 void *data)86{87 int error = 0;88 struct enclosure_device *edev;89 90 mutex_lock(&container_list_lock);91 list_for_each_entry(edev, &container_list, node) {92 error = fn(edev, data);93 if (error)94 break;95 }96 mutex_unlock(&container_list_lock);97 98 return error;99}100EXPORT_SYMBOL_GPL(enclosure_for_each_device);101 102/**103 * enclosure_register - register device as an enclosure104 *105 * @dev: device containing the enclosure106 * @name: chosen device name107 * @components: number of components in the enclosure108 * @cb: platform call-backs109 *110 * This sets up the device for being an enclosure. Note that @dev does111 * not have to be a dedicated enclosure device. It may be some other type112 * of device that additionally responds to enclosure services113 */114struct enclosure_device *115enclosure_register(struct device *dev, const char *name, int components,116 struct enclosure_component_callbacks *cb)117{118 struct enclosure_device *edev =119 kzalloc(struct_size(edev, component, components), GFP_KERNEL);120 int err, i;121 122 BUG_ON(!cb);123 124 if (!edev)125 return ERR_PTR(-ENOMEM);126 127 edev->components = components;128 129 edev->edev.class = &enclosure_class;130 edev->edev.parent = get_device(dev);131 edev->cb = cb;132 dev_set_name(&edev->edev, "%s", name);133 err = device_register(&edev->edev);134 if (err)135 goto err;136 137 for (i = 0; i < components; i++) {138 edev->component[i].number = -1;139 edev->component[i].slot = -1;140 edev->component[i].power_status = -1;141 }142 143 mutex_lock(&container_list_lock);144 list_add_tail(&edev->node, &container_list);145 mutex_unlock(&container_list_lock);146 147 return edev;148 149 err:150 put_device(edev->edev.parent);151 kfree(edev);152 return ERR_PTR(err);153}154EXPORT_SYMBOL_GPL(enclosure_register);155 156static struct enclosure_component_callbacks enclosure_null_callbacks;157 158/**159 * enclosure_unregister - remove an enclosure160 *161 * @edev: the registered enclosure to remove;162 */163void enclosure_unregister(struct enclosure_device *edev)164{165 int i;166 167 mutex_lock(&container_list_lock);168 list_del(&edev->node);169 mutex_unlock(&container_list_lock);170 171 for (i = 0; i < edev->components; i++)172 if (edev->component[i].number != -1)173 device_unregister(&edev->component[i].cdev);174 175 /* prevent any callbacks into service user */176 edev->cb = &enclosure_null_callbacks;177 device_unregister(&edev->edev);178}179EXPORT_SYMBOL_GPL(enclosure_unregister);180 181#define ENCLOSURE_NAME_SIZE 64182#define COMPONENT_NAME_SIZE 64183 184static void enclosure_link_name(struct enclosure_component *cdev, char *name)185{186 strcpy(name, "enclosure_device:");187 strcat(name, dev_name(&cdev->cdev));188}189 190static void enclosure_remove_links(struct enclosure_component *cdev)191{192 char name[ENCLOSURE_NAME_SIZE];193 194 enclosure_link_name(cdev, name);195 196 /*197 * In odd circumstances, like multipath devices, something else may198 * already have removed the links, so check for this condition first.199 */200 if (cdev->dev->kobj.sd)201 sysfs_remove_link(&cdev->dev->kobj, name);202 203 if (cdev->cdev.kobj.sd)204 sysfs_remove_link(&cdev->cdev.kobj, "device");205}206 207static int enclosure_add_links(struct enclosure_component *cdev)208{209 int error;210 char name[ENCLOSURE_NAME_SIZE];211 212 error = sysfs_create_link(&cdev->cdev.kobj, &cdev->dev->kobj, "device");213 if (error)214 return error;215 216 enclosure_link_name(cdev, name);217 error = sysfs_create_link(&cdev->dev->kobj, &cdev->cdev.kobj, name);218 if (error)219 sysfs_remove_link(&cdev->cdev.kobj, "device");220 221 return error;222}223 224static void enclosure_release(struct device *cdev)225{226 struct enclosure_device *edev = to_enclosure_device(cdev);227 228 put_device(cdev->parent);229 kfree(edev);230}231 232static void enclosure_component_release(struct device *dev)233{234 struct enclosure_component *cdev = to_enclosure_component(dev);235 236 if (cdev->dev) {237 enclosure_remove_links(cdev);238 put_device(cdev->dev);239 }240 put_device(dev->parent);241}242 243static struct enclosure_component *244enclosure_component_find_by_name(struct enclosure_device *edev,245 const char *name)246{247 int i;248 const char *cname;249 struct enclosure_component *ecomp;250 251 if (!edev || !name || !name[0])252 return NULL;253 254 for (i = 0; i < edev->components; i++) {255 ecomp = &edev->component[i];256 cname = dev_name(&ecomp->cdev);257 if (ecomp->number != -1 &&258 cname && cname[0] &&259 !strcmp(cname, name))260 return ecomp;261 }262 263 return NULL;264}265 266static const struct attribute_group *enclosure_component_groups[];267 268/**269 * enclosure_component_alloc - prepare a new enclosure component270 * @edev: the enclosure to add the component271 * @number: the device number272 * @type: the type of component being added273 * @name: an optional name to appear in sysfs (leave NULL if none)274 *275 * The name is optional for enclosures that give their components a unique276 * name. If not, leave the field NULL and a name will be assigned.277 *278 * Returns a pointer to the enclosure component or an error.279 */280struct enclosure_component *281enclosure_component_alloc(struct enclosure_device *edev,282 unsigned int number,283 enum enclosure_component_type type,284 const char *name)285{286 struct enclosure_component *ecomp;287 struct device *cdev;288 int i;289 char newname[COMPONENT_NAME_SIZE];290 291 if (number >= edev->components)292 return ERR_PTR(-EINVAL);293 294 ecomp = &edev->component[number];295 296 if (ecomp->number != -1)297 return ERR_PTR(-EINVAL);298 299 ecomp->type = type;300 ecomp->number = number;301 cdev = &ecomp->cdev;302 cdev->parent = get_device(&edev->edev);303 304 if (name && name[0]) {305 /* Some hardware (e.g. enclosure in RX300 S6) has components306 * with non unique names. Registering duplicates in sysfs307 * will lead to warnings during bootup. So make the names308 * unique by appending consecutive numbers -1, -2, ... */309 i = 1;310 snprintf(newname, COMPONENT_NAME_SIZE,311 "%s", name);312 while (enclosure_component_find_by_name(edev, newname))313 snprintf(newname, COMPONENT_NAME_SIZE,314 "%s-%i", name, i++);315 dev_set_name(cdev, "%s", newname);316 } else317 dev_set_name(cdev, "%u", number);318 319 cdev->release = enclosure_component_release;320 cdev->groups = enclosure_component_groups;321 322 return ecomp;323}324EXPORT_SYMBOL_GPL(enclosure_component_alloc);325 326/**327 * enclosure_component_register - publishes an initialized enclosure component328 * @ecomp: component to add329 *330 * Returns 0 on successful registration, releases the component otherwise331 */332int enclosure_component_register(struct enclosure_component *ecomp)333{334 struct device *cdev;335 int err;336 337 cdev = &ecomp->cdev;338 err = device_register(cdev);339 if (err) {340 ecomp->number = -1;341 put_device(cdev);342 return err;343 }344 345 return 0;346}347EXPORT_SYMBOL_GPL(enclosure_component_register);348 349/**350 * enclosure_add_device - add a device as being part of an enclosure351 * @edev: the enclosure device being added to.352 * @component: the number of the component353 * @dev: the device being added354 *355 * Declares a real device to reside in slot (or identifier) @num of an356 * enclosure. This will cause the relevant sysfs links to appear.357 * This function may also be used to change a device associated with358 * an enclosure without having to call enclosure_remove_device() in359 * between.360 *361 * Returns zero on success or an error.362 */363int enclosure_add_device(struct enclosure_device *edev, int component,364 struct device *dev)365{366 struct enclosure_component *cdev;367 int err;368 369 if (!edev || component >= edev->components)370 return -EINVAL;371 372 cdev = &edev->component[component];373 374 if (cdev->dev == dev)375 return -EEXIST;376 377 if (cdev->dev) {378 enclosure_remove_links(cdev);379 put_device(cdev->dev);380 }381 cdev->dev = get_device(dev);382 err = enclosure_add_links(cdev);383 if (err) {384 put_device(cdev->dev);385 cdev->dev = NULL;386 }387 return err;388}389EXPORT_SYMBOL_GPL(enclosure_add_device);390 391/**392 * enclosure_remove_device - remove a device from an enclosure393 * @edev: the enclosure device394 * @dev: device to remove/put395 *396 * Returns zero on success or an error.397 *398 */399int enclosure_remove_device(struct enclosure_device *edev, struct device *dev)400{401 struct enclosure_component *cdev;402 int i;403 404 if (!edev || !dev)405 return -EINVAL;406 407 for (i = 0; i < edev->components; i++) {408 cdev = &edev->component[i];409 if (cdev->dev == dev) {410 enclosure_remove_links(cdev);411 put_device(dev);412 cdev->dev = NULL;413 return 0;414 }415 }416 return -ENODEV;417}418EXPORT_SYMBOL_GPL(enclosure_remove_device);419 420/*421 * sysfs pieces below422 */423 424static ssize_t components_show(struct device *cdev,425 struct device_attribute *attr, char *buf)426{427 struct enclosure_device *edev = to_enclosure_device(cdev);428 429 return sysfs_emit(buf, "%d\n", edev->components);430}431static DEVICE_ATTR_RO(components);432 433static ssize_t id_show(struct device *cdev,434 struct device_attribute *attr,435 char *buf)436{437 struct enclosure_device *edev = to_enclosure_device(cdev);438 439 if (edev->cb->show_id)440 return edev->cb->show_id(edev, buf);441 return -EINVAL;442}443static DEVICE_ATTR_RO(id);444 445static struct attribute *enclosure_class_attrs[] = {446 &dev_attr_components.attr,447 &dev_attr_id.attr,448 NULL,449};450ATTRIBUTE_GROUPS(enclosure_class);451 452static struct class enclosure_class = {453 .name = "enclosure",454 .dev_release = enclosure_release,455 .dev_groups = enclosure_class_groups,456};457 458static const char *const enclosure_status[] = {459 [ENCLOSURE_STATUS_UNSUPPORTED] = "unsupported",460 [ENCLOSURE_STATUS_OK] = "OK",461 [ENCLOSURE_STATUS_CRITICAL] = "critical",462 [ENCLOSURE_STATUS_NON_CRITICAL] = "non-critical",463 [ENCLOSURE_STATUS_UNRECOVERABLE] = "unrecoverable",464 [ENCLOSURE_STATUS_NOT_INSTALLED] = "not installed",465 [ENCLOSURE_STATUS_UNKNOWN] = "unknown",466 [ENCLOSURE_STATUS_UNAVAILABLE] = "unavailable",467 [ENCLOSURE_STATUS_MAX] = NULL,468};469 470static const char *const enclosure_type[] = {471 [ENCLOSURE_COMPONENT_DEVICE] = "device",472 [ENCLOSURE_COMPONENT_ARRAY_DEVICE] = "array device",473};474 475static ssize_t get_component_fault(struct device *cdev,476 struct device_attribute *attr, char *buf)477{478 struct enclosure_device *edev = to_enclosure_device(cdev->parent);479 struct enclosure_component *ecomp = to_enclosure_component(cdev);480 481 if (edev->cb->get_fault)482 edev->cb->get_fault(edev, ecomp);483 return sysfs_emit(buf, "%d\n", ecomp->fault);484}485 486static ssize_t set_component_fault(struct device *cdev,487 struct device_attribute *attr,488 const char *buf, size_t count)489{490 struct enclosure_device *edev = to_enclosure_device(cdev->parent);491 struct enclosure_component *ecomp = to_enclosure_component(cdev);492 int val = simple_strtoul(buf, NULL, 0);493 494 if (edev->cb->set_fault)495 edev->cb->set_fault(edev, ecomp, val);496 return count;497}498 499static ssize_t get_component_status(struct device *cdev,500 struct device_attribute *attr,char *buf)501{502 struct enclosure_device *edev = to_enclosure_device(cdev->parent);503 struct enclosure_component *ecomp = to_enclosure_component(cdev);504 505 if (edev->cb->get_status)506 edev->cb->get_status(edev, ecomp);507 return sysfs_emit(buf, "%s\n", enclosure_status[ecomp->status]);508}509 510static ssize_t set_component_status(struct device *cdev,511 struct device_attribute *attr,512 const char *buf, size_t count)513{514 struct enclosure_device *edev = to_enclosure_device(cdev->parent);515 struct enclosure_component *ecomp = to_enclosure_component(cdev);516 int i;517 518 for (i = 0; enclosure_status[i]; i++) {519 if (strncmp(buf, enclosure_status[i],520 strlen(enclosure_status[i])) == 0 &&521 (buf[strlen(enclosure_status[i])] == '\n' ||522 buf[strlen(enclosure_status[i])] == '\0'))523 break;524 }525 526 if (enclosure_status[i] && edev->cb->set_status) {527 edev->cb->set_status(edev, ecomp, i);528 return count;529 } else530 return -EINVAL;531}532 533static ssize_t get_component_active(struct device *cdev,534 struct device_attribute *attr, char *buf)535{536 struct enclosure_device *edev = to_enclosure_device(cdev->parent);537 struct enclosure_component *ecomp = to_enclosure_component(cdev);538 539 if (edev->cb->get_active)540 edev->cb->get_active(edev, ecomp);541 return sysfs_emit(buf, "%d\n", ecomp->active);542}543 544static ssize_t set_component_active(struct device *cdev,545 struct device_attribute *attr,546 const char *buf, size_t count)547{548 struct enclosure_device *edev = to_enclosure_device(cdev->parent);549 struct enclosure_component *ecomp = to_enclosure_component(cdev);550 int val = simple_strtoul(buf, NULL, 0);551 552 if (edev->cb->set_active)553 edev->cb->set_active(edev, ecomp, val);554 return count;555}556 557static ssize_t get_component_locate(struct device *cdev,558 struct device_attribute *attr, char *buf)559{560 struct enclosure_device *edev = to_enclosure_device(cdev->parent);561 struct enclosure_component *ecomp = to_enclosure_component(cdev);562 563 if (edev->cb->get_locate)564 edev->cb->get_locate(edev, ecomp);565 return sysfs_emit(buf, "%d\n", ecomp->locate);566}567 568static ssize_t set_component_locate(struct device *cdev,569 struct device_attribute *attr,570 const char *buf, size_t count)571{572 struct enclosure_device *edev = to_enclosure_device(cdev->parent);573 struct enclosure_component *ecomp = to_enclosure_component(cdev);574 int val = simple_strtoul(buf, NULL, 0);575 576 if (edev->cb->set_locate)577 edev->cb->set_locate(edev, ecomp, val);578 return count;579}580 581static ssize_t get_component_power_status(struct device *cdev,582 struct device_attribute *attr,583 char *buf)584{585 struct enclosure_device *edev = to_enclosure_device(cdev->parent);586 struct enclosure_component *ecomp = to_enclosure_component(cdev);587 588 if (edev->cb->get_power_status)589 edev->cb->get_power_status(edev, ecomp);590 591 /* If still uninitialized, the callback failed or does not exist. */592 if (ecomp->power_status == -1)593 return (edev->cb->get_power_status) ? -EIO : -ENOTTY;594 595 return sysfs_emit(buf, "%s\n", ecomp->power_status ? "on" : "off");596}597 598static ssize_t set_component_power_status(struct device *cdev,599 struct device_attribute *attr,600 const char *buf, size_t count)601{602 struct enclosure_device *edev = to_enclosure_device(cdev->parent);603 struct enclosure_component *ecomp = to_enclosure_component(cdev);604 int val;605 606 if (strncmp(buf, "on", 2) == 0 &&607 (buf[2] == '\n' || buf[2] == '\0'))608 val = 1;609 else if (strncmp(buf, "off", 3) == 0 &&610 (buf[3] == '\n' || buf[3] == '\0'))611 val = 0;612 else613 return -EINVAL;614 615 if (edev->cb->set_power_status)616 edev->cb->set_power_status(edev, ecomp, val);617 return count;618}619 620static ssize_t get_component_type(struct device *cdev,621 struct device_attribute *attr, char *buf)622{623 struct enclosure_component *ecomp = to_enclosure_component(cdev);624 625 return sysfs_emit(buf, "%s\n", enclosure_type[ecomp->type]);626}627 628static ssize_t get_component_slot(struct device *cdev,629 struct device_attribute *attr, char *buf)630{631 struct enclosure_component *ecomp = to_enclosure_component(cdev);632 int slot;633 634 /* if the enclosure does not override then use 'number' as a stand-in */635 if (ecomp->slot >= 0)636 slot = ecomp->slot;637 else638 slot = ecomp->number;639 640 return sysfs_emit(buf, "%d\n", slot);641}642 643static DEVICE_ATTR(fault, S_IRUGO | S_IWUSR, get_component_fault,644 set_component_fault);645static DEVICE_ATTR(status, S_IRUGO | S_IWUSR, get_component_status,646 set_component_status);647static DEVICE_ATTR(active, S_IRUGO | S_IWUSR, get_component_active,648 set_component_active);649static DEVICE_ATTR(locate, S_IRUGO | S_IWUSR, get_component_locate,650 set_component_locate);651static DEVICE_ATTR(power_status, S_IRUGO | S_IWUSR, get_component_power_status,652 set_component_power_status);653static DEVICE_ATTR(type, S_IRUGO, get_component_type, NULL);654static DEVICE_ATTR(slot, S_IRUGO, get_component_slot, NULL);655 656static struct attribute *enclosure_component_attrs[] = {657 &dev_attr_fault.attr,658 &dev_attr_status.attr,659 &dev_attr_active.attr,660 &dev_attr_locate.attr,661 &dev_attr_power_status.attr,662 &dev_attr_type.attr,663 &dev_attr_slot.attr,664 NULL665};666ATTRIBUTE_GROUPS(enclosure_component);667 668static int __init enclosure_init(void)669{670 return class_register(&enclosure_class);671}672 673static void __exit enclosure_exit(void)674{675 class_unregister(&enclosure_class);676}677 678module_init(enclosure_init);679module_exit(enclosure_exit);680 681MODULE_AUTHOR("James Bottomley");682MODULE_DESCRIPTION("Enclosure Services");683MODULE_LICENSE("GPL v2");684