diff options
author | Paul Mackerras <paulus@samba.org> | 2007-04-29 22:38:01 -0400 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2007-04-29 22:38:01 -0400 |
commit | 49e1900d4cc2e7bcecb681fe60f0990bec2dcce8 (patch) | |
tree | 253801ebf57e0a23856a2c7be129c2c178f62fdf /drivers/base/core.c | |
parent | 34f6d749c0a328817d5e36274e53121c1db734dc (diff) | |
parent | b9099ff63c75216d6ca10bce5a1abcd9293c27e6 (diff) |
Merge branch 'linux-2.6' into for-2.6.22
Diffstat (limited to 'drivers/base/core.c')
-rw-r--r-- | drivers/base/core.c | 293 |
1 files changed, 207 insertions, 86 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c index d7fcf823a42a..8aa090da1cd7 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c | |||
@@ -43,7 +43,8 @@ int (*platform_notify_remove)(struct device * dev) = NULL; | |||
43 | const char *dev_driver_string(struct device *dev) | 43 | const char *dev_driver_string(struct device *dev) |
44 | { | 44 | { |
45 | return dev->driver ? dev->driver->name : | 45 | return dev->driver ? dev->driver->name : |
46 | (dev->bus ? dev->bus->name : ""); | 46 | (dev->bus ? dev->bus->name : |
47 | (dev->class ? dev->class->name : "")); | ||
47 | } | 48 | } |
48 | EXPORT_SYMBOL(dev_driver_string); | 49 | EXPORT_SYMBOL(dev_driver_string); |
49 | 50 | ||
@@ -119,6 +120,8 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj) | |||
119 | 120 | ||
120 | if (ktype == &ktype_device) { | 121 | if (ktype == &ktype_device) { |
121 | struct device *dev = to_dev(kobj); | 122 | struct device *dev = to_dev(kobj); |
123 | if (dev->uevent_suppress) | ||
124 | return 0; | ||
122 | if (dev->bus) | 125 | if (dev->bus) |
123 | return 1; | 126 | return 1; |
124 | if (dev->class) | 127 | if (dev->class) |
@@ -156,6 +159,11 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp, | |||
156 | "MINOR=%u", MINOR(dev->devt)); | 159 | "MINOR=%u", MINOR(dev->devt)); |
157 | } | 160 | } |
158 | 161 | ||
162 | if (dev->type && dev->type->name) | ||
163 | add_uevent_var(envp, num_envp, &i, | ||
164 | buffer, buffer_size, &length, | ||
165 | "DEVTYPE=%s", dev->type->name); | ||
166 | |||
159 | if (dev->driver) | 167 | if (dev->driver) |
160 | add_uevent_var(envp, num_envp, &i, | 168 | add_uevent_var(envp, num_envp, &i, |
161 | buffer, buffer_size, &length, | 169 | buffer, buffer_size, &length, |
@@ -238,71 +246,152 @@ static struct kset_uevent_ops device_uevent_ops = { | |||
238 | .uevent = dev_uevent, | 246 | .uevent = dev_uevent, |
239 | }; | 247 | }; |
240 | 248 | ||
249 | static ssize_t show_uevent(struct device *dev, struct device_attribute *attr, | ||
250 | char *buf) | ||
251 | { | ||
252 | struct kobject *top_kobj; | ||
253 | struct kset *kset; | ||
254 | char *envp[32]; | ||
255 | char data[PAGE_SIZE]; | ||
256 | char *pos; | ||
257 | int i; | ||
258 | size_t count = 0; | ||
259 | int retval; | ||
260 | |||
261 | /* search the kset, the device belongs to */ | ||
262 | top_kobj = &dev->kobj; | ||
263 | if (!top_kobj->kset && top_kobj->parent) { | ||
264 | do { | ||
265 | top_kobj = top_kobj->parent; | ||
266 | } while (!top_kobj->kset && top_kobj->parent); | ||
267 | } | ||
268 | if (!top_kobj->kset) | ||
269 | goto out; | ||
270 | kset = top_kobj->kset; | ||
271 | if (!kset->uevent_ops || !kset->uevent_ops->uevent) | ||
272 | goto out; | ||
273 | |||
274 | /* respect filter */ | ||
275 | if (kset->uevent_ops && kset->uevent_ops->filter) | ||
276 | if (!kset->uevent_ops->filter(kset, &dev->kobj)) | ||
277 | goto out; | ||
278 | |||
279 | /* let the kset specific function add its keys */ | ||
280 | pos = data; | ||
281 | retval = kset->uevent_ops->uevent(kset, &dev->kobj, | ||
282 | envp, ARRAY_SIZE(envp), | ||
283 | pos, PAGE_SIZE); | ||
284 | if (retval) | ||
285 | goto out; | ||
286 | |||
287 | /* copy keys to file */ | ||
288 | for (i = 0; envp[i]; i++) { | ||
289 | pos = &buf[count]; | ||
290 | count += sprintf(pos, "%s\n", envp[i]); | ||
291 | } | ||
292 | out: | ||
293 | return count; | ||
294 | } | ||
295 | |||
241 | static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, | 296 | static ssize_t store_uevent(struct device *dev, struct device_attribute *attr, |
242 | const char *buf, size_t count) | 297 | const char *buf, size_t count) |
243 | { | 298 | { |
299 | if (memcmp(buf, "add", 3) != 0) | ||
300 | dev_err(dev, "uevent: unsupported action-string; this will " | ||
301 | "be ignored in a future kernel version"); | ||
244 | kobject_uevent(&dev->kobj, KOBJ_ADD); | 302 | kobject_uevent(&dev->kobj, KOBJ_ADD); |
245 | return count; | 303 | return count; |
246 | } | 304 | } |
247 | 305 | ||
248 | static int device_add_groups(struct device *dev) | 306 | static int device_add_attributes(struct device *dev, |
307 | struct device_attribute *attrs) | ||
308 | { | ||
309 | int error = 0; | ||
310 | int i; | ||
311 | |||
312 | if (attrs) { | ||
313 | for (i = 0; attr_name(attrs[i]); i++) { | ||
314 | error = device_create_file(dev, &attrs[i]); | ||
315 | if (error) | ||
316 | break; | ||
317 | } | ||
318 | if (error) | ||
319 | while (--i >= 0) | ||
320 | device_remove_file(dev, &attrs[i]); | ||
321 | } | ||
322 | return error; | ||
323 | } | ||
324 | |||
325 | static void device_remove_attributes(struct device *dev, | ||
326 | struct device_attribute *attrs) | ||
249 | { | 327 | { |
250 | int i; | 328 | int i; |
329 | |||
330 | if (attrs) | ||
331 | for (i = 0; attr_name(attrs[i]); i++) | ||
332 | device_remove_file(dev, &attrs[i]); | ||
333 | } | ||
334 | |||
335 | static int device_add_groups(struct device *dev, | ||
336 | struct attribute_group **groups) | ||
337 | { | ||
251 | int error = 0; | 338 | int error = 0; |
339 | int i; | ||
252 | 340 | ||
253 | if (dev->groups) { | 341 | if (groups) { |
254 | for (i = 0; dev->groups[i]; i++) { | 342 | for (i = 0; groups[i]; i++) { |
255 | error = sysfs_create_group(&dev->kobj, dev->groups[i]); | 343 | error = sysfs_create_group(&dev->kobj, groups[i]); |
256 | if (error) { | 344 | if (error) { |
257 | while (--i >= 0) | 345 | while (--i >= 0) |
258 | sysfs_remove_group(&dev->kobj, dev->groups[i]); | 346 | sysfs_remove_group(&dev->kobj, groups[i]); |
259 | goto out; | 347 | break; |
260 | } | 348 | } |
261 | } | 349 | } |
262 | } | 350 | } |
263 | out: | ||
264 | return error; | 351 | return error; |
265 | } | 352 | } |
266 | 353 | ||
267 | static void device_remove_groups(struct device *dev) | 354 | static void device_remove_groups(struct device *dev, |
355 | struct attribute_group **groups) | ||
268 | { | 356 | { |
269 | int i; | 357 | int i; |
270 | if (dev->groups) { | 358 | |
271 | for (i = 0; dev->groups[i]; i++) { | 359 | if (groups) |
272 | sysfs_remove_group(&dev->kobj, dev->groups[i]); | 360 | for (i = 0; groups[i]; i++) |
273 | } | 361 | sysfs_remove_group(&dev->kobj, groups[i]); |
274 | } | ||
275 | } | 362 | } |
276 | 363 | ||
277 | static int device_add_attrs(struct device *dev) | 364 | static int device_add_attrs(struct device *dev) |
278 | { | 365 | { |
279 | struct class *class = dev->class; | 366 | struct class *class = dev->class; |
280 | struct device_type *type = dev->type; | 367 | struct device_type *type = dev->type; |
281 | int error = 0; | 368 | int error; |
282 | int i; | ||
283 | 369 | ||
284 | if (class && class->dev_attrs) { | 370 | if (class) { |
285 | for (i = 0; attr_name(class->dev_attrs[i]); i++) { | 371 | error = device_add_attributes(dev, class->dev_attrs); |
286 | error = device_create_file(dev, &class->dev_attrs[i]); | ||
287 | if (error) | ||
288 | break; | ||
289 | } | ||
290 | if (error) | 372 | if (error) |
291 | while (--i >= 0) | 373 | return error; |
292 | device_remove_file(dev, &class->dev_attrs[i]); | ||
293 | } | 374 | } |
294 | 375 | ||
295 | if (type && type->attrs) { | 376 | if (type) { |
296 | for (i = 0; attr_name(type->attrs[i]); i++) { | 377 | error = device_add_groups(dev, type->groups); |
297 | error = device_create_file(dev, &type->attrs[i]); | ||
298 | if (error) | ||
299 | break; | ||
300 | } | ||
301 | if (error) | 378 | if (error) |
302 | while (--i >= 0) | 379 | goto err_remove_class_attrs; |
303 | device_remove_file(dev, &type->attrs[i]); | ||
304 | } | 380 | } |
305 | 381 | ||
382 | error = device_add_groups(dev, dev->groups); | ||
383 | if (error) | ||
384 | goto err_remove_type_groups; | ||
385 | |||
386 | return 0; | ||
387 | |||
388 | err_remove_type_groups: | ||
389 | if (type) | ||
390 | device_remove_groups(dev, type->groups); | ||
391 | err_remove_class_attrs: | ||
392 | if (class) | ||
393 | device_remove_attributes(dev, class->dev_attrs); | ||
394 | |||
306 | return error; | 395 | return error; |
307 | } | 396 | } |
308 | 397 | ||
@@ -310,17 +399,14 @@ static void device_remove_attrs(struct device *dev) | |||
310 | { | 399 | { |
311 | struct class *class = dev->class; | 400 | struct class *class = dev->class; |
312 | struct device_type *type = dev->type; | 401 | struct device_type *type = dev->type; |
313 | int i; | ||
314 | 402 | ||
315 | if (class && class->dev_attrs) { | 403 | device_remove_groups(dev, dev->groups); |
316 | for (i = 0; attr_name(class->dev_attrs[i]); i++) | ||
317 | device_remove_file(dev, &class->dev_attrs[i]); | ||
318 | } | ||
319 | 404 | ||
320 | if (type && type->attrs) { | 405 | if (type) |
321 | for (i = 0; attr_name(type->attrs[i]); i++) | 406 | device_remove_groups(dev, type->groups); |
322 | device_remove_file(dev, &type->attrs[i]); | 407 | |
323 | } | 408 | if (class) |
409 | device_remove_attributes(dev, class->dev_attrs); | ||
324 | } | 410 | } |
325 | 411 | ||
326 | 412 | ||
@@ -394,9 +480,10 @@ void device_remove_bin_file(struct device *dev, struct bin_attribute *attr) | |||
394 | EXPORT_SYMBOL_GPL(device_remove_bin_file); | 480 | EXPORT_SYMBOL_GPL(device_remove_bin_file); |
395 | 481 | ||
396 | /** | 482 | /** |
397 | * device_schedule_callback - helper to schedule a callback for a device | 483 | * device_schedule_callback_owner - helper to schedule a callback for a device |
398 | * @dev: device. | 484 | * @dev: device. |
399 | * @func: callback function to invoke later. | 485 | * @func: callback function to invoke later. |
486 | * @owner: module owning the callback routine | ||
400 | * | 487 | * |
401 | * Attribute methods must not unregister themselves or their parent device | 488 | * Attribute methods must not unregister themselves or their parent device |
402 | * (which would amount to the same thing). Attempts to do so will deadlock, | 489 | * (which would amount to the same thing). Attempts to do so will deadlock, |
@@ -407,20 +494,23 @@ EXPORT_SYMBOL_GPL(device_remove_bin_file); | |||
407 | * argument in the workqueue's process context. @dev will be pinned until | 494 | * argument in the workqueue's process context. @dev will be pinned until |
408 | * @func returns. | 495 | * @func returns. |
409 | * | 496 | * |
497 | * This routine is usually called via the inline device_schedule_callback(), | ||
498 | * which automatically sets @owner to THIS_MODULE. | ||
499 | * | ||
410 | * Returns 0 if the request was submitted, -ENOMEM if storage could not | 500 | * Returns 0 if the request was submitted, -ENOMEM if storage could not |
411 | * be allocated. | 501 | * be allocated, -ENODEV if a reference to @owner isn't available. |
412 | * | 502 | * |
413 | * NOTE: This routine won't work if CONFIG_SYSFS isn't set! It uses an | 503 | * NOTE: This routine won't work if CONFIG_SYSFS isn't set! It uses an |
414 | * underlying sysfs routine (since it is intended for use by attribute | 504 | * underlying sysfs routine (since it is intended for use by attribute |
415 | * methods), and if sysfs isn't available you'll get nothing but -ENOSYS. | 505 | * methods), and if sysfs isn't available you'll get nothing but -ENOSYS. |
416 | */ | 506 | */ |
417 | int device_schedule_callback(struct device *dev, | 507 | int device_schedule_callback_owner(struct device *dev, |
418 | void (*func)(struct device *)) | 508 | void (*func)(struct device *), struct module *owner) |
419 | { | 509 | { |
420 | return sysfs_schedule_callback(&dev->kobj, | 510 | return sysfs_schedule_callback(&dev->kobj, |
421 | (void (*)(void *)) func, dev); | 511 | (void (*)(void *)) func, dev, owner); |
422 | } | 512 | } |
423 | EXPORT_SYMBOL_GPL(device_schedule_callback); | 513 | EXPORT_SYMBOL_GPL(device_schedule_callback_owner); |
424 | 514 | ||
425 | static void klist_children_get(struct klist_node *n) | 515 | static void klist_children_get(struct klist_node *n) |
426 | { | 516 | { |
@@ -477,34 +567,58 @@ static struct kobject * get_device_parent(struct device *dev, | |||
477 | return NULL; | 567 | return NULL; |
478 | } | 568 | } |
479 | #else | 569 | #else |
480 | static struct kobject * virtual_device_parent(struct device *dev) | 570 | static struct kobject *virtual_device_parent(struct device *dev) |
481 | { | 571 | { |
482 | if (!dev->class) | 572 | static struct kobject *virtual_dir = NULL; |
483 | return ERR_PTR(-ENODEV); | ||
484 | 573 | ||
485 | if (!dev->class->virtual_dir) { | 574 | if (!virtual_dir) |
486 | static struct kobject *virtual_dir = NULL; | 575 | virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual"); |
487 | 576 | ||
488 | if (!virtual_dir) | 577 | return virtual_dir; |
489 | virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual"); | ||
490 | dev->class->virtual_dir = kobject_add_dir(virtual_dir, dev->class->name); | ||
491 | } | ||
492 | |||
493 | return dev->class->virtual_dir; | ||
494 | } | 578 | } |
495 | 579 | ||
496 | static struct kobject * get_device_parent(struct device *dev, | 580 | static struct kobject * get_device_parent(struct device *dev, |
497 | struct device *parent) | 581 | struct device *parent) |
498 | { | 582 | { |
499 | /* if this is a class device, and has no parent, create one */ | 583 | if (dev->class) { |
500 | if ((dev->class) && (parent == NULL)) { | 584 | struct kobject *kobj = NULL; |
501 | return virtual_device_parent(dev); | 585 | struct kobject *parent_kobj; |
502 | } else if (parent) | 586 | struct kobject *k; |
587 | |||
588 | /* | ||
589 | * If we have no parent, we live in "virtual". | ||
590 | * Class-devices with a bus-device as parent, live | ||
591 | * in a class-directory to prevent namespace collisions. | ||
592 | */ | ||
593 | if (parent == NULL) | ||
594 | parent_kobj = virtual_device_parent(dev); | ||
595 | else if (parent->class) | ||
596 | return &parent->kobj; | ||
597 | else | ||
598 | parent_kobj = &parent->kobj; | ||
599 | |||
600 | /* find our class-directory at the parent and reference it */ | ||
601 | spin_lock(&dev->class->class_dirs.list_lock); | ||
602 | list_for_each_entry(k, &dev->class->class_dirs.list, entry) | ||
603 | if (k->parent == parent_kobj) { | ||
604 | kobj = kobject_get(k); | ||
605 | break; | ||
606 | } | ||
607 | spin_unlock(&dev->class->class_dirs.list_lock); | ||
608 | if (kobj) | ||
609 | return kobj; | ||
610 | |||
611 | /* or create a new class-directory at the parent device */ | ||
612 | return kobject_kset_add_dir(&dev->class->class_dirs, | ||
613 | parent_kobj, dev->class->name); | ||
614 | } | ||
615 | |||
616 | if (parent) | ||
503 | return &parent->kobj; | 617 | return &parent->kobj; |
504 | return NULL; | 618 | return NULL; |
505 | } | 619 | } |
506 | |||
507 | #endif | 620 | #endif |
621 | |||
508 | static int setup_parent(struct device *dev, struct device *parent) | 622 | static int setup_parent(struct device *dev, struct device *parent) |
509 | { | 623 | { |
510 | struct kobject *kobj; | 624 | struct kobject *kobj; |
@@ -541,7 +655,6 @@ int device_add(struct device *dev) | |||
541 | pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); | 655 | pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); |
542 | 656 | ||
543 | parent = get_device(dev->parent); | 657 | parent = get_device(dev->parent); |
544 | |||
545 | error = setup_parent(dev, parent); | 658 | error = setup_parent(dev, parent); |
546 | if (error) | 659 | if (error) |
547 | goto Error; | 660 | goto Error; |
@@ -562,10 +675,11 @@ int device_add(struct device *dev) | |||
562 | BUS_NOTIFY_ADD_DEVICE, dev); | 675 | BUS_NOTIFY_ADD_DEVICE, dev); |
563 | 676 | ||
564 | dev->uevent_attr.attr.name = "uevent"; | 677 | dev->uevent_attr.attr.name = "uevent"; |
565 | dev->uevent_attr.attr.mode = S_IWUSR; | 678 | dev->uevent_attr.attr.mode = S_IRUGO | S_IWUSR; |
566 | if (dev->driver) | 679 | if (dev->driver) |
567 | dev->uevent_attr.attr.owner = dev->driver->owner; | 680 | dev->uevent_attr.attr.owner = dev->driver->owner; |
568 | dev->uevent_attr.store = store_uevent; | 681 | dev->uevent_attr.store = store_uevent; |
682 | dev->uevent_attr.show = show_uevent; | ||
569 | error = device_create_file(dev, &dev->uevent_attr); | 683 | error = device_create_file(dev, &dev->uevent_attr); |
570 | if (error) | 684 | if (error) |
571 | goto attrError; | 685 | goto attrError; |
@@ -614,16 +728,12 @@ int device_add(struct device *dev) | |||
614 | 728 | ||
615 | if ((error = device_add_attrs(dev))) | 729 | if ((error = device_add_attrs(dev))) |
616 | goto AttrsError; | 730 | goto AttrsError; |
617 | if ((error = device_add_groups(dev))) | ||
618 | goto GroupError; | ||
619 | if ((error = device_pm_add(dev))) | 731 | if ((error = device_pm_add(dev))) |
620 | goto PMError; | 732 | goto PMError; |
621 | if ((error = bus_add_device(dev))) | 733 | if ((error = bus_add_device(dev))) |
622 | goto BusError; | 734 | goto BusError; |
623 | if (!dev->uevent_suppress) | 735 | kobject_uevent(&dev->kobj, KOBJ_ADD); |
624 | kobject_uevent(&dev->kobj, KOBJ_ADD); | 736 | bus_attach_device(dev); |
625 | if ((error = bus_attach_device(dev))) | ||
626 | goto AttachError; | ||
627 | if (parent) | 737 | if (parent) |
628 | klist_add_tail(&dev->knode_parent, &parent->klist_children); | 738 | klist_add_tail(&dev->knode_parent, &parent->klist_children); |
629 | 739 | ||
@@ -639,19 +749,15 @@ int device_add(struct device *dev) | |||
639 | up(&dev->class->sem); | 749 | up(&dev->class->sem); |
640 | } | 750 | } |
641 | Done: | 751 | Done: |
642 | kfree(class_name); | 752 | kfree(class_name); |
643 | put_device(dev); | 753 | put_device(dev); |
644 | return error; | 754 | return error; |
645 | AttachError: | ||
646 | bus_remove_device(dev); | ||
647 | BusError: | 755 | BusError: |
648 | device_pm_remove(dev); | 756 | device_pm_remove(dev); |
649 | PMError: | 757 | PMError: |
650 | if (dev->bus) | 758 | if (dev->bus) |
651 | blocking_notifier_call_chain(&dev->bus->bus_notifier, | 759 | blocking_notifier_call_chain(&dev->bus->bus_notifier, |
652 | BUS_NOTIFY_DEL_DEVICE, dev); | 760 | BUS_NOTIFY_DEL_DEVICE, dev); |
653 | device_remove_groups(dev); | ||
654 | GroupError: | ||
655 | device_remove_attrs(dev); | 761 | device_remove_attrs(dev); |
656 | AttrsError: | 762 | AttrsError: |
657 | if (dev->devt_attr) { | 763 | if (dev->devt_attr) { |
@@ -677,15 +783,6 @@ int device_add(struct device *dev) | |||
677 | #endif | 783 | #endif |
678 | sysfs_remove_link(&dev->kobj, "device"); | 784 | sysfs_remove_link(&dev->kobj, "device"); |
679 | } | 785 | } |
680 | |||
681 | down(&dev->class->sem); | ||
682 | /* notify any interfaces that the device is now gone */ | ||
683 | list_for_each_entry(class_intf, &dev->class->interfaces, node) | ||
684 | if (class_intf->remove_dev) | ||
685 | class_intf->remove_dev(dev, class_intf); | ||
686 | /* remove the device from the class list */ | ||
687 | list_del_init(&dev->node); | ||
688 | up(&dev->class->sem); | ||
689 | } | 786 | } |
690 | ueventattrError: | 787 | ueventattrError: |
691 | device_remove_file(dev, &dev->uevent_attr); | 788 | device_remove_file(dev, &dev->uevent_attr); |
@@ -796,9 +893,33 @@ void device_del(struct device * dev) | |||
796 | /* remove the device from the class list */ | 893 | /* remove the device from the class list */ |
797 | list_del_init(&dev->node); | 894 | list_del_init(&dev->node); |
798 | up(&dev->class->sem); | 895 | up(&dev->class->sem); |
896 | |||
897 | /* If we live in a parent class-directory, unreference it */ | ||
898 | if (dev->kobj.parent->kset == &dev->class->class_dirs) { | ||
899 | struct device *d; | ||
900 | int other = 0; | ||
901 | |||
902 | /* | ||
903 | * if we are the last child of our class, delete | ||
904 | * our class-directory at this parent | ||
905 | */ | ||
906 | down(&dev->class->sem); | ||
907 | list_for_each_entry(d, &dev->class->devices, node) { | ||
908 | if (d == dev) | ||
909 | continue; | ||
910 | if (d->kobj.parent == dev->kobj.parent) { | ||
911 | other = 1; | ||
912 | break; | ||
913 | } | ||
914 | } | ||
915 | if (!other) | ||
916 | kobject_del(dev->kobj.parent); | ||
917 | |||
918 | kobject_put(dev->kobj.parent); | ||
919 | up(&dev->class->sem); | ||
920 | } | ||
799 | } | 921 | } |
800 | device_remove_file(dev, &dev->uevent_attr); | 922 | device_remove_file(dev, &dev->uevent_attr); |
801 | device_remove_groups(dev); | ||
802 | device_remove_attrs(dev); | 923 | device_remove_attrs(dev); |
803 | bus_remove_device(dev); | 924 | bus_remove_device(dev); |
804 | 925 | ||