Sleepy linux support, demo version, but it works on my thinkpad x60 ;-). Signed-off-by: Pavel Machek <pavel@xxxxxxx> diff --git a/Documentation/power/sleepy.txt b/Documentation/power/sleepy.txt new file mode 100644 index 0000000..a9caf05 --- /dev/null +++ b/Documentation/power/sleepy.txt @@ -0,0 +1,55 @@ + Sleepy Linux + ~~~~~~~~~~~~ + +Copyright 2007 Pavel Machek <pavel@xxxxxxx> + GPLv2 + +Current Linux versions can enter suspend-to-RAM just fine, but only +can do it on explicit request. But suspend-to-RAM is important, eating +something like 10% of power needed for idle system. Starting suspend +manually is not too convinient; it is not an option on multiuser +machine, and even on single user machine, some things are not easy: + +1) Download this big chunk in mozilla, then go to sleep + +2) Compile this, then go to sleep + +3) You can sleep now, but wake me up in 8:30 with mp3 player + +Todays hardware is mostly capable of doing better: with correctly set +up wakeups, machine can sleep and successfully pretend it is not +sleeping -- by waking up whenever something interesting happens. Of +course, it is easier on machines not connected to the network, and on +notebook computers. + +Requirements: + +0) Working suspend-to-RAM, with kernel being able to bring video back. + +1) RTC clock that can wake up system + +2) Lid that can wake up a system, + or keyboard that can wake up system and does not loose keypress + or special screensaver setup + +3) Network card that is either down + or can wake up system on any packet (and not loose too many packets) + +How to use it +~~~~~~~~~~~~~ + +First, make sure your config is tiny enough that cpu sleeps at least +five or so seconds between wakeups. You'll probably need to disable +USB, make some kernel timers way longer than default and boot with +init=/bin/bash. + +Then, enable SCSI powersave by something like: + +mount /sys +echo auto > /sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/power/level +echo 3 > /sys/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/power/autosuspend +echo adisk > /sys/power/state +mount / -oremount,commit=900 + +Then, echo auto > /sys/power/state should enable sleepy support. Do it +twice, and it will ignore open lid and sleep anyway. diff --git a/arch/x86/kernel/process_32.c b/arch/x86/kernel/process_32.c index a7d50a5..4c25613 100644 --- a/arch/x86/kernel/process_32.c +++ b/arch/x86/kernel/process_32.c @@ -187,6 +187,7 @@ void cpu_idle(void) /* endless idle loop with no priority at all */ while (1) { tick_nohz_stop_sched_tick(); + detect_idle(); while (!need_resched()) { void (*idle)(void); diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 29e71bd..0197b1f 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -32,6 +32,7 @@ * */ +#define DEBUG #include <linux/kernel.h> #include <linux/module.h> #include <linux/pci.h> @@ -259,8 +260,8 @@ static void ahci_fill_cmd_slot(struct ah u32 opts); #ifdef CONFIG_PM static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg); -static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg); -static int ahci_pci_device_resume(struct pci_dev *pdev); +int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg); +int ahci_pci_device_resume(struct pci_dev *pdev); #endif static struct class_device_attribute *ahci_shost_attrs[] = { @@ -268,6 +269,41 @@ static struct class_device_attribute *ah NULL }; +struct pci_dev *my_pdev; +int autosuspend_enabled; + +struct sleep_disabled_reason ahci_active = { + "ahci" +}; + +/* The host and its devices are all idle so we can autosuspend */ +static int autosuspend(struct Scsi_Host *host) +{ + if (my_pdev && autosuspend_enabled) { + printk("ahci: should autosuspend\n"); + ahci_pci_device_suspend(my_pdev, PMSG_SUSPEND); + enable_auto_sleep(&ahci_active); + return 0; + } + printk("ahci: autosuspend disabled\n"); + return -EINVAL; +} + +/* The host needs to be autoresumed */ +static int autoresume(struct Scsi_Host *host) +{ + if (my_pdev && autosuspend_enabled) { + printk("ahci: should autoresume\n"); + disable_auto_sleep(&ahci_active); + ahci_pci_device_resume(my_pdev); + return 0; + } + printk("ahci: autoresume disabled\n"); + return -EINVAL; +} + + + static struct scsi_host_template ahci_sht = { .module = THIS_MODULE, .name = DRV_NAME, @@ -286,6 +322,8 @@ static struct scsi_host_template ahci_sh .slave_destroy = ata_scsi_slave_destroy, .bios_param = ata_std_bios_param, .shost_attrs = ahci_shost_attrs, + .autosuspend = autosuspend, + .autoresume = autoresume, }; static const struct ata_port_operations ahci_ops = { @@ -1820,6 +1858,10 @@ static void ahci_thaw(struct ata_port *a static void ahci_error_handler(struct ata_port *ap) { + struct ata_host *host = ap->host; + int rc; + extern int slept; + if (!(ap->pflags & ATA_PFLAG_FROZEN)) { /* restart engine */ ahci_stop_engine(ap); @@ -1926,13 +1968,16 @@ static int ahci_port_suspend(struct ata_ return rc; } -static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg) +int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg) { struct ata_host *host = dev_get_drvdata(&pdev->dev); void __iomem *mmio = host->iomap[AHCI_PCI_BAR]; u32 ctl; + int ret; - if (mesg.event == PM_EVENT_SUSPEND) { + printk("sleepy: ahci_pci_device_suspend start\n"); + if (mesg.event == PM_EVENT_SUSPEND + || mesg.event == PM_EVENT_HIBERNATE) { /* AHCI spec rev1.1 section 8.3.3: * Software must disable interrupts prior to requesting a * transition of the HBA to D3 state. @@ -1943,28 +1988,38 @@ static int ahci_pci_device_suspend(struc readl(mmio + HOST_CTL); /* flush */ } - return ata_pci_device_suspend(pdev, mesg); + ret = ata_pci_device_suspend(pdev, mesg); + printk("sleepy: ahci_pci_device_suspend done\n"); + return ret; } -static int ahci_pci_device_resume(struct pci_dev *pdev) + +int ahci_pci_device_resume(struct pci_dev *pdev) { struct ata_host *host = dev_get_drvdata(&pdev->dev); int rc; + printk("sleepy: ahci_pci_device_resume start\n"); rc = ata_pci_device_do_resume(pdev); + printk("do_resume done\n"); if (rc) return rc; - if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + if (1) { + printk("reset_controller\n"); rc = ahci_reset_controller(host); if (rc) return rc; + printk("init_controller\n"); ahci_init_controller(host); } + printk("ata_host_resume\n"); ata_host_resume(host); + printk("all ok\n"); + printk("sleepy: ahci_pci_device_resume done\n"); return 0; } #endif @@ -2189,6 +2244,7 @@ static void ahci_p5wdh_workaround(struct } } + static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { static int printed_version; @@ -2300,8 +2356,12 @@ static int ahci_init_one(struct pci_dev ahci_print_info(host); pci_set_master(pdev); - return ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED, + + rc = ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED, &ahci_sht); + pci_save_state(pdev); + my_pdev = pdev; + return rc; } static int __init ahci_init(void) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index beaa3a9..ee77dc9 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -782,6 +782,7 @@ void ata_lpm_schedule(struct ata_port *a } #ifdef CONFIG_PM +/* This disables link power management */ static void ata_lpm_enable(struct ata_host *host) { struct ata_link *link; @@ -798,6 +799,7 @@ static void ata_lpm_enable(struct ata_ho } } +/* This enables link power management */ static void ata_lpm_disable(struct ata_host *host) { int i; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 4e31071..5c40ac2 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -32,6 +32,7 @@ * */ +#define DEBUG #include <linux/kernel.h> #include <linux/pci.h> #include <scsi/scsi.h> @@ -380,7 +381,7 @@ enum scsi_eh_timer_return ata_scsi_timed * Inherited from SCSI layer (none, can sleep) * * RETURNS: - * Zero. + * Nothing. */ void ata_scsi_error(struct Scsi_Host *host) { diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 1cea18f..62b3ee0 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -33,6 +33,7 @@ * */ +#define DEBUG #include <linux/kernel.h> #include <linux/blkdev.h> #include <linux/spinlock.h> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index bdc03f7..268855d 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -19,6 +19,7 @@ * ancestral dependencies that the subsystem list maintains. */ +#define DEBUG #include <linux/device.h> #include <linux/kallsyms.h> #include <linux/mutex.h> @@ -319,6 +320,7 @@ static void unregister_dropped_devices(v * * Resume all the devices, unlock them all, and allow new * devices to be registered once again. + * Called with interrupts disabled, may not sleep. */ void device_resume(void) { @@ -419,11 +421,6 @@ int suspend_device(struct device *dev, p { int error = 0; - if (dev->power.power_state.event) { - dev_dbg(dev, "PM: suspend %d-->%d\n", - dev->power.power_state.event, state.event); - } - if (dev->class && dev->class->suspend) { suspend_device_dbg(dev, state, "class "); error = dev->class->suspend(dev, state); @@ -444,6 +441,34 @@ int suspend_device(struct device *dev, p return error; } +int device_suspend_fake(pm_message_t state) +{ + int error = 0; + + mutex_lock(&dpm_list_mtx); + while (!list_empty(&dpm_active) && error == 0) { + struct list_head * entry = dpm_active.prev; + struct device * dev = to_device(entry); + + get_device(dev); + + /* Check if the device got removed */ + if (!list_empty(&dev->power.entry)) { + /* Move it to the dpm_off list */ + if (!error) + list_move(&dev->power.entry, &dpm_off); + } + if (error) + printk(KERN_ERR "Could not suspend device %s: " + "error %d%s\n", + kobject_name(&dev->kobj), error, + error == -EAGAIN ? " (please convert to suspend_late)" : ""); + put_device(dev); + } + mutex_unlock(&dpm_list_mtx); + return error; +} + /** * dpm_suspend - Suspend every device. * @state: Power state to put each device in. @@ -496,6 +521,8 @@ static int dpm_suspend(pm_message_t stat * * Go through the dpm_active list. Carefully lock each device's * semaphore and put it in on the dpm_locked list. + * + * Must be called with disabled interrupts, may not sleep. */ static void lock_all_devices(void) { diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c index 490918a..ed2bdf2 100644 --- a/drivers/input/input-polldev.c +++ b/drivers/input/input-polldev.c @@ -153,7 +153,7 @@ int input_register_polled_device(struct INIT_DELAYED_WORK(&dev->work, input_polled_device_work); if (!dev->poll_interval) - dev->poll_interval = 500; + dev->poll_interval = 50000; input->private = dev; input->open = input_open_polled_device; input->close = input_close_polled_device; diff --git a/drivers/input/input.c b/drivers/input/input.c index f02c242..7e9f46a 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -251,6 +251,11 @@ static void input_handle_event(struct in input_pass_event(dev, type, code, value); } +struct sleep_disabled_reason keyboard_active = { + "keyboard" +}; + + /** * input_event() - report new input event * @dev: device that generated the event @@ -267,8 +272,14 @@ void input_event(struct input_dev *dev, { unsigned long flags; - if (is_event_supported(type, dev->evbit, EV_MAX)) { + if ((type == EV_SW) && (code == SW_LID)) { + int is_closed = value; +// printk("LID: %d\n", value); + if (is_closed) enable_auto_sleep(&keyboard_active); + else disable_auto_sleep(&keyboard_active); + } + if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); add_input_randomness(type, code, value); input_handle_event(dev, type, code, value); @@ -1646,6 +1657,8 @@ static int __init input_init(void) goto fail2; } + /* FIXME: should only inc it if LID is open */ + disable_auto_sleep(&keyboard_active); return 0; fail2: input_proc_exit(); diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index e059f94..fadf651 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -93,7 +93,7 @@ static inline int is_intr(u8 rtc_intr) /*----------------------------------------------------------------*/ -static int cmos_read_time(struct device *dev, struct rtc_time *t) +int cmos_read_time(struct device *dev, struct rtc_time *t) { /* REVISIT: if the clock has a "century" register, use * that instead of the heuristic in get_rtc_time(). @@ -185,7 +185,7 @@ static int cmos_read_alarm(struct device return 0; } -static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) +int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) { struct cmos_rtc *cmos = dev_get_drvdata(dev); unsigned char mon, mday, hrs, min, sec; @@ -494,6 +494,35 @@ static struct bin_attribute nvram = { /*----------------------------------------------------------------*/ static struct cmos_rtc cmos_rtc; +static struct device *pc_rtc_device; + +int set_alarm(int length) +{ + ssize_t retval; + unsigned long now, alarm; + struct rtc_wkalrm alm; + + if (!pc_rtc_device) + return -EFAULT; + retval = cmos_read_time(pc_rtc_device, &alm.time); + if (retval < 0) { + printk("Auto sleep: can't get time?\n"); + return retval; + } + rtc_tm_to_time(&alm.time, &now); + printk("Auto sleep: Now %ld\n", now); + + alarm = now+length; + rtc_time_to_tm(alarm, &alm.time); + + retval = cmos_set_alarm(pc_rtc_device, &alm); + if (retval < 0) { + printk("Auto sleep: can't set alarm.\n"); + return retval; + } + printk("Auto sleep: Alarm set\n"); + return 0; +} static irqreturn_t cmos_interrupt(int irq, void *p) { @@ -552,6 +581,8 @@ cmos_do_probe(struct device *dev, struct if (cmos_rtc.dev) return -EBUSY; + pc_rtc_device = dev; + if (!ports) return -ENODEV; @@ -712,7 +743,7 @@ cleanup0: static void cmos_do_shutdown(void) { - unsigned char rtc_control; + unsigned char rtc_control; spin_lock_irq(&rtc_lock); rtc_control = CMOS_READ(RTC_CONTROL); @@ -727,6 +758,7 @@ static void __exit cmos_do_remove(struct struct cmos_rtc *cmos = dev_get_drvdata(dev); struct resource *ports; + pc_rtc_device = NULL; cmos_do_shutdown(); sysfs_remove_bin_file(&dev->kobj, &nvram); diff --git a/drivers/rtc/rtc-sysfs.c b/drivers/rtc/rtc-sysfs.c index 4d27ccc..b0c609e 100644 --- a/drivers/rtc/rtc-sysfs.c +++ b/drivers/rtc/rtc-sysfs.c @@ -156,16 +156,6 @@ rtc_sysfs_set_wakealarm(struct device *d alarm = simple_strtoul(buf, NULL, 0); if (alarm > now) { - /* Avoid accidentally clobbering active alarms; we can't - * entirely prevent that here, without even the minimal - * locking from the /dev/rtcN api. - */ - retval = rtc_read_alarm(rtc, &alm); - if (retval < 0) - return retval; - if (alm.enabled) - return -EBUSY; - alm.enabled = 1; } else { alm.enabled = 0; diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 5393e15..25bebc1 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -340,6 +340,21 @@ static int scsi_bus_uevent(struct device return 0; } +static int scsi_bus_remove(struct device *dev) +{ + struct device_driver *drv = dev->driver; + struct scsi_device *sdev = to_scsi_device(dev); + int err = 0; + + /* reset the prep_fn back to the default since the + * driver may have altered it and it's being removed */ + blk_queue_prep_rq(sdev->request_queue, scsi_prep_fn); + + if (drv && drv->remove) + err = drv->remove(dev); + + return 0; +} struct bus_type scsi_bus_type = { .name = "scsi", diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 16add9a..9ca12d1 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -1596,6 +1596,8 @@ static int sd_revalidate_disk(struct gen return 0; } +struct device *my_scsi_disk; + /** * sd_probe - called during driver initialization and whenever a * new scsi device is attached to the system. It is called once @@ -1715,6 +1717,7 @@ static int sd_probe(struct device *dev) scsi_use_ULD_pm(sdp, 1); scsi_autosuspend_device(sdp); + my_scsi_disk = dev; return 0; out_suspend: @@ -1835,6 +1838,8 @@ static int sd_suspend(struct device *dev struct scsi_disk *sdkp = scsi_disk_get_from_dev(dev); int ret = 0; + printk("sleepy: sd_suspend start\n"); + if (!sdkp) return 0; /* this can happen */ @@ -1845,14 +1850,17 @@ static int sd_suspend(struct device *dev goto done; } - if (mesg.event == PM_EVENT_SUSPEND && - sdkp->device->manage_start_stop) { + if ((mesg.event == PM_EVENT_SUSPEND + ) + && sdkp->device->manage_start_stop) { sd_printk(KERN_NOTICE, sdkp, "Stopping disk\n"); ret = sd_start_stop_device(sdkp, 0); } done: scsi_disk_put(sdkp); + + printk("sleepy: sd_suspend done\n"); return ret; } @@ -1861,6 +1869,8 @@ static int sd_resume(struct device *dev) struct scsi_disk *sdkp = scsi_disk_get_from_dev(dev); int ret = 0; + printk("sleepy: sd_resume start\n"); + if (!sdkp->device->manage_start_stop) goto done; @@ -1869,6 +1879,7 @@ static int sd_resume(struct device *dev) done: scsi_disk_put(sdkp); + printk("sleepy: sd_resume done\n"); return ret; } diff --git a/include/linux/ata.h b/include/linux/ata.h index 78bbaca..df2dd4f 100644 --- a/include/linux/ata.h +++ b/include/linux/ata.h @@ -298,6 +298,13 @@ enum { SCR_ACTIVE = 3, SCR_NOTIFICATION = 4, + /* SControl subfields, each field is 4 bit wide */ + ATA_SCTL_DET = 0, /* lsb */ + ATA_SCTL_SPD = 1, + ATA_SCTL_IPM = 2, + ATA_SCTL_SPM = 3, + ATA_SCTL_PMP = 4, + /* SError bits */ SERR_DATA_RECOVERED = (1 << 0), /* recovered data error */ SERR_COMM_RECOVERED = (1 << 1), /* recovered comm failure */ @@ -441,8 +448,12 @@ static inline int ata_is_data(u8 prot) #define ata_id_is_ata(id) (((id)[0] & (1 << 15)) == 0) #define ata_id_has_lba(id) ((id)[49] & (1 << 9)) #define ata_id_has_dma(id) ((id)[49] & (1 << 8)) +#define ata_id_has_sata(id) ((id)[76] && (id)[76] != 0xffff) #define ata_id_has_ncq(id) ((id)[76] & (1 << 8)) #define ata_id_queue_depth(id) (((id)[75] & 0x1f) + 1) +#define ata_id_has_hips(id) (ata_id_has_sata(id) && ((id)[76] & (1 << 9))) +#define ata_id_has_dips(id) (ata_id_has_sata(id) && ((id)[78] & (1 << 3))) +#define ata_id_dips_enabled(id) (ata_id_has_sata(id) && ((id)[79] & (1 << 3))) #define ata_id_removeable(id) ((id)[0] & (1 << 7)) #define ata_id_has_atapi_AN(id) \ ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \ diff --git a/include/linux/pm.h b/include/linux/pm.h index eccf59e..3a02c91 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -143,6 +143,9 @@ typedef struct pm_message { * the upcoming system state (such as PCI_D3hot), and enable * wakeup events as appropriate. * + * HIBERNATE Enter a low power device state appropriate for the hibernation + * state (eg. ACPI S4) and enable wakeup events as appropriate. + * * FREEZE Quiesce operations so that a consistent image can be saved; * but do NOT otherwise enter a low power device state, and do * NOT emit system wakeup events. @@ -167,14 +170,16 @@ #define PM_EVENT_ON 0 #define PM_EVENT_FREEZE 1 #define PM_EVENT_SUSPEND 2 #define PM_EVENT_PRETHAW 3 +#define PM_EVENT_HIBERNATE 4 #define PMSG_FREEZE ((struct pm_message){ .event = PM_EVENT_FREEZE, }) #define PMSG_PRETHAW ((struct pm_message){ .event = PM_EVENT_PRETHAW, }) #define PMSG_SUSPEND ((struct pm_message){ .event = PM_EVENT_SUSPEND, }) +#define PMSG_HIBERNATE ((struct pm_message){ .event = PM_EVENT_HIBERNATE, }) #define PMSG_ON ((struct pm_message){ .event = PM_EVENT_ON, }) struct dev_pm_info { - pm_message_t power_state; + pm_message_t __deprecated power_state; unsigned can_wakeup:1; #ifdef CONFIG_PM_SLEEP unsigned should_wakeup:1; @@ -246,6 +251,24 @@ #define device_init_wakeup(dev,val) \ device_set_wakeup_enable(dev,val); \ } while(0) +void detect_idle(void); +void enter_auto_sleep(int length); +extern atomic_t cpu_needed; + +struct sleep_disabled_reason { + char *text; +}; + +static inline void disable_auto_sleep(struct sleep_disabled_reason *reason) +{ + atomic_inc(&cpu_needed); +} + +static inline void enable_auto_sleep(struct sleep_disabled_reason *reason) +{ + atomic_dec(&cpu_needed); +} + /* * Global Power Management flags * Used to keep APM and ACPI from both being active diff --git a/kernel/power/Makefile b/kernel/power/Makefile index f7dfff2..e5693d6 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -5,7 +5,7 @@ endif obj-y := main.o obj-$(CONFIG_PM_LEGACY) += pm.o -obj-$(CONFIG_PM_SLEEP) += process.o console.o +obj-$(CONFIG_PM_SLEEP) += process.o console.o sleepy.o obj-$(CONFIG_HIBERNATION) += swsusp.o disk.o snapshot.o swap.o user.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o diff --git a/kernel/power/main.c b/kernel/power/main.c index a29da58..2470e94 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -3,6 +3,7 @@ * * Copyright (c) 2003 Patrick Mochel * Copyright (c) 2003 Open Source Development Lab + * Copyright (c) 2007 Pavel Machek <pavel@xxxxxxx> * * This file is released under the GPLv2 * @@ -22,7 +23,10 @@ #include <linux/freezer.h> #include <linux/vmstat.h> #include <linux/syscalls.h> +#include <asm/percpu.h> + #include "power.h" +#include "../../drivers/scsi/scsi_priv.h" DEFINE_MUTEX(pm_mutex); @@ -269,6 +273,7 @@ void __attribute__ ((weak)) arch_suspend * @state: state to enter * * This function should be called after devices have been suspended. + * May not sleep. */ static int suspend_enter(suspend_state_t state) { @@ -277,6 +282,7 @@ static int suspend_enter(suspend_state_t arch_suspend_disable_irqs(); BUG_ON(!irqs_disabled()); + printk("Device_power_down\n"); if ((error = device_power_down(PMSG_SUSPEND))) { printk(KERN_ERR "PM: Some devices failed to power down\n"); goto Done; @@ -296,6 +302,8 @@ static int suspend_enter(suspend_state_t * suspend_devices_and_enter - suspend devices and enter the desired system * sleep state. * @state: state to enter + * + * Needs to be called from process state and may sleep. */ int suspend_devices_and_enter(suspend_state_t state) { @@ -339,6 +347,7 @@ int suspend_devices_and_enter(suspend_st Finish: if (suspend_ops->finish) suspend_ops->finish(); + Resume_devices: suspend_test_start(); device_resume(); @@ -392,6 +401,8 @@ static inline int valid_state(suspend_st * happen when we wake up. * Then, do the setup for suspend, enter the state, and cleaup (after * we've woken up). + * + * Needs to be called from process context, and may sleep. */ static int enter_state(suspend_state_t state) { @@ -426,6 +437,76 @@ static int enter_state(suspend_state_t s return error; } +/* Returns how long it waited in ms */ +//extern long (*panic_blink)(long time); + +int slept; + +/* + list crying_babies? + + struct one_baby { + list_head; + char *name; + } + + struct one_baby { "morning tasks" }; +*/ + +struct sleep_disabled_reason wakeup_tasks = { + "wakeup_tasks" +}; + +int device_suspend_fake(pm_message_t state); + +int +do_auto_sleep(void) +{ + int error,i; + int state = PM_SUSPEND_MEM; + + /* add baby "morning tasks" to the lists. BUG if it is not + the only baby */ + + if (slept) + return; + slept++; + disable_auto_sleep(&wakeup_tasks); +// device_suspend_fake(PMSG_SUSPEND); + suspend_enter(state); + + /* FIXME: run "morning tasks" in process context; drivers need to check why + machine was woken, and perhaps put themselves back to crying babies list + if lid was opened or something. */ + + enable_auto_sleep(&wakeup_tasks); + +#if 0 + printk("finishing\n"); + if (suspend_ops->finish) + suspend_ops->finish(); +#endif + + return 0; +} + +extern int set_alarm(int length); + +void +enter_auto_sleep(int length) +{ + int error; + + if (atomic_read(&cpu_needed)) + return; + + printk("Auto sleeping\n"); + set_alarm(length); + + error = do_auto_sleep(); + if (error) + printk("enter auto sleep failed: %d\n", error); +} /** * pm_suspend - Externally visible function for suspending system. @@ -481,6 +562,12 @@ #endif return (s - buf); } +extern struct pci_dev *my_pdev; +extern int autosuspend_enabled; +extern struct device *my_scsi_disk; +extern int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg); +extern int ahci_pci_device_resume(struct pci_dev *pdev); + static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { @@ -495,6 +582,63 @@ #endif p = memchr(buf, '\n', n); len = p ? p - buf : n; + if (len == 5 && !strncmp(buf, "adisk", len)) { + autosuspend_enabled = 1; + error = 0; + goto Exit; + } + + if (len == 4 && !strncmp(buf, "down", len)) { + scsi_bus_suspend(my_scsi_disk, PMSG_SUSPEND); + ahci_pci_device_suspend(my_pdev, PMSG_SUSPEND); + error = 0; + goto Exit; + } + + if (len == 2 && !strncmp(buf, "up", len)) { + ahci_pci_device_resume(my_pdev); + scsi_bus_resume(my_scsi_disk); + error = 0; + goto Exit; + } + + if (len == 3 && !strncmp(buf, "now", len)) { + do_auto_sleep(); + error = 0; + goto Exit; + } + + if (len == 4 && !strncmp(buf, "now2", len)) { + suspend_devices_and_enter(PM_SUSPEND_MEM); + error = 0; + goto Exit; + } + + if (len == 4 && !strncmp(buf, "auto", len)) { + static int acpi_ready = 0; + if (!acpi_ready) { + int error; + int state = PM_SUSPEND_MEM; + + if (suspend_ops->begin) { + error = suspend_ops->begin(state); + if (error) + return error; + } + + if (suspend_ops->prepare) { + error = suspend_ops->prepare(); + if (error) + return error; + } + acpi_ready = 1; + } + atomic_dec(&cpu_needed); + printk("CPU needed now = %d\n", atomic_read(&cpu_needed)); + error = 0; + goto Exit; + } + /* First, check if we are requested to hibernate */ if (len == 4 && !strncmp(buf, "disk", len)) { error = hibernate(); diff --git a/kernel/power/power.h b/kernel/power/power.h index 700f44e..fa93da8 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -1,3 +1,6 @@ +#ifndef KERNEL_POWER_POWER_H +#define KERNEL_POWER_POWER_H + #include <linux/suspend.h> #include <linux/suspend_ioctls.h> #include <linux/utsname.h> @@ -225,3 +228,4 @@ static inline void suspend_thaw_processe { } #endif +#endif diff --git a/kernel/power/sleepy.c b/kernel/power/sleepy.c new file mode 100644 index 0000000..45a178c --- /dev/null +++ b/kernel/power/sleepy.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2007 Pavel Machek <pavel@xxxxxxx> + * + * This file is released under the GPLv2 + * + */ + +#include <linux/module.h> +#include <linux/suspend.h> +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/cpu.h> +#include <linux/resume-trace.h> +#include <linux/freezer.h> +#include <linux/vmstat.h> +#include <linux/syscalls.h> +#include <linux/rtc.h> +#include <linux/kthread.h> + +#include <asm/percpu.h> + +#include "power.h" + diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index d3d94c1..0fab3ca 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -287,3 +287,87 @@ static int __init init_timer_list_procfs return 0; } __initcall(init_timer_list_procfs); + +/* + * Sleepy linux support + */ + +#include <linux/suspend.h> +#include <asm/atomic.h> + +atomic_t cpu_needed = ATOMIC_INIT(1); + +static s64 +detect_active_timers(struct hrtimer_clock_base *base, s64 first_timer) +{ + struct hrtimer *timer, tmp; + unsigned long next = 0, i; + struct rb_node *curr; + unsigned long flags; + +next_one: + i = 0; + spin_lock_irqsave(&base->cpu_base->lock, flags); + + curr = base->first; + /* + * Crude but we have to do this O(N*N) thing, because + * we have to unlock the base when printing: + */ + while (curr && i < next) { + curr = rb_next(curr); + i++; + } + + if (curr) { + timer = rb_entry(curr, struct hrtimer, node); + tmp = *timer; + spin_unlock_irqrestore(&base->cpu_base->lock, flags); + +// printk("[%Ld]", ktime_to_ns(tmp.expires)); + first_timer = min_t(s64, first_timer, ktime_to_ns(tmp.expires)); + + next++; + goto next_one; + } + spin_unlock_irqrestore(&base->cpu_base->lock, flags); + return first_timer; +} + +void detect_idle(void) +{ + int i; + s64 first_timer = (3600ULL*NSEC_PER_SEC); + int cpu; + s64 now; + + + /* + * Other CPUs could be non-idle, leading to nastiness: + * + * 1) they could be running timer code, and we would mistakenly call them idle + * + * 2) we can't sleep in SMP mode, anyway + */ + if (num_online_cpus() != 1) + return; + + if (atomic_read(&cpu_needed) > 1) + return; + + for_each_online_cpu(cpu) { + struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu); + for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) { +// printk("_"); + first_timer = detect_active_timers(cpu_base->clock_base + i, first_timer); + } + } + now = ktime_to_ns(ktime_get()); + if (first_timer > (now + 3ULL*NSEC_PER_SEC)) { + long long seconds = ((long long) first_timer - (long long) now); + + do_div(seconds, NSEC_PER_SEC); + printk("nohz: Ready for ~ %Ld msec (%Ld sec) wait, %d\n", (long long) first_timer - (long long) now, seconds, atomic_read(&cpu_needed)); + enter_auto_sleep(seconds-1); + } +} diff --git a/mm/slab.c b/mm/slab.c index 473e6c2..87b7f06 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -944,7 +944,7 @@ static void __cpuinit start_cpu_timer(in init_reap_node(cpu); INIT_DELAYED_WORK(reap_work, cache_reap); schedule_delayed_work_on(cpu, reap_work, - __round_jiffies_relative(HZ, cpu)); + __round_jiffies_relative(HZ*10000, cpu)); /* FIXME !*/ } } diff --git a/mm/vmstat.c b/mm/vmstat.c index 422d960..ce25ad1 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -793,7 +793,7 @@ #endif /* CONFIG_PROC_FS */ #ifdef CONFIG_SMP static DEFINE_PER_CPU(struct delayed_work, vmstat_work); -int sysctl_stat_interval __read_mostly = HZ; +int sysctl_stat_interval __read_mostly = 10000 * HZ; static void vmstat_update(struct work_struct *w) { -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html _______________________________________________ linux-pm mailing list linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/linux-pm