Driver core: Fix device_move() vs. dpm list ordering, v2
authorCornelia Huck <cornelia.huck@de.ibm.com>
Wed, 4 Mar 2009 11:44:00 +0000 (12:44 +0100)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 24 Mar 2009 23:38:26 +0000 (16:38 -0700)
dpm_list currently relies on the fact that child devices will
be registered after their parents to get a correct suspend
order. Using device_move() however destroys this assumption, as
an already registered device may be moved under a newly registered
one.

This patch adds a new argument to device_move(), allowing callers
to specify how dpm_list should be adapted.

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/base/core.c
drivers/base/power/main.c
drivers/base/power/power.h
drivers/s390/cio/device.c
include/linux/device.h
include/linux/pm.h
net/bluetooth/hci_sysfs.c
net/bluetooth/rfcomm/tty.c

index 95c67ffd71da12484f90b137815908b93b3f6b4b..e73c92d13a23dfcd0e5ead18322584de1bb3a154 100644 (file)
@@ -1561,8 +1561,10 @@ out:
  * device_move - moves a device to a new parent
  * @dev: the pointer to the struct device to be moved
  * @new_parent: the new parent of the device (can by NULL)
+ * @dpm_order: how to reorder the dpm_list
  */
-int device_move(struct device *dev, struct device *new_parent)
+int device_move(struct device *dev, struct device *new_parent,
+               enum dpm_order dpm_order)
 {
        int error;
        struct device *old_parent;
@@ -1572,6 +1574,7 @@ int device_move(struct device *dev, struct device *new_parent)
        if (!dev)
                return -EINVAL;
 
+       device_pm_lock();
        new_parent = get_device(new_parent);
        new_parent_kobj = get_device_parent(dev, new_parent);
 
@@ -1613,9 +1616,23 @@ int device_move(struct device *dev, struct device *new_parent)
                put_device(new_parent);
                goto out;
        }
+       switch (dpm_order) {
+       case DPM_ORDER_NONE:
+               break;
+       case DPM_ORDER_DEV_AFTER_PARENT:
+               device_pm_move_after(dev, new_parent);
+               break;
+       case DPM_ORDER_PARENT_BEFORE_DEV:
+               device_pm_move_before(new_parent, dev);
+               break;
+       case DPM_ORDER_DEV_LAST:
+               device_pm_move_last(dev);
+               break;
+       }
 out_put:
        put_device(old_parent);
 out:
+       device_pm_unlock();
        put_device(dev);
        return error;
 }
index 2d14f4ae6c01da28be636629db5e50b9205b5699..e255341682c88d4a0955f1151595c1334e005dd5 100644 (file)
@@ -106,6 +106,50 @@ void device_pm_remove(struct device *dev)
        mutex_unlock(&dpm_list_mtx);
 }
 
+/**
+ *     device_pm_move_before - move device in dpm_list
+ *     @deva:  Device to move in dpm_list
+ *     @devb:  Device @deva should come before
+ */
+void device_pm_move_before(struct device *deva, struct device *devb)
+{
+       pr_debug("PM: Moving %s:%s before %s:%s\n",
+                deva->bus ? deva->bus->name : "No Bus",
+                kobject_name(&deva->kobj),
+                devb->bus ? devb->bus->name : "No Bus",
+                kobject_name(&devb->kobj));
+       /* Delete deva from dpm_list and reinsert before devb. */
+       list_move_tail(&deva->power.entry, &devb->power.entry);
+}
+
+/**
+ *     device_pm_move_after - move device in dpm_list
+ *     @deva:  Device to move in dpm_list
+ *     @devb:  Device @deva should come after
+ */
+void device_pm_move_after(struct device *deva, struct device *devb)
+{
+       pr_debug("PM: Moving %s:%s after %s:%s\n",
+                deva->bus ? deva->bus->name : "No Bus",
+                kobject_name(&deva->kobj),
+                devb->bus ? devb->bus->name : "No Bus",
+                kobject_name(&devb->kobj));
+       /* Delete deva from dpm_list and reinsert after devb. */
+       list_move(&deva->power.entry, &devb->power.entry);
+}
+
+/**
+ *     device_pm_move_last - move device to end of dpm_list
+ *     @dev:   Device to move in dpm_list
+ */
+void device_pm_move_last(struct device *dev)
+{
+       pr_debug("PM: Moving %s:%s to end of list\n",
+                dev->bus ? dev->bus->name : "No Bus",
+                kobject_name(&dev->kobj));
+       list_move_tail(&dev->power.entry, &dpm_list);
+}
+
 /**
  *     pm_op - execute the PM operation appropiate for given PM event
  *     @dev:   Device.
index 41f51fae042f06f06d7548f4ad10463b7b399f4f..c7cb4fc3735c5594cdb26530c81d92fbcacaf438 100644 (file)
@@ -18,11 +18,19 @@ static inline struct device *to_device(struct list_head *entry)
 
 extern void device_pm_add(struct device *);
 extern void device_pm_remove(struct device *);
+extern void device_pm_move_before(struct device *, struct device *);
+extern void device_pm_move_after(struct device *, struct device *);
+extern void device_pm_move_last(struct device *);
 
 #else /* CONFIG_PM_SLEEP */
 
 static inline void device_pm_add(struct device *dev) {}
 static inline void device_pm_remove(struct device *dev) {}
+static inline void device_pm_move_before(struct device *deva,
+                                        struct device *devb) {}
+static inline void device_pm_move_after(struct device *deva,
+                                       struct device *devb) {}
+static inline void device_pm_move_last(struct device *dev) {}
 
 #endif
 
index 611d2e001dd5f784a10e6b5bcd45224ce812c53f..e28f8ae534539c4014b095409ed51017504a0868 100644 (file)
@@ -799,7 +799,7 @@ static void sch_attach_disconnected_device(struct subchannel *sch,
                return;
        other_sch = to_subchannel(cdev->dev.parent);
        /* Note: device_move() changes cdev->dev.parent */
-       ret = device_move(&cdev->dev, &sch->dev);
+       ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
        if (ret) {
                CIO_MSG_EVENT(0, "Moving disconnected device 0.%x.%04x failed "
                              "(ret=%d)!\n", cdev->private->dev_id.ssid,
@@ -830,7 +830,7 @@ static void sch_attach_orphaned_device(struct subchannel *sch,
         * Try to move the ccw device to its new subchannel.
         * Note: device_move() changes cdev->dev.parent
         */
-       ret = device_move(&cdev->dev, &sch->dev);
+       ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
        if (ret) {
                CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
                              "failed (ret=%d)!\n",
@@ -897,7 +897,8 @@ void ccw_device_move_to_orphanage(struct work_struct *work)
         * ccw device can take its place on the subchannel.
         * Note: device_move() changes cdev->dev.parent
         */
-       ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev);
+       ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev,
+               DPM_ORDER_NONE);
        if (ret) {
                CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
                              "(ret=%d)!\n", cdev->private->dev_id.ssid,
@@ -1129,7 +1130,7 @@ static void ccw_device_move_to_sch(struct work_struct *work)
         * Try to move the ccw device to its new subchannel.
         * Note: device_move() changes cdev->dev.parent
         */
-       rc = device_move(&cdev->dev, &sch->dev);
+       rc = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
        mutex_unlock(&sch->reg_mutex);
        if (rc) {
                CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to subchannel "
index 914c1016dd8f45941c07c763a34444ccc23afde2..f98d0cfb4f81e6d05b269d81912a18e5f2021018 100644 (file)
@@ -494,7 +494,8 @@ extern int device_for_each_child(struct device *dev, void *data,
 extern struct device *device_find_child(struct device *dev, void *data,
                                int (*match)(struct device *dev, void *data));
 extern int device_rename(struct device *dev, char *new_name);
-extern int device_move(struct device *dev, struct device *new_parent);
+extern int device_move(struct device *dev, struct device *new_parent,
+                      enum dpm_order dpm_order);
 
 /*
  * Root device objects for grouping under /sys/devices
index 24ba5f67b3a3a590480b6ad32b62326be2156c9f..1d4e2d289821d1d74f9e203dc966ed0ed1e5e0ef 100644 (file)
@@ -400,6 +400,9 @@ extern void __suspend_report_result(const char *function, void *fn, int ret);
 
 #else /* !CONFIG_PM_SLEEP */
 
+#define device_pm_lock() do {} while (0)
+#define device_pm_unlock() do {} while (0)
+
 static inline int device_suspend(pm_message_t state)
 {
        return 0;
@@ -409,6 +412,14 @@ static inline int device_suspend(pm_message_t state)
 
 #endif /* !CONFIG_PM_SLEEP */
 
+/* How to reorder dpm_list after device_move() */
+enum dpm_order {
+       DPM_ORDER_NONE,
+       DPM_ORDER_DEV_AFTER_PARENT,
+       DPM_ORDER_PARENT_BEFORE_DEV,
+       DPM_ORDER_DEV_LAST,
+};
+
 /*
  * Global Power Management flags
  * Used to keep APM and ACPI from both being active
index 1a1f916be44e258a853fcbf4d60483199c976c32..ed82796d4a0fe353a2ac8722221c479957c56907 100644 (file)
@@ -140,7 +140,7 @@ static void del_conn(struct work_struct *work)
                dev = device_find_child(&conn->dev, NULL, __match_tty);
                if (!dev)
                        break;
-               device_move(dev, NULL);
+               device_move(dev, NULL, DPM_ORDER_DEV_LAST);
                put_device(dev);
        }
 
index d030c69cb5a306ee2cf8c89584e286fc4cf1afb9..abdc703a11d2709ea0305f1cf0b9d9ff0965818a 100644 (file)
@@ -731,7 +731,8 @@ static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
        remove_wait_queue(&dev->wait, &wait);
 
        if (err == 0)
-               device_move(dev->tty_dev, rfcomm_get_device(dev));
+               device_move(dev->tty_dev, rfcomm_get_device(dev),
+                           DPM_ORDER_DEV_AFTER_PARENT);
 
        rfcomm_tty_copy_pending(dev);
 
@@ -751,7 +752,7 @@ static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
 
        if (atomic_dec_and_test(&dev->opened)) {
                if (dev->tty_dev->parent)
-                       device_move(dev->tty_dev, NULL);
+                       device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST);
 
                /* Close DLC and dettach TTY */
                rfcomm_dlc_close(dev->dlc, 0);