From 311aab73d273eb22be976055f6cab224f7279d5e Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 8 Aug 2011 23:39:36 +0200 Subject: PM / Runtime: Add might_sleep() to runtime PM functions Some of the entry points to pm runtime are not safe to call in atomic context unless pm_runtime_irq_safe() has been called. Inspecting the code, it is not immediately obvious that the functions sleep at all, as they run inside a spin_lock_irqsave, but under some conditions they can drop the lock and turn on irqs. If a driver incorrectly calls the pm_runtime apis, it can cause sleeping and irq processing when it expects to stay in atomic context. Add might_sleep_if to the majority of the __pm_runtime_* entry points to enforce correct usage. Add pm_runtime_put_sync_autosuspend to the list of functions that can be called in atomic context. Signed-off-by: Colin Cross Reviewed-by: Kevin Hilman Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index 4ce5450ab6e..62f37bc3866 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt @@ -484,6 +484,7 @@ pm_runtime_resume() pm_runtime_get_sync() pm_runtime_put_sync() pm_runtime_put_sync_suspend() +pm_runtime_put_sync_autosuspend() 5. Runtime PM Initialization, Device Probing and Removal -- cgit v1.2.3-70-g09d2 From 5b1b0b812a7b1a5b968c5d06d90d1cb88621b941 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 19 Aug 2011 23:49:48 +0200 Subject: PM / Runtime: Add macro to test for runtime PM events This patch (as1482) adds a macro for testing whether or not a pm_message value represents an autosuspend or autoresume (i.e., a runtime PM) event. Encapsulating this notion seems preferable to open-coding the test all over the place. Signed-off-by: Alan Stern Acked-by: Greg Kroah-Hartman Signed-off-by: Rafael J. Wysocki --- Documentation/usb/power-management.txt | 8 ++++---- drivers/bluetooth/btusb.c | 2 +- drivers/hid/hid-picolcd.c | 2 +- drivers/hid/usbhid/hid-core.c | 7 +++---- drivers/net/usb/usbnet.c | 2 +- drivers/net/wimax/i2400m/usb.c | 4 ++-- drivers/usb/class/cdc-acm.c | 2 +- drivers/usb/class/cdc-wdm.c | 6 +++--- drivers/usb/core/driver.c | 9 ++++----- drivers/usb/core/hcd.c | 4 ++-- drivers/usb/core/hub.c | 10 +++++----- drivers/usb/serial/sierra.c | 2 +- drivers/usb/serial/usb_wwan.c | 2 +- include/linux/pm.h | 2 ++ sound/usb/card.c | 2 +- 15 files changed, 32 insertions(+), 32 deletions(-) (limited to 'Documentation') diff --git a/Documentation/usb/power-management.txt b/Documentation/usb/power-management.txt index c9ffa9ced7e..e8662a5fbc5 100644 --- a/Documentation/usb/power-management.txt +++ b/Documentation/usb/power-management.txt @@ -439,10 +439,10 @@ cause autosuspends to fail with -EBUSY if the driver needs to use the device. External suspend calls should never be allowed to fail in this way, -only autosuspend calls. The driver can tell them apart by checking -the PM_EVENT_AUTO bit in the message.event argument to the suspend -method; this bit will be set for internal PM events (autosuspend) and -clear for external PM events. +only autosuspend calls. The driver can tell them apart by applying +the PMSG_IS_AUTO() macro to the message argument to the suspend +method; it will return True for internal PM events (autosuspend) and +False for external PM events. Mutual exclusion diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 91d13a9e8c6..91b190c4049 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1103,7 +1103,7 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) return 0; spin_lock_irq(&data->txlock); - if (!((message.event & PM_EVENT_AUTO) && data->tx_in_flight)) { + if (!(PMSG_IS_AUTO(message) && data->tx_in_flight)) { set_bit(BTUSB_SUSPENDING, &data->flags); spin_unlock_irq(&data->txlock); } else { diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 9d8710f8bc7..1782693819f 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -2409,7 +2409,7 @@ static int picolcd_raw_event(struct hid_device *hdev, #ifdef CONFIG_PM static int picolcd_suspend(struct hid_device *hdev, pm_message_t message) { - if (message.event & PM_EVENT_AUTO) + if (PMSG_IS_AUTO(message)) return 0; picolcd_suspend_backlight(hid_get_drvdata(hdev)); diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index ad978f5748d..a9fa294ee7d 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1332,7 +1332,7 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) struct usbhid_device *usbhid = hid->driver_data; int status; - if (message.event & PM_EVENT_AUTO) { + if (PMSG_IS_AUTO(message)) { spin_lock_irq(&usbhid->lock); /* Sync with error handler */ if (!test_bit(HID_RESET_PENDING, &usbhid->iofl) && !test_bit(HID_CLEAR_HALT, &usbhid->iofl) @@ -1367,7 +1367,7 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) return -EIO; } - if (!ignoreled && (message.event & PM_EVENT_AUTO)) { + if (!ignoreled && PMSG_IS_AUTO(message)) { spin_lock_irq(&usbhid->lock); if (test_bit(HID_LED_ON, &usbhid->iofl)) { spin_unlock_irq(&usbhid->lock); @@ -1380,8 +1380,7 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) hid_cancel_delayed_stuff(usbhid); hid_cease_io(usbhid); - if ((message.event & PM_EVENT_AUTO) && - test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) { + if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) { /* lost race against keypresses */ status = hid_start_in(hid); if (status < 0) diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index ce395fe5de2..f1c435ba528 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1470,7 +1470,7 @@ int usbnet_suspend (struct usb_interface *intf, pm_message_t message) if (!dev->suspend_count++) { spin_lock_irq(&dev->txq.lock); /* don't autosuspend while transmitting */ - if (dev->txq.qlen && (message.event & PM_EVENT_AUTO)) { + if (dev->txq.qlen && PMSG_IS_AUTO(message)) { spin_unlock_irq(&dev->txq.lock); return -EBUSY; } else { diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c index 298f2b0b631..9a644d052f1 100644 --- a/drivers/net/wimax/i2400m/usb.c +++ b/drivers/net/wimax/i2400m/usb.c @@ -599,7 +599,7 @@ void i2400mu_disconnect(struct usb_interface *iface) * * As well, the device might refuse going to sleep for whichever * reason. In this case we just fail. For system suspend/hibernate, - * we *can't* fail. We check PM_EVENT_AUTO to see if the + * we *can't* fail. We check PMSG_IS_AUTO to see if the * suspend call comes from the USB stack or from the system and act * in consequence. * @@ -615,7 +615,7 @@ int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg) struct i2400m *i2400m = &i2400mu->i2400m; #ifdef CONFIG_PM - if (pm_msg.event & PM_EVENT_AUTO) + if (PMSG_IS_AUTO(pm_msg)) is_autosuspend = 1; #endif diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index dac7676ce21..94e6c5c09dd 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1305,7 +1305,7 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message) struct acm *acm = usb_get_intfdata(intf); int cnt; - if (message.event & PM_EVENT_AUTO) { + if (PMSG_IS_AUTO(message)) { int b; spin_lock_irq(&acm->write_lock); diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 2b9ff518b50..42f180aca3f 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -798,11 +798,11 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); /* if this is an autosuspend the caller does the locking */ - if (!(message.event & PM_EVENT_AUTO)) + if (!PMSG_IS_AUTO(message)) mutex_lock(&desc->lock); spin_lock_irq(&desc->iuspin); - if ((message.event & PM_EVENT_AUTO) && + if (PMSG_IS_AUTO(message) && (test_bit(WDM_IN_USE, &desc->flags) || test_bit(WDM_RESPONDING, &desc->flags))) { spin_unlock_irq(&desc->iuspin); @@ -815,7 +815,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) kill_urbs(desc); cancel_work_sync(&desc->rxwork); } - if (!(message.event & PM_EVENT_AUTO)) + if (!PMSG_IS_AUTO(message)) mutex_unlock(&desc->lock); return rv; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 34e3da5aa72..e03042883c6 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1046,8 +1046,7 @@ static int usb_resume_device(struct usb_device *udev, pm_message_t msg) /* Non-root devices on a full/low-speed bus must wait for their * companion high-speed root hub, in case a handoff is needed. */ - if (!(msg.event & PM_EVENT_AUTO) && udev->parent && - udev->bus->hs_companion) + if (!PMSG_IS_AUTO(msg) && udev->parent && udev->bus->hs_companion) device_pm_wait_for_dev(&udev->dev, &udev->bus->hs_companion->root_hub->dev); @@ -1075,7 +1074,7 @@ static int usb_suspend_interface(struct usb_device *udev, if (driver->suspend) { status = driver->suspend(intf, msg); - if (status && !(msg.event & PM_EVENT_AUTO)) + if (status && !PMSG_IS_AUTO(msg)) dev_err(&intf->dev, "%s error %d\n", "suspend", status); } else { @@ -1189,7 +1188,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) status = usb_suspend_interface(udev, intf, msg); /* Ignore errors during system sleep transitions */ - if (!(msg.event & PM_EVENT_AUTO)) + if (!PMSG_IS_AUTO(msg)) status = 0; if (status != 0) break; @@ -1199,7 +1198,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) status = usb_suspend_device(udev, msg); /* Again, ignore errors during system sleep transitions */ - if (!(msg.event & PM_EVENT_AUTO)) + if (!PMSG_IS_AUTO(msg)) status = 0; } diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 8669ba3fe79..da582f4e486 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1960,7 +1960,7 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) int old_state = hcd->state; dev_dbg(&rhdev->dev, "bus %s%s\n", - (msg.event & PM_EVENT_AUTO ? "auto-" : ""), "suspend"); + (PMSG_IS_AUTO(msg) ? "auto-" : ""), "suspend"); if (HCD_DEAD(hcd)) { dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "suspend"); return 0; @@ -1996,7 +1996,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) int old_state = hcd->state; dev_dbg(&rhdev->dev, "usb %s%s\n", - (msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume"); + (PMSG_IS_AUTO(msg) ? "auto-" : ""), "resume"); if (HCD_DEAD(hcd)) { dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume"); return 0; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a428aa080a3..ee50e0bf84e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2342,7 +2342,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) dev_dbg(&udev->dev, "won't remote wakeup, status %d\n", status); /* bail if autosuspend is requested */ - if (msg.event & PM_EVENT_AUTO) + if (PMSG_IS_AUTO(msg)) return status; } } @@ -2367,12 +2367,12 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) USB_CTRL_SET_TIMEOUT); /* System sleep transitions should never fail */ - if (!(msg.event & PM_EVENT_AUTO)) + if (!PMSG_IS_AUTO(msg)) status = 0; } else { /* device has up to 10 msec to fully suspend */ dev_dbg(&udev->dev, "usb %ssuspend\n", - (msg.event & PM_EVENT_AUTO ? "auto-" : "")); + (PMSG_IS_AUTO(msg) ? "auto-" : "")); usb_set_device_state(udev, USB_STATE_SUSPENDED); msleep(10); } @@ -2523,7 +2523,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } else { /* drive resume for at least 20 msec */ dev_dbg(&udev->dev, "usb %sresume\n", - (msg.event & PM_EVENT_AUTO ? "auto-" : "")); + (PMSG_IS_AUTO(msg) ? "auto-" : "")); msleep(25); /* Virtual root hubs can trigger on GET_PORT_STATUS to @@ -2625,7 +2625,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) udev = hdev->children [port1-1]; if (udev && udev->can_submit) { dev_warn(&intf->dev, "port %d nyet suspended\n", port1); - if (msg.event & PM_EVENT_AUTO) + if (PMSG_IS_AUTO(msg)) return -EBUSY; } } diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index d5d136a53b6..b18179bda0d 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -1009,7 +1009,7 @@ static int sierra_suspend(struct usb_serial *serial, pm_message_t message) struct sierra_intf_private *intfdata; int b; - if (message.event & PM_EVENT_AUTO) { + if (PMSG_IS_AUTO(message)) { intfdata = serial->private; spin_lock_irq(&intfdata->susp_lock); b = intfdata->in_flight; diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index e4fad5e643d..d555ca9567b 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -651,7 +651,7 @@ int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message) dbg("%s entered", __func__); - if (message.event & PM_EVENT_AUTO) { + if (PMSG_IS_AUTO(message)) { spin_lock_irq(&intfdata->susp_lock); b = intfdata->in_flight; spin_unlock_irq(&intfdata->susp_lock); diff --git a/include/linux/pm.h b/include/linux/pm.h index f7c84c9abd3..18de9f89349 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -366,6 +366,8 @@ extern struct dev_pm_ops generic_subsys_pm_ops; #define PMSG_AUTO_RESUME ((struct pm_message) \ { .event = PM_EVENT_AUTO_RESUME, }) +#define PMSG_IS_AUTO(msg) (((msg).event & PM_EVENT_AUTO) != 0) + /** * Device run-time power management status. * diff --git a/sound/usb/card.c b/sound/usb/card.c index 781d9e61adf..d5754fa5e55 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -628,7 +628,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) if (chip == (void *)-1L) return 0; - if (!(message.event & PM_EVENT_AUTO)) { + if (!PMSG_IS_AUTO(message)) { snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); if (!chip->num_suspended_intf++) { list_for_each(p, &chip->pcm_list) { -- cgit v1.2.3-70-g09d2 From 2e6ba515f50ef7ddf35b2703d014d3216c9b8b24 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 21 Sep 2011 22:31:33 +0200 Subject: PM / Runtime: pm_runtime_idle() can be called in atomic context Add to pm_runtime_idle the list of functions that can be called in atomic context if pm_runtime_irq_safe() has been called for the device. Signed-off-by: Ming Lei Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index 62f37bc3866..1750740b17e 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt @@ -478,6 +478,7 @@ pm_runtime_autosuspend_expiration() If pm_runtime_irq_safe() has been called for a device then the following helper functions may also be used in interrupt context: +pm_runtime_idle() pm_runtime_suspend() pm_runtime_autosuspend() pm_runtime_resume() -- cgit v1.2.3-70-g09d2 From 9005b65099ee4f14b6be691c4574612fe947531a Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Sun, 2 Oct 2011 00:19:28 +0200 Subject: PM / devfreq: Add common sysfs interfaces Device specific sysfs interface /sys/devices/.../power/devfreq_* - governor R: name of governor - cur_freq R: current frequency - polling_interval R: polling interval in ms given with devfreq profile W: update polling interval. - central_polling R: 1 if polling is managed by devfreq framework Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mike Turquette Acked-by: Kevin Hilman Signed-off-by: Rafael J. Wysocki -- Documentation/ABI/testing/sysfs-class-devfreq | 44 ++++++++++++++++ drivers/devfreq/devfreq.c | 69 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-devfreq --- Documentation/ABI/testing/sysfs-class-devfreq | 44 +++++++++++++++++ drivers/devfreq/devfreq.c | 69 +++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-devfreq (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-devfreq b/Documentation/ABI/testing/sysfs-class-devfreq new file mode 100644 index 00000000000..0ec855f479c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-devfreq @@ -0,0 +1,44 @@ +What: /sys/class/devfreq/.../ +Date: September 2011 +Contact: MyungJoo Ham +Description: + Provide a place in sysfs for the devfreq objects. + This allows accessing various devfreq specific variables. + The name of devfreq object denoted as ... is same as the + name of device using devfreq. + +What: /sys/class/devfreq/.../governor +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../governor shows the name of the + governor used by the corresponding devfreq object. + +What: /sys/class/devfreq/.../cur_freq +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../cur_freq shows the current + frequency of the corresponding devfreq object. + +What: /sys/class/devfreq/.../central_polling +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../central_polling shows whether + the devfreq ojbect is using devfreq-provided central + polling mechanism or not. + +What: /sys/class/devfreq/.../polling_interval +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../polling_interval shows and sets + the requested polling interval of the corresponding devfreq + object. The values are represented in ms. If the value is + less than 1 jiffy, it is considered to be 0, which means + no polling. This value is meaningless if the governor is + not polling; thus. If the governor is not using + devfreq-provided central polling + (/sys/class/devfreq/.../central_polling is 0), this value + may be useless. diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index f3100b19f79..5d15b812377 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -437,6 +437,74 @@ int devfreq_remove_device(struct devfreq *devfreq) return 0; } +static ssize_t show_governor(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", to_devfreq(dev)->governor->name); +} + +static ssize_t show_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->previous_freq); +} + +static ssize_t show_polling_interval(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", to_devfreq(dev)->profile->polling_ms); +} + +static ssize_t store_polling_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned int value; + int ret; + + ret = sscanf(buf, "%u", &value); + if (ret != 1) + goto out; + + mutex_lock(&df->lock); + df->profile->polling_ms = value; + df->next_polling = df->polling_jiffies + = msecs_to_jiffies(value); + mutex_unlock(&df->lock); + + ret = count; + + if (df->governor->no_central_polling) + goto out; + + mutex_lock(&devfreq_list_lock); + if (df->next_polling > 0 && !polling) { + polling = true; + queue_delayed_work(devfreq_wq, &devfreq_work, + df->next_polling); + } + mutex_unlock(&devfreq_list_lock); +out: + return ret; +} + +static ssize_t show_central_polling(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", + !to_devfreq(dev)->governor->no_central_polling); +} + +static struct device_attribute devfreq_attrs[] = { + __ATTR(governor, S_IRUGO, show_governor, NULL), + __ATTR(cur_freq, S_IRUGO, show_freq, NULL), + __ATTR(central_polling, S_IRUGO, show_central_polling, NULL), + __ATTR(polling_interval, S_IRUGO | S_IWUSR, show_polling_interval, + store_polling_interval), + { }, +}; + /** * devfreq_start_polling() - Initialize data structure for devfreq framework and * start polling registered devfreq devices. @@ -461,6 +529,7 @@ static int __init devfreq_init(void) pr_err("%s: couldn't create class\n", __FILE__); return PTR_ERR(devfreq_class); } + devfreq_class->dev_attrs = devfreq_attrs; return 0; } subsys_initcall(devfreq_init); -- cgit v1.2.3-70-g09d2 From ce26c5bb9569d8b826f01b8620fc16d8da6821e9 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Sun, 2 Oct 2011 00:19:34 +0200 Subject: PM / devfreq: Add basic governors Four cpufreq-like governors are provided as examples. powersave: use the lowest frequency possible. The user (device) should set the polling_ms as 0 because polling is useless for this governor. performance: use the highest freqeuncy possible. The user (device) should set the polling_ms as 0 because polling is useless for this governor. userspace: use the user specified frequency stored at devfreq.user_set_freq. With sysfs support in the following patch, a user may set the value with the sysfs interface. simple_ondemand: simplified version of cpufreq's ondemand governor. When a user updates OPP entries (enable/disable/add), OPP framework automatically notifies devfreq to update operating frequency accordingly. Thus, devfreq users (device drivers) do not need to update devfreq manually with OPP entry updates or set polling_ms for powersave , performance, userspace, or any other "static" governors. Note that these are given only as basic examples for governors and any devices with devfreq may implement their own governors with the drivers and use them. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mike Turquette Acked-by: Kevin Hilman Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-class-devfreq | 8 ++ drivers/devfreq/Kconfig | 36 ++++++++ drivers/devfreq/Makefile | 4 + drivers/devfreq/governor_performance.c | 29 +++++++ drivers/devfreq/governor_powersave.c | 29 +++++++ drivers/devfreq/governor_simpleondemand.c | 88 +++++++++++++++++++ drivers/devfreq/governor_userspace.c | 116 ++++++++++++++++++++++++++ include/linux/devfreq.h | 35 ++++++++ 8 files changed, 345 insertions(+) create mode 100644 drivers/devfreq/governor_performance.c create mode 100644 drivers/devfreq/governor_powersave.c create mode 100644 drivers/devfreq/governor_simpleondemand.c create mode 100644 drivers/devfreq/governor_userspace.c (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-devfreq b/Documentation/ABI/testing/sysfs-class-devfreq index 0ec855f479c..23d78b5aab1 100644 --- a/Documentation/ABI/testing/sysfs-class-devfreq +++ b/Documentation/ABI/testing/sysfs-class-devfreq @@ -42,3 +42,11 @@ Description: devfreq-provided central polling (/sys/class/devfreq/.../central_polling is 0), this value may be useless. + +What: /sys/class/devfreq/.../userspace/set_freq +Date: September 2011 +Contact: MyungJoo Ham +Description: + The /sys/class/devfreq/.../userspace/set_freq shows and + sets the requested frequency for the devfreq object if + userspace governor is in effect. diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 1fb42de4f42..643b055ed3c 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -34,6 +34,42 @@ menuconfig PM_DEVFREQ if PM_DEVFREQ +comment "DEVFREQ Governors" + +config DEVFREQ_GOV_SIMPLE_ONDEMAND + bool "Simple Ondemand" + help + Chooses frequency based on the recent load on the device. Works + similar as ONDEMAND governor of CPUFREQ does. A device with + Simple-Ondemand should be able to provide busy/total counter + values that imply the usage rate. A device may provide tuned + values to the governor with data field at devfreq_add_device(). + +config DEVFREQ_GOV_PERFORMANCE + bool "Performance" + help + Sets the frequency at the maximum available frequency. + This governor always returns UINT_MAX as frequency so that + the DEVFREQ framework returns the highest frequency available + at any time. + +config DEVFREQ_GOV_POWERSAVE + bool "Powersave" + help + Sets the frequency at the minimum available frequency. + This governor always returns 0 as frequency so that + the DEVFREQ framework returns the lowest frequency available + at any time. + +config DEVFREQ_GOV_USERSPACE + bool "Userspace" + help + Sets the frequency at the user specified one. + This governor returns the user configured frequency if there + has been an input to /sys/devices/.../power/devfreq_set_freq. + Otherwise, the governor does not change the frequnecy + given at the initialization. + comment "DEVFREQ Drivers" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 168934a12b3..4564a89e970 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -1 +1,5 @@ obj-$(CONFIG_PM_DEVFREQ) += devfreq.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o +obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o +obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o +obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c new file mode 100644 index 00000000000..c0596b29176 --- /dev/null +++ b/drivers/devfreq/governor_performance.c @@ -0,0 +1,29 @@ +/* + * linux/drivers/devfreq/governor_performance.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +static int devfreq_performance_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get floor value as + * said in devfreq.h + */ + *freq = UINT_MAX; + return 0; +} + +const struct devfreq_governor devfreq_performance = { + .name = "performance", + .get_target_freq = devfreq_performance_func, + .no_central_polling = true, +}; diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c new file mode 100644 index 00000000000..2483a85a266 --- /dev/null +++ b/drivers/devfreq/governor_powersave.c @@ -0,0 +1,29 @@ +/* + * linux/drivers/devfreq/governor_powersave.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +static int devfreq_powersave_func(struct devfreq *df, + unsigned long *freq) +{ + /* + * target callback should be able to get ceiling value as + * said in devfreq.h + */ + *freq = 0; + return 0; +} + +const struct devfreq_governor devfreq_powersave = { + .name = "powersave", + .get_target_freq = devfreq_powersave_func, + .no_central_polling = true, +}; diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c new file mode 100644 index 00000000000..efad8dcf902 --- /dev/null +++ b/drivers/devfreq/governor_simpleondemand.c @@ -0,0 +1,88 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +/* Default constants for DevFreq-Simple-Ondemand (DFSO) */ +#define DFSO_UPTHRESHOLD (90) +#define DFSO_DOWNDIFFERENCTIAL (5) +static int devfreq_simple_ondemand_func(struct devfreq *df, + unsigned long *freq) +{ + struct devfreq_dev_status stat; + int err = df->profile->get_dev_status(df->dev.parent, &stat); + unsigned long long a, b; + unsigned int dfso_upthreshold = DFSO_UPTHRESHOLD; + unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL; + struct devfreq_simple_ondemand_data *data = df->data; + + if (err) + return err; + + if (data) { + if (data->upthreshold) + dfso_upthreshold = data->upthreshold; + if (data->downdifferential) + dfso_downdifferential = data->downdifferential; + } + if (dfso_upthreshold > 100 || + dfso_upthreshold < dfso_downdifferential) + return -EINVAL; + + /* Assume MAX if it is going to be divided by zero */ + if (stat.total_time == 0) { + *freq = UINT_MAX; + return 0; + } + + /* Prevent overflow */ + if (stat.busy_time >= (1 << 24) || stat.total_time >= (1 << 24)) { + stat.busy_time >>= 7; + stat.total_time >>= 7; + } + + /* Set MAX if it's busy enough */ + if (stat.busy_time * 100 > + stat.total_time * dfso_upthreshold) { + *freq = UINT_MAX; + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat.current_frequency == 0) { + *freq = UINT_MAX; + return 0; + } + + /* Keep the current frequency */ + if (stat.busy_time * 100 > + stat.total_time * (dfso_upthreshold - dfso_downdifferential)) { + *freq = stat.current_frequency; + return 0; + } + + /* Set the desired frequency based on the load */ + a = stat.busy_time; + a *= stat.current_frequency; + b = div_u64(a, stat.total_time); + b *= 100; + b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); + *freq = (unsigned long) b; + + return 0; +} + +const struct devfreq_governor devfreq_simple_ondemand = { + .name = "simple_ondemand", + .get_target_freq = devfreq_simple_ondemand_func, +}; diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c new file mode 100644 index 00000000000..4f8b563da78 --- /dev/null +++ b/drivers/devfreq/governor_userspace.c @@ -0,0 +1,116 @@ +/* + * linux/drivers/devfreq/governor_simpleondemand.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include "governor.h" + +struct userspace_data { + unsigned long user_frequency; + bool valid; +}; + +static int devfreq_userspace_func(struct devfreq *df, unsigned long *freq) +{ + struct userspace_data *data = df->data; + + if (!data->valid) + *freq = df->previous_freq; /* No user freq specified yet */ + else + *freq = data->user_frequency; + return 0; +} + +static ssize_t store_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + int err = 0; + + + mutex_lock(&devfreq->lock); + data = devfreq->data; + + sscanf(buf, "%lu", &wanted); + data->user_frequency = wanted; + data->valid = true; + err = update_devfreq(devfreq); + if (err == 0) + err = count; + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t show_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + + if (data->valid) + err = sprintf(buf, "%lu\n", data->user_frequency); + else + err = sprintf(buf, "undefined\n"); + mutex_unlock(&devfreq->lock); + return err; +} + +static DEVICE_ATTR(set_freq, 0644, show_freq, store_freq); +static struct attribute *dev_entries[] = { + &dev_attr_set_freq.attr, + NULL, +}; +static struct attribute_group dev_attr_group = { + .name = "userspace", + .attrs = dev_entries, +}; + +static int userspace_init(struct devfreq *devfreq) +{ + int err = 0; + struct userspace_data *data = kzalloc(sizeof(struct userspace_data), + GFP_KERNEL); + + if (!data) { + err = -ENOMEM; + goto out; + } + data->valid = false; + devfreq->data = data; + + err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); +out: + return err; +} + +static void userspace_exit(struct devfreq *devfreq) +{ + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group); + kfree(devfreq->data); + devfreq->data = NULL; +} + +const struct devfreq_governor devfreq_userspace = { + .name = "userspace", + .get_target_freq = devfreq_userspace_func, + .init = userspace_init, + .exit = userspace_exit, + .no_central_polling = true, +}; diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index b3be3d3cbaa..afb94583960 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -166,6 +166,36 @@ extern int devfreq_register_opp_notifier(struct device *dev, extern int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq); +#ifdef CONFIG_DEVFREQ_GOV_POWERSAVE +extern const struct devfreq_governor devfreq_powersave; +#endif +#ifdef CONFIG_DEVFREQ_GOV_PERFORMANCE +extern const struct devfreq_governor devfreq_performance; +#endif +#ifdef CONFIG_DEVFREQ_GOV_USERSPACE +extern const struct devfreq_governor devfreq_userspace; +#endif +#ifdef CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND +extern const struct devfreq_governor devfreq_simple_ondemand; +/** + * struct devfreq_simple_ondemand_data - void *data fed to struct devfreq + * and devfreq_add_device + * @ upthreshold If the load is over this value, the frequency jumps. + * Specify 0 to use the default. Valid value = 0 to 100. + * @ downdifferential If the load is under upthreshold - downdifferential, + * the governor may consider slowing the frequency down. + * Specify 0 to use the default. Valid value = 0 to 100. + * downdifferential < upthreshold must hold. + * + * If the fed devfreq_simple_ondemand_data pointer is NULL to the governor, + * the governor uses the default values. + */ +struct devfreq_simple_ondemand_data { + unsigned int upthreshold; + unsigned int downdifferential; +}; +#endif + #else /* !CONFIG_PM_DEVFREQ */ static struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, @@ -198,6 +228,11 @@ static int devfreq_unregister_opp_notifier(struct device *dev, return -EINVAL; } +#define devfreq_powersave NULL +#define devfreq_performance NULL +#define devfreq_userspace NULL +#define devfreq_simple_ondemand NULL + #endif /* CONFIG_PM_DEVFREQ */ #endif /* __LINUX_DEVFREQ_H__ */ -- cgit v1.2.3-70-g09d2 From e3cba3243eb853a052613c804dea033bc4c9cf2d Mon Sep 17 00:00:00 2001 From: Jean Pihet Date: Tue, 4 Oct 2011 21:54:45 +0200 Subject: PM / QoS: Update Documentation for the pm_qos and dev_pm_qos frameworks Update the documentation for the recently updated pm_qos API, kernel and user space. Add documentation for the per-device PM QoS (dev_pm_qos) framework API. Signed-off-by: Jean Pihet Signed-off-by: Rafael J. Wysocki --- Documentation/power/pm_qos_interface.txt | 92 ++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/power/pm_qos_interface.txt b/Documentation/power/pm_qos_interface.txt index bfed898a03f..17e130a8034 100644 --- a/Documentation/power/pm_qos_interface.txt +++ b/Documentation/power/pm_qos_interface.txt @@ -4,14 +4,19 @@ This interface provides a kernel and user mode interface for registering performance expectations by drivers, subsystems and user space applications on one of the parameters. -Currently we have {cpu_dma_latency, network_latency, network_throughput} as the -initial set of pm_qos parameters. +Two different PM QoS frameworks are available: +1. PM QoS classes for cpu_dma_latency, network_latency, network_throughput. +2. the per-device PM QoS framework provides the API to manage the per-device latency +constraints. Each parameters have defined units: * latency: usec * timeout: usec * throughput: kbs (kilo bit / sec) + +1. PM QoS framework + The infrastructure exposes multiple misc device nodes one per implemented parameter. The set of parameters implement is defined by pm_qos_power_init() and pm_qos_params.h. This is done because having the available parameters @@ -23,14 +28,18 @@ an aggregated target value. The aggregated target value is updated with changes to the request list or elements of the list. Typically the aggregated target value is simply the max or min of the request values held in the parameter list elements. +Note: the aggregated target value is implemented as an atomic variable so that +reading the aggregated value does not require any locking mechanism. + From kernel mode the use of this interface is simple: -handle = pm_qos_add_request(param_class, target_value): -Will insert an element into the list for that identified PM_QOS class with the +void pm_qos_add_request(handle, param_class, target_value): +Will insert an element into the list for that identified PM QoS class with the target value. Upon change to this list the new target is recomputed and any registered notifiers are called only if the target value is now different. -Clients of pm_qos need to save the returned handle. +Clients of pm_qos need to save the returned handle for future use in other +pm_qos API functions. void pm_qos_update_request(handle, new_target_value): Will update the list element pointed to by the handle with the new target value @@ -42,6 +51,20 @@ Will remove the element. After removal it will update the aggregate target and call the notification tree if the target was changed as a result of removing the request. +int pm_qos_request(param_class): +Returns the aggregated value for a given PM QoS class. + +int pm_qos_request_active(handle): +Returns if the request is still active, i.e. it has not been removed from a +PM QoS class constraints list. + +int pm_qos_add_notifier(param_class, notifier): +Adds a notification callback function to the PM QoS class. The callback is +called when the aggregated value for the PM QoS class is changed. + +int pm_qos_remove_notifier(int param_class, notifier): +Removes the notification callback function for the PM QoS class. + From user mode: Only processes can register a pm_qos request. To provide for automatic @@ -63,4 +86,63 @@ To remove the user mode request for a target value simply close the device node. +2. PM QoS per-device latency framework + +For each device a list of performance requests is maintained along with +an aggregated target value. The aggregated target value is updated with +changes to the request list or elements of the list. Typically the +aggregated target value is simply the max or min of the request values held +in the parameter list elements. +Note: the aggregated target value is implemented as an atomic variable so that +reading the aggregated value does not require any locking mechanism. + + +From kernel mode the use of this interface is the following: + +int dev_pm_qos_add_request(device, handle, value): +Will insert an element into the list for that identified device with the +target value. Upon change to this list the new target is recomputed and any +registered notifiers are called only if the target value is now different. +Clients of dev_pm_qos need to save the handle for future use in other +dev_pm_qos API functions. + +int dev_pm_qos_update_request(handle, new_value): +Will update the list element pointed to by the handle with the new target value +and recompute the new aggregated target, calling the notification trees if the +target is changed. + +int dev_pm_qos_remove_request(handle): +Will remove the element. After removal it will update the aggregate target and +call the notification trees if the target was changed as a result of removing +the request. + +s32 dev_pm_qos_read_value(device): +Returns the aggregated value for a given device's constraints list. + + +Notification mechanisms: +The per-device PM QoS framework has 2 different and distinct notification trees: +a per-device notification tree and a global notification tree. + +int dev_pm_qos_add_notifier(device, notifier): +Adds a notification callback function for the device. +The callback is called when the aggregated value of the device constraints list +is changed. + +int dev_pm_qos_remove_notifier(device, notifier): +Removes the notification callback function for the device. + +int dev_pm_qos_add_global_notifier(notifier): +Adds a notification callback function in the global notification tree of the +framework. +The callback is called when the aggregated value for any device is changed. + +int dev_pm_qos_remove_global_notifier(notifier): +Removes the notification callback function from the global notification tree +of the framework. + + +From user mode: +No API for user space access to the per-device latency constraints is provided +yet - still under discussion. -- cgit v1.2.3-70-g09d2 From 2fb242adcaab5defa2f208775ac4f181ac998fdd Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sun, 9 Oct 2011 11:40:25 +0800 Subject: PM / Runtime: Update document about callbacks Support for device power domains has been introduced in commit 9659cc0678b954f187290c6e8b247a673c5d37e1 (PM: Make system-wide PM and runtime PM treat subsystems consistently), also power domain callbacks will take precedence over subsystem ones from commit 4d27e9dcff00a6425d779b065ec8892e4f391661(PM: Make power domain callbacks take precedence over subsystem ones). So update part of "Device Runtime PM Callbacks" in Documentation/power/runtime_pm.txt. Signed-off-by: Ming Lei Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.txt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index 1750740b17e..f670836f8e5 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt @@ -43,13 +43,18 @@ struct dev_pm_ops { ... }; -The ->runtime_suspend(), ->runtime_resume() and ->runtime_idle() callbacks are -executed by the PM core for either the device type, or the class (if the device -type's struct dev_pm_ops object does not exist), or the bus type (if the -device type's and class' struct dev_pm_ops objects do not exist) of the given -device (this allows device types to override callbacks provided by bus types or -classes if necessary). The bus type, device type and class callbacks are -referred to as subsystem-level callbacks in what follows. +The ->runtime_suspend(), ->runtime_resume() and ->runtime_idle() callbacks +are executed by the PM core for either the power domain, or the device type +(if the device power domain's struct dev_pm_ops does not exist), or the class +(if the device power domain's and type's struct dev_pm_ops object does not +exist), or the bus type (if the device power domain's, type's and class' +struct dev_pm_ops objects do not exist) of the given device, so the priority +order of callbacks from high to low is that power domain callbacks, device +type callbacks, class callbacks and bus type callbacks, and the high priority +one will take precedence over low priority one. The bus type, device type and +class callbacks are referred to as subsystem-level callbacks in what follows, +and generally speaking, the power domain callbacks are used for representing +power domains within a SoC. By default, the callbacks are always invoked in process context with interrupts enabled. However, subsystems can use the pm_runtime_irq_safe() helper function -- cgit v1.2.3-70-g09d2 From 2a77c46de1e3dace73745015635ebbc648eca69c Mon Sep 17 00:00:00 2001 From: ShuoX Liu Date: Wed, 10 Aug 2011 23:01:26 +0200 Subject: PM / Suspend: Add statistics debugfs file for suspend to RAM Record S3 failure time about each reason and the latest two failed devices' names in S3 progress. We can check it through 'suspend_stats' entry in debugfs. The motivation of the patch: We are enabling power features on Medfield. Comparing with PC/notebook, a mobile enters/exits suspend-2-ram (we call it s3 on Medfield) far more frequently. If it can't enter suspend-2-ram in time, the power might be used up soon. We often find sometimes, a device suspend fails. Then, system retries s3 over and over again. As display is off, testers and developers don't know what happens. Some testers and developers complain they don't know if system tries suspend-2-ram, and what device fails to suspend. They need such info for a quick check. The patch adds suspend_stats under debugfs for users to check suspend to RAM statistics quickly. If not using this patch, we have other methods to get info about what device fails. One is to turn on CONFIG_PM_DEBUG, but users would get too much info and testers need recompile the system. In addition, dynamic debug is another good tool to dump debug info. But it still doesn't match our utilization scenario closely. 1) user need write a user space parser to process the syslog output; 2) Our testing scenario is we leave the mobile for at least hours. Then, check its status. No serial console available during the testing. One is because console would be suspended, and the other is serial console connecting with spi or HSU devices would consume power. These devices are powered off at suspend-2-ram. Signed-off-by: ShuoX Liu Signed-off-by: Rafael J. Wysocki --- Documentation/power/basic-pm-debugging.txt | 24 +++++++ drivers/base/power/main.c | 31 +++++++-- include/linux/suspend.h | 52 +++++++++++++++ kernel/power/main.c | 102 +++++++++++++++++++++++++++++ kernel/power/suspend.c | 17 ++++- 5 files changed, 218 insertions(+), 8 deletions(-) (limited to 'Documentation') diff --git a/Documentation/power/basic-pm-debugging.txt b/Documentation/power/basic-pm-debugging.txt index ddd78172ef7..62eca080a71 100644 --- a/Documentation/power/basic-pm-debugging.txt +++ b/Documentation/power/basic-pm-debugging.txt @@ -201,3 +201,27 @@ case, you may be able to search for failing drivers by following the procedure analogous to the one described in section 1. If you find some failing drivers, you will have to unload them every time before an STR transition (ie. before you run s2ram), and please report the problems with them. + +There is a debugfs entry which shows the suspend to RAM statistics. Here is an +example of its output. + # mount -t debugfs none /sys/kernel/debug + # cat /sys/kernel/debug/suspend_stats + success: 20 + fail: 5 + failed_freeze: 0 + failed_prepare: 0 + failed_suspend: 5 + failed_suspend_noirq: 0 + failed_resume: 0 + failed_resume_noirq: 0 + failures: + last_failed_dev: alarm + adc + last_failed_errno: -16 + -16 + last_failed_step: suspend + suspend +Field success means the success number of suspend to RAM, and field fail means +the failure number. Others are the failure number of different steps of suspend +to RAM. suspend_stats just lists the last 2 failed devices, error number and +failed step of suspend. diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index c6291ab725a..b1b58260b4f 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -46,6 +46,7 @@ LIST_HEAD(dpm_prepared_list); LIST_HEAD(dpm_suspended_list); LIST_HEAD(dpm_noirq_list); +struct suspend_stats suspend_stats; static DEFINE_MUTEX(dpm_list_mtx); static pm_message_t pm_transition; @@ -467,8 +468,12 @@ void dpm_resume_noirq(pm_message_t state) mutex_unlock(&dpm_list_mtx); error = device_resume_noirq(dev, state); - if (error) + if (error) { + suspend_stats.failed_resume_noirq++; + dpm_save_failed_step(SUSPEND_RESUME_NOIRQ); + dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, state, " early", error); + } mutex_lock(&dpm_list_mtx); put_device(dev); @@ -629,8 +634,12 @@ void dpm_resume(pm_message_t state) mutex_unlock(&dpm_list_mtx); error = device_resume(dev, state, false); - if (error) + if (error) { + suspend_stats.failed_resume++; + dpm_save_failed_step(SUSPEND_RESUME); + dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, state, "", error); + } mutex_lock(&dpm_list_mtx); } @@ -805,6 +814,9 @@ int dpm_suspend_noirq(pm_message_t state) mutex_lock(&dpm_list_mtx); if (error) { pm_dev_err(dev, state, " late", error); + suspend_stats.failed_suspend_noirq++; + dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ); + dpm_save_failed_dev(dev_name(dev)); put_device(dev); break; } @@ -926,8 +938,10 @@ static void async_suspend(void *data, async_cookie_t cookie) int error; error = __device_suspend(dev, pm_transition, true); - if (error) + if (error) { + dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, pm_transition, " async", error); + } put_device(dev); } @@ -970,6 +984,7 @@ int dpm_suspend(pm_message_t state) mutex_lock(&dpm_list_mtx); if (error) { pm_dev_err(dev, state, "", error); + dpm_save_failed_dev(dev_name(dev)); put_device(dev); break; } @@ -983,7 +998,10 @@ int dpm_suspend(pm_message_t state) async_synchronize_full(); if (!error) error = async_error; - if (!error) + if (error) { + suspend_stats.failed_suspend++; + dpm_save_failed_step(SUSPEND_SUSPEND); + } else dpm_show_time(starttime, state, NULL); return error; } @@ -1091,7 +1109,10 @@ int dpm_suspend_start(pm_message_t state) int error; error = dpm_prepare(state); - if (!error) + if (error) { + suspend_stats.failed_prepare++; + dpm_save_failed_step(SUSPEND_PREPARE); + } else error = dpm_suspend(state); return error; } diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 6bbcef22e10..76f42e49b72 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -34,6 +34,58 @@ typedef int __bitwise suspend_state_t; #define PM_SUSPEND_MEM ((__force suspend_state_t) 3) #define PM_SUSPEND_MAX ((__force suspend_state_t) 4) +enum suspend_stat_step { + SUSPEND_FREEZE = 1, + SUSPEND_PREPARE, + SUSPEND_SUSPEND, + SUSPEND_SUSPEND_NOIRQ, + SUSPEND_RESUME_NOIRQ, + SUSPEND_RESUME +}; + +struct suspend_stats { + int success; + int fail; + int failed_freeze; + int failed_prepare; + int failed_suspend; + int failed_suspend_noirq; + int failed_resume; + int failed_resume_noirq; +#define REC_FAILED_NUM 2 + int last_failed_dev; + char failed_devs[REC_FAILED_NUM][40]; + int last_failed_errno; + int errno[REC_FAILED_NUM]; + int last_failed_step; + enum suspend_stat_step failed_steps[REC_FAILED_NUM]; +}; + +extern struct suspend_stats suspend_stats; + +static inline void dpm_save_failed_dev(const char *name) +{ + strlcpy(suspend_stats.failed_devs[suspend_stats.last_failed_dev], + name, + sizeof(suspend_stats.failed_devs[0])); + suspend_stats.last_failed_dev++; + suspend_stats.last_failed_dev %= REC_FAILED_NUM; +} + +static inline void dpm_save_failed_errno(int err) +{ + suspend_stats.errno[suspend_stats.last_failed_errno] = err; + suspend_stats.last_failed_errno++; + suspend_stats.last_failed_errno %= REC_FAILED_NUM; +} + +static inline void dpm_save_failed_step(enum suspend_stat_step step) +{ + suspend_stats.failed_steps[suspend_stats.last_failed_step] = step; + suspend_stats.last_failed_step++; + suspend_stats.last_failed_step %= REC_FAILED_NUM; +} + /** * struct platform_suspend_ops - Callbacks for managing platform dependent * system sleep states. diff --git a/kernel/power/main.c b/kernel/power/main.c index 6c601f87196..2757acba8e8 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "power.h" @@ -133,6 +135,101 @@ power_attr(pm_test); #endif /* CONFIG_PM_SLEEP */ +#ifdef CONFIG_DEBUG_FS +static char *suspend_step_name(enum suspend_stat_step step) +{ + switch (step) { + case SUSPEND_FREEZE: + return "freeze"; + case SUSPEND_PREPARE: + return "prepare"; + case SUSPEND_SUSPEND: + return "suspend"; + case SUSPEND_SUSPEND_NOIRQ: + return "suspend_noirq"; + case SUSPEND_RESUME_NOIRQ: + return "resume_noirq"; + case SUSPEND_RESUME: + return "resume"; + default: + return ""; + } +} + +static int suspend_stats_show(struct seq_file *s, void *unused) +{ + int i, index, last_dev, last_errno, last_step; + + last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1; + last_dev %= REC_FAILED_NUM; + last_errno = suspend_stats.last_failed_errno + REC_FAILED_NUM - 1; + last_errno %= REC_FAILED_NUM; + last_step = suspend_stats.last_failed_step + REC_FAILED_NUM - 1; + last_step %= REC_FAILED_NUM; + seq_printf(s, "%s: %d\n%s: %d\n%s: %d\n%s: %d\n" + "%s: %d\n%s: %d\n%s: %d\n%s: %d\n", + "success", suspend_stats.success, + "fail", suspend_stats.fail, + "failed_freeze", suspend_stats.failed_freeze, + "failed_prepare", suspend_stats.failed_prepare, + "failed_suspend", suspend_stats.failed_suspend, + "failed_suspend_noirq", + suspend_stats.failed_suspend_noirq, + "failed_resume", suspend_stats.failed_resume, + "failed_resume_noirq", + suspend_stats.failed_resume_noirq); + seq_printf(s, "failures:\n last_failed_dev:\t%-s\n", + suspend_stats.failed_devs[last_dev]); + for (i = 1; i < REC_FAILED_NUM; i++) { + index = last_dev + REC_FAILED_NUM - i; + index %= REC_FAILED_NUM; + seq_printf(s, "\t\t\t%-s\n", + suspend_stats.failed_devs[index]); + } + seq_printf(s, " last_failed_errno:\t%-d\n", + suspend_stats.errno[last_errno]); + for (i = 1; i < REC_FAILED_NUM; i++) { + index = last_errno + REC_FAILED_NUM - i; + index %= REC_FAILED_NUM; + seq_printf(s, "\t\t\t%-d\n", + suspend_stats.errno[index]); + } + seq_printf(s, " last_failed_step:\t%-s\n", + suspend_step_name( + suspend_stats.failed_steps[last_step])); + for (i = 1; i < REC_FAILED_NUM; i++) { + index = last_step + REC_FAILED_NUM - i; + index %= REC_FAILED_NUM; + seq_printf(s, "\t\t\t%-s\n", + suspend_step_name( + suspend_stats.failed_steps[index])); + } + + return 0; +} + +static int suspend_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, suspend_stats_show, NULL); +} + +static const struct file_operations suspend_stats_operations = { + .open = suspend_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init pm_debugfs_init(void) +{ + debugfs_create_file("suspend_stats", S_IFREG | S_IRUGO, + NULL, NULL, &suspend_stats_operations); + return 0; +} + +late_initcall(pm_debugfs_init); +#endif /* CONFIG_DEBUG_FS */ + struct kobject *power_kobj; /** @@ -194,6 +291,11 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, } if (state < PM_SUSPEND_MAX && *s) error = enter_state(state); + if (error) { + suspend_stats.fail++; + dpm_save_failed_errno(error); + } else + suspend_stats.success++; #endif Exit: diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index b6b71ad2208..595a3dd56a8 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -104,7 +104,10 @@ static int suspend_prepare(void) goto Finish; error = suspend_freeze_processes(); - if (!error) + if (error) { + suspend_stats.failed_freeze++; + dpm_save_failed_step(SUSPEND_FREEZE); + } else return 0; suspend_thaw_processes(); @@ -315,8 +318,16 @@ int enter_state(suspend_state_t state) */ int pm_suspend(suspend_state_t state) { - if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX) - return enter_state(state); + int ret; + if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX) { + ret = enter_state(state); + if (ret) { + suspend_stats.fail++; + dpm_save_failed_errno(ret); + } else + suspend_stats.success++; + return ret; + } return -EINVAL; } EXPORT_SYMBOL(pm_suspend); -- cgit v1.2.3-70-g09d2 From 8f88893c05f2f677f18f2ce5591b4bed5d4a7535 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 26 Sep 2011 17:38:50 +0200 Subject: PM: Update the policy on default wakeup settings This patch (as1485) documents a change to the kernel's default wakeup policy. Devices that forward wakeup requests between buses should be enabled for wakeup by default. Signed-off-by: Alan Stern Signed-off-by: Rafael J. Wysocki --- Documentation/power/devices.txt | 4 +++- drivers/base/power/wakeup.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt index 3384d5996be..29b7a9817f5 100644 --- a/Documentation/power/devices.txt +++ b/Documentation/power/devices.txt @@ -152,7 +152,9 @@ try to use its wakeup mechanism. device_set_wakeup_enable() affects this flag; for the most part drivers should not change its value. The initial value of should_wakeup is supposed to be false for the majority of devices; the major exceptions are power buttons, keyboards, and Ethernet adapters whose WoL -(wake-on-LAN) feature has been set up with ethtool. +(wake-on-LAN) feature has been set up with ethtool. It should also default +to true for devices that don't generate wakeup requests on their own but merely +forward wakeup requests from one bus to another (like PCI bridges). Whether or not a device is capable of issuing wakeup events is a hardware matter, and the kernel is responsible for keeping track of it. By contrast, diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 84f7c7d5a09..14ee07e9cc4 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -276,7 +276,9 @@ EXPORT_SYMBOL_GPL(device_set_wakeup_capable); * * By default, most devices should leave wakeup disabled. The exceptions are * devices that everyone expects to be wakeup sources: keyboards, power buttons, - * possibly network interfaces, etc. + * possibly network interfaces, etc. Also, devices that don't generate their + * own wakeup requests but merely forward requests from one bus to another + * (like PCI bridges) should have wakeup enabled by default. */ int device_init_wakeup(struct device *dev, bool enable) { -- cgit v1.2.3-70-g09d2 From 2aede851ddf08666f68ffc17be446420e9d2a056 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 26 Sep 2011 20:32:27 +0200 Subject: PM / Hibernate: Freeze kernel threads after preallocating memory There is a problem with the current ordering of hibernate code which leads to deadlocks in some filesystems' memory shrinkers. Namely, some filesystems use freezable kernel threads that are inactive when the hibernate memory preallocation is carried out. Those same filesystems use memory shrinkers that may be triggered by the hibernate memory preallocation. If those memory shrinkers wait for the frozen kernel threads, the hibernate process deadlocks (this happens with XFS, for one example). Apparently, it is not technically viable to redesign the filesystems in question to avoid the situation described above, so the only possible solution of this issue is to defer the freezing of kernel threads until the hibernate memory preallocation is done, which is implemented by this change. Unfortunately, this requires the memory preallocation to be done before the "prepare" stage of device freeze, so after this change the only way drivers can allocate additional memory for their freeze routines in a clean way is to use PM notifiers. Reported-by: Christoph Signed-off-by: Rafael J. Wysocki --- Documentation/power/devices.txt | 4 ---- include/linux/freezer.h | 4 +++- kernel/power/hibernate.c | 12 ++++++++---- kernel/power/power.h | 3 ++- kernel/power/process.c | 30 ++++++++++++++++++++---------- 5 files changed, 33 insertions(+), 20 deletions(-) (limited to 'Documentation') diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt index 29b7a9817f5..646a89e0c07 100644 --- a/Documentation/power/devices.txt +++ b/Documentation/power/devices.txt @@ -281,10 +281,6 @@ When the system goes into the standby or memory sleep state, the phases are: time.) Unlike the other suspend-related phases, during the prepare phase the device tree is traversed top-down. - In addition to that, if device drivers need to allocate additional - memory to be able to hadle device suspend correctly, that should be - done in the prepare phase. - After the prepare callback method returns, no new children may be registered below the device. The method may also prepare the device or driver in some way for the upcoming system power transition (for diff --git a/include/linux/freezer.h b/include/linux/freezer.h index 1effc8b56b4..aa56cf31f7f 100644 --- a/include/linux/freezer.h +++ b/include/linux/freezer.h @@ -49,6 +49,7 @@ extern int thaw_process(struct task_struct *p); extern void refrigerator(void); extern int freeze_processes(void); +extern int freeze_kernel_threads(void); extern void thaw_processes(void); static inline int try_to_freeze(void) @@ -171,7 +172,8 @@ static inline void clear_freeze_flag(struct task_struct *p) {} static inline int thaw_process(struct task_struct *p) { return 1; } static inline void refrigerator(void) {} -static inline int freeze_processes(void) { BUG(); return 0; } +static inline int freeze_processes(void) { return -ENOSYS; } +static inline int freeze_kernel_threads(void) { return -ENOSYS; } static inline void thaw_processes(void) {} static inline int try_to_freeze(void) { return 0; } diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 8f7b1db1ece..3a20466015f 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -334,12 +334,16 @@ int hibernation_snapshot(int platform_mode) if (error) goto Close; - error = dpm_prepare(PMSG_FREEZE); - if (error) - goto Complete_devices; - /* Preallocate image memory before shutting down devices. */ error = hibernate_preallocate_memory(); + if (error) + goto Close; + + error = freeze_kernel_threads(); + if (error) + goto Close; + + error = dpm_prepare(PMSG_FREEZE); if (error) goto Complete_devices; diff --git a/kernel/power/power.h b/kernel/power/power.h index 9a00a0a2628..e6206397ce6 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -228,7 +228,8 @@ extern int pm_test_level; #ifdef CONFIG_SUSPEND_FREEZER static inline int suspend_freeze_processes(void) { - return freeze_processes(); + int error = freeze_processes(); + return error ? : freeze_kernel_threads(); } static inline void suspend_thaw_processes(void) diff --git a/kernel/power/process.c b/kernel/power/process.c index 0cf3a27a6c9..addbbe5531b 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -135,7 +135,7 @@ static int try_to_freeze_tasks(bool sig_only) } /** - * freeze_processes - tell processes to enter the refrigerator + * freeze_processes - Signal user space processes to enter the refrigerator. */ int freeze_processes(void) { @@ -143,20 +143,30 @@ int freeze_processes(void) printk("Freezing user space processes ... "); error = try_to_freeze_tasks(true); - if (error) - goto Exit; - printk("done.\n"); + if (!error) { + printk("done."); + oom_killer_disable(); + } + printk("\n"); + BUG_ON(in_atomic()); + + return error; +} + +/** + * freeze_kernel_threads - Make freezable kernel threads go to the refrigerator. + */ +int freeze_kernel_threads(void) +{ + int error; printk("Freezing remaining freezable tasks ... "); error = try_to_freeze_tasks(false); - if (error) - goto Exit; - printk("done."); + if (!error) + printk("done."); - oom_killer_disable(); - Exit: - BUG_ON(in_atomic()); printk("\n"); + BUG_ON(in_atomic()); return error; } -- cgit v1.2.3-70-g09d2 From 6f8d7022a842809aeb24db1d15669198ef02c131 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Thu, 6 Oct 2011 20:34:46 +0200 Subject: PM / Hibernate: Add resumewait param to support MMC-like devices as resume file Some devices like MMC are async detected very slow. For example, drivers/mmc/host/sdhci.c launches a 200ms delayed work to detect MMC partitions then add disk. We have wait_for_device_probe() and scsi_complete_async_scans() before calling swsusp_check(), but it is not enough to wait for MMC. This patch adds resumewait kernel param just like rootwait so that we have enough time to wait until MMC is ready. The difference is that we wait for resume partition whereas rootwait waits for rootfs partition (which may be on a different device). This patch will make hibernation support many embedded products without SCSI devices, but with devices like MMC. [rjw: Modified the changelog slightly.] Signed-off-by: Barry Song Reviewed-by: Valdis Kletnieks Signed-off-by: Rafael J. Wysocki --- Documentation/kernel-parameters.txt | 4 ++++ kernel/power/hibernate.c | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 854ed5ca7e3..88a7b197a4e 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2240,6 +2240,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. in units (needed only for swap files). See Documentation/power/swsusp-and-swap-files.txt + resumewait [HIBERNATION] Wait (indefinitely) for resume device to show up. + Useful for devices that are detected asynchronously + (e.g. USB and MMC devices). + hibernate= [HIBERNATION] noresume Don't check if there's a hibernation image present during boot. diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 7f44e5c2697..0f8785080cd 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ static int nocompress = 0; static int noresume = 0; +static int resume_wait = 0; static char resume_file[256] = CONFIG_PM_STD_PARTITION; dev_t swsusp_resume_device; sector_t swsusp_resume_block; @@ -736,6 +738,13 @@ static int software_resume(void) * to wait for this to finish. */ wait_for_device_probe(); + + if (resume_wait) { + while ((swsusp_resume_device = name_to_dev_t(resume_file)) == 0) + msleep(10); + async_synchronize_full(); + } + /* * We can't depend on SCSI devices being available after loading * one of their modules until scsi_complete_async_scans() is @@ -1064,7 +1073,14 @@ static int __init noresume_setup(char *str) return 1; } +static int __init resumewait_setup(char *str) +{ + resume_wait = 1; + return 1; +} + __setup("noresume", noresume_setup); __setup("resume_offset=", resume_offset_setup); __setup("resume=", resume_setup); __setup("hibernate=", hibernate_setup); +__setup("resumewait", resumewait_setup); -- cgit v1.2.3-70-g09d2 From f126f7334f72e2fd1b7a62bba20c488b86e6e7c4 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Mon, 10 Oct 2011 23:38:41 +0200 Subject: PM / Hibernate: Add resumedelay kernel param in addition to resumewait Patch "PM / Hibernate: Add resumewait param to support MMC-like devices as resume file" added the resumewait kernel command line option. The present patch adds resumedelay so that resumewait/delay were analogous to rootwait/delay. [rjw: Modified the subject and changelog slightly.] Signed-off-by: Barry Song Acked-by: Pavel Machek Signed-off-by: Rafael J. Wysocki --- Documentation/kernel-parameters.txt | 3 +++ kernel/power/hibernate.c | 14 ++++++++++++++ 2 files changed, 17 insertions(+) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 88a7b197a4e..831bde222bd 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2240,6 +2240,9 @@ bytes respectively. Such letter suffixes can also be entirely omitted. in units (needed only for swap files). See Documentation/power/swsusp-and-swap-files.txt + resumedelay= [HIBERNATION] Delay (in seconds) to pause before attempting to + read the resume files + resumewait [HIBERNATION] Wait (indefinitely) for resume device to show up. Useful for devices that are detected asynchronously (e.g. USB and MMC devices). diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 0f8785080cd..50f537953e7 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -33,6 +33,7 @@ static int nocompress = 0; static int noresume = 0; static int resume_wait = 0; +static int resume_delay = 0; static char resume_file[256] = CONFIG_PM_STD_PARTITION; dev_t swsusp_resume_device; sector_t swsusp_resume_block; @@ -730,6 +731,12 @@ static int software_resume(void) pr_debug("PM: Checking hibernation image partition %s\n", resume_file); + if (resume_delay) { + printk(KERN_INFO "Waiting %dsec before reading resume device...\n", + resume_delay); + ssleep(resume_delay); + } + /* Check if the device is there */ swsusp_resume_device = name_to_dev_t(resume_file); if (!swsusp_resume_device) { @@ -1079,8 +1086,15 @@ static int __init resumewait_setup(char *str) return 1; } +static int __init resumedelay_setup(char *str) +{ + resume_delay = simple_strtoul(str, NULL, 0); + return 1; +} + __setup("noresume", noresume_setup); __setup("resume_offset=", resume_offset_setup); __setup("resume=", resume_setup); __setup("hibernate=", hibernate_setup); __setup("resumewait", resumewait_setup); +__setup("resumedelay=", resumedelay_setup); -- cgit v1.2.3-70-g09d2 From 7fef9fc83fbd7293ea9fe665d14046422ebf4219 Mon Sep 17 00:00:00 2001 From: "Srivatsa S. Bhat" Date: Wed, 19 Oct 2011 23:59:05 +0200 Subject: PM / Documentation: Update docs about suspend and CPU hotplug Update the documentation about the interaction between the suspend (S3) call path and the CPU hotplug infrastructure. This patch focusses only on the activities of the freezer, cpu hotplug and the notifications involved. It outlines how regular CPU hotplug differs from the way it is invoked during suspend and also tries to explain the locking involved. In addition to that, it discusses the issue of microcode update during CPU hotplug operations. Signed-off-by: Srivatsa S. Bhat Signed-off-by: Rafael J. Wysocki --- Documentation/power/00-INDEX | 2 + Documentation/power/suspend-and-cpuhotplug.txt | 275 +++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 Documentation/power/suspend-and-cpuhotplug.txt (limited to 'Documentation') diff --git a/Documentation/power/00-INDEX b/Documentation/power/00-INDEX index 45e9d4a9128..a4d682f5423 100644 --- a/Documentation/power/00-INDEX +++ b/Documentation/power/00-INDEX @@ -26,6 +26,8 @@ s2ram.txt - How to get suspend to ram working (and debug it when it isn't) states.txt - System power management states +suspend-and-cpuhotplug.txt + - Explains the interaction between Suspend-to-RAM (S3) and CPU hotplug swsusp-and-swap-files.txt - Using swap files with software suspend (to disk) swsusp-dmcrypt.txt diff --git a/Documentation/power/suspend-and-cpuhotplug.txt b/Documentation/power/suspend-and-cpuhotplug.txt new file mode 100644 index 00000000000..f28f9a6f034 --- /dev/null +++ b/Documentation/power/suspend-and-cpuhotplug.txt @@ -0,0 +1,275 @@ +Interaction of Suspend code (S3) with the CPU hotplug infrastructure + + (C) 2011 Srivatsa S. Bhat + + +I. How does the regular CPU hotplug code differ from how the Suspend-to-RAM + infrastructure uses it internally? And where do they share common code? + +Well, a picture is worth a thousand words... So ASCII art follows :-) + +[This depicts the current design in the kernel, and focusses only on the +interactions involving the freezer and CPU hotplug and also tries to explain +the locking involved. It outlines the notifications involved as well. +But please note that here, only the call paths are illustrated, with the aim +of describing where they take different paths and where they share code. +What happens when regular CPU hotplug and Suspend-to-RAM race with each other +is not depicted here.] + +On a high level, the suspend-resume cycle goes like this: + +|Freeze| -> |Disable nonboot| -> |Do suspend| -> |Enable nonboot| -> |Thaw | +|tasks | | cpus | | | | cpus | |tasks| + + +More details follow: + + Suspend call path + ----------------- + + Write 'mem' to + /sys/power/state + syfs file + | + v + Acquire pm_mutex lock + | + v + Send PM_SUSPEND_PREPARE + notifications + | + v + Freeze tasks + | + | + v + disable_nonboot_cpus() + /* start */ + | + v + Acquire cpu_add_remove_lock + | + v + Iterate over CURRENTLY + online CPUs + | + | + | ---------- + v | L + ======> _cpu_down() | + | [This takes cpuhotplug.lock | + Common | before taking down the CPU | + code | and releases it when done] | O + | While it is at it, notifications | + | are sent when notable events occur, | + ======> by running all registered callbacks. | + | | O + | | + | | + v | + Note down these cpus in | P + frozen_cpus mask ---------- + | + v + Disable regular cpu hotplug + by setting cpu_hotplug_disabled=1 + | + v + Release cpu_add_remove_lock + | + v + /* disable_nonboot_cpus() complete */ + | + v + Do suspend + + + +Resuming back is likewise, with the counterparts being (in the order of +execution during resume): +* enable_nonboot_cpus() which involves: + | Acquire cpu_add_remove_lock + | Reset cpu_hotplug_disabled to 0, thereby enabling regular cpu hotplug + | Call _cpu_up() [for all those cpus in the frozen_cpus mask, in a loop] + | Release cpu_add_remove_lock + v + +* thaw tasks +* send PM_POST_SUSPEND notifications +* Release pm_mutex lock. + + +It is to be noted here that the pm_mutex lock is acquired at the very +beginning, when we are just starting out to suspend, and then released only +after the entire cycle is complete (i.e., suspend + resume). + + + + Regular CPU hotplug call path + ----------------------------- + + Write 0 (or 1) to + /sys/devices/system/cpu/cpu*/online + sysfs file + | + | + v + cpu_down() + | + v + Acquire cpu_add_remove_lock + | + v + If cpu_hotplug_disabled is 1 + return gracefully + | + | + v + ======> _cpu_down() + | [This takes cpuhotplug.lock + Common | before taking down the CPU + code | and releases it when done] + | While it is at it, notifications + | are sent when notable events occur, + ======> by running all registered callbacks. + | + | + v + Release cpu_add_remove_lock + [That's it!, for + regular CPU hotplug] + + + +So, as can be seen from the two diagrams (the parts marked as "Common code"), +regular CPU hotplug and the suspend code path converge at the _cpu_down() and +_cpu_up() functions. They differ in the arguments passed to these functions, +in that during regular CPU hotplug, 0 is passed for the 'tasks_frozen' +argument. But during suspend, since the tasks are already frozen by the time +the non-boot CPUs are offlined or onlined, the _cpu_*() functions are called +with the 'tasks_frozen' argument set to 1. +[See below for some known issues regarding this.] + + +Important files and functions/entry points: +------------------------------------------ + +kernel/power/process.c : freeze_processes(), thaw_processes() +kernel/power/suspend.c : suspend_prepare(), suspend_enter(), suspend_finish() +kernel/cpu.c: cpu_[up|down](), _cpu_[up|down](), [disable|enable]_nonboot_cpus() + + + +II. What are the issues involved in CPU hotplug? + ------------------------------------------- + +There are some interesting situations involving CPU hotplug and microcode +update on the CPUs, as discussed below: + +[Please bear in mind that the kernel requests the microcode images from +userspace, using the request_firmware() function defined in +drivers/base/firmware_class.c] + + +a. When all the CPUs are identical: + + This is the most common situation and it is quite straightforward: we want + to apply the same microcode revision to each of the CPUs. + To give an example of x86, the collect_cpu_info() function defined in + arch/x86/kernel/microcode_core.c helps in discovering the type of the CPU + and thereby in applying the correct microcode revision to it. + But note that the kernel does not maintain a common microcode image for the + all CPUs, in order to handle case 'b' described below. + + +b. When some of the CPUs are different than the rest: + + In this case since we probably need to apply different microcode revisions + to different CPUs, the kernel maintains a copy of the correct microcode + image for each CPU (after appropriate CPU type/model discovery using + functions such as collect_cpu_info()). + + +c. When a CPU is physically hot-unplugged and a new (and possibly different + type of) CPU is hot-plugged into the system: + + In the current design of the kernel, whenever a CPU is taken offline during + a regular CPU hotplug operation, upon receiving the CPU_DEAD notification + (which is sent by the CPU hotplug code), the microcode update driver's + callback for that event reacts by freeing the kernel's copy of the + microcode image for that CPU. + + Hence, when a new CPU is brought online, since the kernel finds that it + doesn't have the microcode image, it does the CPU type/model discovery + afresh and then requests the userspace for the appropriate microcode image + for that CPU, which is subsequently applied. + + For example, in x86, the mc_cpu_callback() function (which is the microcode + update driver's callback registered for CPU hotplug events) calls + microcode_update_cpu() which would call microcode_init_cpu() in this case, + instead of microcode_resume_cpu() when it finds that the kernel doesn't + have a valid microcode image. This ensures that the CPU type/model + discovery is performed and the right microcode is applied to the CPU after + getting it from userspace. + + +d. Handling microcode update during suspend/hibernate: + + Strictly speaking, during a CPU hotplug operation which does not involve + physically removing or inserting CPUs, the CPUs are not actually powered + off during a CPU offline. They are just put to the lowest C-states possible. + Hence, in such a case, it is not really necessary to re-apply microcode + when the CPUs are brought back online, since they wouldn't have lost the + image during the CPU offline operation. + + This is the usual scenario encountered during a resume after a suspend. + However, in the case of hibernation, since all the CPUs are completely + powered off, during restore it becomes necessary to apply the microcode + images to all the CPUs. + + [Note that we don't expect someone to physically pull out nodes and insert + nodes with a different type of CPUs in-between a suspend-resume or a + hibernate/restore cycle.] + + In the current design of the kernel however, during a CPU offline operation + as part of the suspend/hibernate cycle (the CPU_DEAD_FROZEN notification), + the existing copy of microcode image in the kernel is not freed up. + And during the CPU online operations (during resume/restore), since the + kernel finds that it already has copies of the microcode images for all the + CPUs, it just applies them to the CPUs, avoiding any re-discovery of CPU + type/model and the need for validating whether the microcode revisions are + right for the CPUs or not (due to the above assumption that physical CPU + hotplug will not be done in-between suspend/resume or hibernate/restore + cycles). + + +III. Are there any known problems when regular CPU hotplug and suspend race + with each other? + +Yes, they are listed below: + +1. When invoking regular CPU hotplug, the 'tasks_frozen' argument passed to + the _cpu_down() and _cpu_up() functions is *always* 0. + This might not reflect the true current state of the system, since the + tasks could have been frozen by an out-of-band event such as a suspend + operation in progress. Hence, it will lead to wrong notifications being + sent during the cpu online/offline events (eg, CPU_ONLINE notification + instead of CPU_ONLINE_FROZEN) which in turn will lead to execution of + inappropriate code by the callbacks registered for such CPU hotplug events. + +2. If a regular CPU hotplug stress test happens to race with the freezer due + to a suspend operation in progress at the same time, then we could hit the + situation described below: + + * A regular cpu online operation continues its journey from userspace + into the kernel, since the freezing has not yet begun. + * Then freezer gets to work and freezes userspace. + * If cpu online has not yet completed the microcode update stuff by now, + it will now start waiting on the frozen userspace in the + TASK_UNINTERRUPTIBLE state, in order to get the microcode image. + * Now the freezer continues and tries to freeze the remaining tasks. But + due to this wait mentioned above, the freezer won't be able to freeze + the cpu online hotplug task and hence freezing of tasks fails. + + As a result of this task freezing failure, the suspend operation gets + aborted. -- cgit v1.2.3-70-g09d2