FYI: My current suspend bigdiff

Pavel Machek
2003-11-24T17:03:26Z

Hi!

This is how my current "bigdiff" looks... [This is not for
application, but if you want to make S3 working, it might help you].

Pavel

%patch
Index: linux/drivers/input/serio/i8042.c
===================================================================
--- linux.orig/drivers/input/serio/i8042.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/serio/i8042.c 2003-11-22 16:56:05.000000000 +0100
@@ -18,6 +18,7 @@
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/serio.h>
+#include <linux/sysdev.h>

#include <asm/io.h>

@@ -398,18 +399,15 @@
* desired.
*/

-static int __init i8042_controller_init(void)
+static int i8042_controller_init(void)
{
-
/*
* Test the i8042. We need to know if it thinks it's working correctly
* before doing anything else.
*/

i8042_flush();
-
if (i8042_reset) {
-
unsigned char param;

if (i8042_command(&param, I8042_CMD_CTL_TEST)) {
@@ -783,6 +781,32 @@
values->mux = index;
}

+static int i8042_resume_port(struct serio *port)
+{
+ struct serio_dev *dev = port->dev;
+ if (dev && dev->resume)
+ dev->resume(port);
+}
+
+static int i8042_resume(struct sys_device *dev)
+{
+ if (i8042_controller_init())
+ printk(KERN_ERR "i8042: resume failed\n");
+ i8042_resume_port(&i8042_aux_port);
+ i8042_resume_port(&i8042_kbd_port);
+ return 0;
+}
+
+static struct sysdev_class kbc_sysclass = {
+ set_kset_name("i8042"),
+ .resume = i8042_resume,
+};
+
+static struct sys_device device_i8042 = {
+ .id = 0,
+ .cls = &kbc_sysclass,
+};
+
int __init i8042_init(void)
{
int i;
@@ -819,6 +843,14 @@

register_reboot_notifier(&i8042_notifier);

+ {
+ int error = sysdev_class_register(&kbc_sysclass);
+ if (!error)
+ error = sys_device_register(&device_i8042);
+ if (error)
+ printk(KERN_CRIT "Unable to register i8042 to driver model\n");
+ }
+
return 0;
}

Index: linux/drivers/input/keyboard/atkbd.c
===================================================================
--- linux.orig/drivers/input/keyboard/atkbd.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/keyboard/atkbd.c 2003-11-24 10:31:28.000000000 +0100
@@ -145,7 +145,7 @@
unsigned char set;
unsigned char release;
int lastkey;
- volatile signed char ack;
+ atomic_t ack;
unsigned char emul;
unsigned short id;
unsigned char write;
@@ -214,10 +214,10 @@

switch (code) {
case ATKBD_RET_ACK:
- atkbd->ack = 1;
+ atomic_set(&atkbd->ack, 1);
goto out;
case ATKBD_RET_NAK:
- atkbd->ack = -1;
+ atomic_set(&atkbd->ack, 2);
goto out;
}

@@ -294,7 +294,7 @@
static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
{
int timeout = 20000; /* 200 msec */
- atkbd->ack = 0;
+ atomic_set(&atkbd->ack, 0);

#ifdef ATKBD_DEBUG
printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte);
@@ -302,9 +302,9 @@
if (serio_write(atkbd->serio, byte))
return -1;

- while (!atkbd->ack && timeout--) udelay(10);
+ while (!atomic_read(&atkbd->ack) && timeout--) udelay(10);

- return -(atkbd->ack <= 0);
+ return -(atomic_read(&atkbd->ack) != 1);
}

/*
@@ -582,6 +582,37 @@
kfree(atkbd);
}

+int atkbd_hwinit(struct atkbd *atkbd)
+{
+ if (atkbd->write) {
+ if (atkbd_probe(atkbd)) {
+ return -EIO;
+ }
+
+ atkbd->set = atkbd_set_3(atkbd);
+ atkbd_enable(atkbd);
+
+ } else {
+ atkbd->set = 2;
+ atkbd->id = 0xab00;
+ }
+
+ if (atkbd->set == 4) {
+ atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) | BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
+ sprintf(atkbd->name, "AT Set 2 Extended keyboard");
+ } else
+ sprintf(atkbd->name, "AT %s Set %d keyboard",
+ atkbd->translated ? "Translated" : "Raw", atkbd->set);
+ return 0;
+}
+
+static void atkbd_resume(struct serio *serio)
+{
+ struct atkbd *atkbd = serio->private;
+ if (atkbd_hwinit(atkbd))
+ printk(KERN_ERR "Ouch, keyboard no longer there after resume?\n");
+}
+
/*
* atkbd_connect() is called when the serio module finds and interface
* that isn't handled yet by an appropriate device driver. We check if
@@ -641,29 +672,11 @@
return;
}

- if (atkbd->write) {
-
- if (atkbd_probe(atkbd)) {
- serio_close(serio);
- kfree(atkbd);
- return;
- }
-
- atkbd->set = atkbd_set_3(atkbd);
- atkbd_enable(atkbd);
-
- } else {
- atkbd->set = 2;
- atkbd->id = 0xab00;
+ if (atkbd_hwinit(atkbd)) {
+ serio_close(serio);
+ kfree(atkbd);
+ return;
}
-
- if (atkbd->set == 4) {
- atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) | BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
- sprintf(atkbd->name, "AT Set 2 Extended keyboard");
- } else
- sprintf(atkbd->name, "AT %s Set %d keyboard",
- atkbd->translated ? "Translated" : "Raw", atkbd->set);
-
sprintf(atkbd->phys, "%s/input0", serio->phys);

if (atkbd->set == 3)
@@ -688,10 +701,11 @@
}

-static struct serio_dev atkbd_dev = {
+struct serio_dev atkbd_dev = {
.interrupt = atkbd_interrupt,
.connect = atkbd_connect,
.disconnect = atkbd_disconnect,
+ .resume = atkbd_resume,
.cleanup = atkbd_cleanup,
};

Index: linux/include/linux/serio.h
===================================================================
--- linux.orig/include/linux/serio.h 2003-11-24 11:30:30.000000000 +0100
+++ linux/include/linux/serio.h 2003-11-22 16:54:24.000000000 +0100
@@ -50,6 +50,7 @@
unsigned int, struct pt_regs *);
void (*connect)(struct serio *, struct serio_dev *dev);
void (*disconnect)(struct serio *);
+ void (*resume)(struct serio *);
void (*cleanup)(struct serio *);

struct list_head node;
Index: linux/drivers/input/power.c
===================================================================
--- linux.orig/drivers/input/power.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/power.c 2003-07-23 00:18:13.000000000 +0200
@@ -72,6 +72,18 @@
break;
case KEY_POWER:
/* Hum power down the machine. */
+ {
+ char *argv[2] = {NULL, NULL};
+ char *envp[3] = {NULL, NULL, NULL};
+
+ argv[0] = "/sbin/poweroff";
+
+ /* minimal command environment */
+ envp[0] = "HOME=/";
+ envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+
+ call_usermodehelper(argv[0], argv, envp, 0);
+ }
break;
default:
return;
Index: linux/drivers/acpi/thermal.c
===================================================================
--- linux.orig/drivers/acpi/thermal.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/acpi/thermal.c 2003-11-24 10:45:49.000000000 +0100
@@ -467,6 +467,7 @@
if (result)
return_VALUE(result);

+ printk(KERN_EMERG "Critical temperature reached (%d C), shutting down.\n", tz->temperature);
acpi_bus_generate_event(device, ACPI_THERMAL_NOTIFY_CRITICAL, tz->trips.critical.flags.enabled);

acpi_thermal_call_usermode(ACPI_THERMAL_PATH_POWEROFF);
Index: linux/arch/i386/kernel/cpu/mtrr/main.c
===================================================================
--- linux.orig/arch/i386/kernel/cpu/mtrr/main.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/arch/i386/kernel/cpu/mtrr/main.c 2003-10-23 00:42:26.000000000 +0200
@@ -588,6 +588,7 @@
{
int i;

+#if 0
for (i = 0; i < num_var_ranges; i++) {
if (mtrr_state[i].lsize)
set_mtrr(i,
@@ -596,6 +597,8 @@
mtrr_state[i].ltype);
}
kfree(mtrr_state);
+#endif
+
return 0;
}

Index: linux/arch/x86_64/kernel/time.c
===================================================================
--- linux.orig/arch/x86_64/kernel/time.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/arch/x86_64/kernel/time.c 2003-10-09 00:19:18.000000000 +0200
@@ -22,7 +22,7 @@
#include <linux/time.h>
#include <linux/ioport.h>
#include <linux/module.h>
-#include <linux/device.h>
+#include <linux/sysdev.h>
#include <linux/bcd.h>
#include <asm/pgtable.h>
#include <asm/vsyscall.h>
@@ -75,7 +75,7 @@
* timer interrupt has happened already, but vxtime.trigger wasn't updated yet.
* This is not a problem, because jiffies hasn't updated either. They are bound
* together by xtime_lock.
- */
+ */

static inline unsigned int do_gettimeoffset_tsc(void)
{
@@ -626,7 +626,17 @@
return 0;
}

-void __init pit_init(void)
+static struct sysdev_class rtc_sysclass = {
+ set_kset_name("rtc"),
+};
+
+/* XXX this driverfs stuff should probably go elsewhere later -john */
+static struct sys_device device_i8253 = {
+ .id = 0,
+ .cls = &rtc_sysclass,
+};
+
+int __init pit_init(void)
{
unsigned long flags;

@@ -635,6 +645,11 @@
outb_p(LATCH & 0xff, 0x40); /* LSB */
outb_p(LATCH >> 8, 0x40); /* MSB */
spin_unlock_irqrestore(&i8253_lock, flags);
+
+ int error = sysdev_class_register(&rtc_sysclass);
+ if (!error)
+ error = sys_device_register(&device_i8253);
+ return error;
}

int __init time_setup(char *str)
@@ -678,8 +693,8 @@
cpu_khz = hpet_calibrate_tsc();
timename = "HPET";
} else {
- pit_init();
- cpu_khz = pit_calibrate_tsc();
+ pit_init();
+ cpu_khz = pit_calibrate_tsc();
timename = "PIT";
}

Index: linux/include/linux/suspend.h
===================================================================
--- linux.orig/include/linux/suspend.h 2003-11-24 11:30:30.000000000 +0100
+++ linux/include/linux/suspend.h 2003-10-18 23:06:22.000000000 +0200
@@ -45,31 +45,43 @@
/* mm/page_alloc.c */
extern void drain_local_pages(void);

+/* kernel/power/swsusp.c */
+extern int software_suspend(void);
+
extern unsigned int nr_copy_pages __nosavedata;
extern suspend_pagedir_t *pagedir_nosave __nosavedata;
-#endif /* CONFIG_PM */
-
-#ifdef CONFIG_SOFTWARE_SUSPEND
-
-extern unsigned char software_suspend_enabled;

-extern void software_suspend(void);
#else /* CONFIG_SOFTWARE_SUSPEND */
-static inline void software_suspend(void)
+static inline int software_suspend(void)
{
printk("Warning: fake suspend called\n");
+ return -EPERM;
}
+#define software_resume() do { } while(0)
#endif /* CONFIG_SOFTWARE_SUSPEND */

#ifdef CONFIG_PM
extern void refrigerator(unsigned long);
+extern int freeze_processes(void);
+extern void thaw_processes(void);
+
+extern int pm_prepare_console(void);
+extern void pm_restore_console(void);

#else
static inline void refrigerator(unsigned long flag)
{

}
+static inline int freeze_processes(void)
+{
+ return 0;
+}
+static inline void thaw_processes(void)
+{
+
+}
#endif /* CONFIG_PM */

#endif /* _LINUX_SWSUSP_H */
Index: linux/kernel/power/swsusp.c
===================================================================
--- linux.orig/kernel/power/swsusp.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/kernel/power/swsusp.c 2003-10-23 00:59:17.000000000 +0200
@@ -73,9 +73,8 @@

extern void do_magic(int resume);

-#define NORESUME 1
-#define RESUME_SPECIFIED 2
-
+#define NORESUME 1
+#define RESUME_SPECIFIED 2

#define __ADDRESS(x) ((unsigned long) phys_to_virt(x))
#define ADDRESS(x) __ADDRESS((x) << PAGE_SHIFT)
@@ -227,6 +226,7 @@
static void read_swapfiles(void) /* This is called before saving image */
{
int i, len;
+ char buff[sizeof(resume_file)], *sname;

len=strlen(resume_file);
root_swap = 0xFFFF;
@@ -245,8 +245,11 @@
swapfile_used[i] = SWAPFILE_IGNORED;
} else {
/* we ignore all swap devices that are not the resume_file */
- if (1) {
-// FIXME if(resume_device == swap_info[i].swap_device) {
+ sname = d_path(swap_info[i].swap_file->f_dentry,
+ swap_info[i].swap_file->f_vfsmnt,
+ buff,
+ sizeof(buff));
+ if (!strcmp(sname, resume_file)) {
swapfile_used[i] = SWAPFILE_SUSPEND;
root_swap = i;
} else {
@@ -283,8 +286,8 @@
* would happen on next reboot -- corrupting data.
*
* Note: The buffer we allocate to use to write the suspend header is
- * not freed; its not needed since system is going down anyway
- * (plus it causes oops and I'm lazy^H^H^H^Htoo busy).
+ * not freed; its not needed since the system is going down anyway
+ * (plus it causes an oops and I'm lazy^H^H^H^Htoo busy).
*/
static int write_suspend_image(void)
{
@@ -488,33 +491,6 @@
printk("|\n");
}

-/* Make disk drivers accept operations, again */
-static void drivers_unsuspend(void)
-{
- device_resume();
-}
-
-/* Called from process context */
-static int drivers_suspend(void)
-{
- return device_suspend(4);
-}
-
-#define RESUME_PHASE1 1 /* Called from interrupts disabled */
-#define RESUME_PHASE2 2 /* Called with interrupts enabled */
-#define RESUME_ALL_PHASES (RESUME_PHASE1 | RESUME_PHASE2)
-static void drivers_resume(int flags)
-{
- if (flags & RESUME_PHASE1) {
- device_resume();
- }
- if (flags & RESUME_PHASE2) {
-#ifdef SUSPEND_CONSOLE
- update_screen(fg_console); /* Hmm, is this the problem? */
-#endif
- }
-}
-
static int suspend_prepare_image(void)
{
struct sysinfo i;
@@ -569,7 +545,7 @@

static void suspend_save_image(void)
{
- drivers_unsuspend();
+ device_resume();

lock_swapdevices();
write_suspend_image();
@@ -615,6 +591,7 @@
mb();
spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */

+ device_power_down(4);
PRINTK( "Waiting for DMAs to settle down...\n");
mdelay(1000); /* We do not want some readahead with DMA to corrupt our memory, right?
Do it with disabled interrupts for best effect. That way, if some
@@ -630,8 +607,10 @@

PRINTK( "Freeing prev allocated pagedir\n" );
free_suspend_pagedir((unsigned long) pagedir_save);
+ device_power_up();
spin_unlock_irq(&suspend_pagedir_lock);
- drivers_resume(RESUME_ALL_PHASES);
+ device_resume();
+ update_screen(fg_console); /* Hmm, is this the problem? */

PRINTK( "Fixing swap signatures... " );
mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
@@ -672,7 +651,9 @@
{
int is_problem;
read_swapfiles();
+ device_power_down(4);
is_problem = suspend_prepare_image();
+ device_power_up();
spin_unlock_irq(&suspend_pagedir_lock);
if (!is_problem) {
kernel_fpu_end(); /* save_processor_state() does kernel_fpu_begin, and we need to revert it in order to pass in_atomic() checks */
@@ -694,11 +675,22 @@
mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
}

-static void do_software_suspend(void)
+/*
+ * This is main interface to the outside world. It needs to be
+ * called from process context.
+ */
+int software_suspend(void)
{
+ int res;
+ if (!software_suspend_enabled)
+ return -EAGAIN;
+
+ software_suspend_enabled = 0;
+ might_sleep();
+
if (arch_prepare_suspend()) {
printk("%sArchitecture failed to prepare\n", name_suspend);
- return;
+ return -EPERM;
}
if (pm_prepare_console())
printk( "%sCan't allocate a console... proceeding\n", name_suspend);
@@ -716,7 +708,7 @@
blk_run_queues();

/* Save state of all device drivers, and stop them. */
- if(drivers_suspend()==0)
+ if ((res = device_suspend(4))==0)
/* If stopping device drivers worked, we proceed basically into
* suspend_save_image.
*
@@ -728,24 +720,12 @@
*/
do_magic(0);
thaw_processes();
- }
+ } else
+ res = -EBUSY;
software_suspend_enabled = 1;
MDELAY(1000);
pm_restore_console();
-}
-
-/*
- * This is main interface to the outside world. It needs to be
- * called from process context.
- */
-void software_suspend(void)
-{
- if(!software_suspend_enabled)
- return;
-
- software_suspend_enabled = 0;
- might_sleep();
- do_software_suspend();
+ return res;
}

/* More restore stuff */
@@ -915,7 +895,7 @@

extern dev_t __init name_to_dev_t(const char *line);

-static int __read_suspend_image(struct block_device *bdev, union diskpage *cur, int noresume)
+static int __init __read_suspend_image(struct block_device *bdev, union diskpage *cur, int noresume)
{
swp_entry_t next;
int i, nr_pgdir_pages;
@@ -1091,6 +1071,7 @@
printk( "resuming from %s\n", resume_file);
if (read_suspend_image(resume_file, 0))
goto read_failure;
+ device_suspend(4);
do_magic(1);
panic("This never returns");

Index: linux/kernel/sys.c
===================================================================
--- linux.orig/kernel/sys.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/kernel/sys.c 2003-10-26 13:09:00.000000000 +0100
@@ -472,13 +472,11 @@

#ifdef CONFIG_SOFTWARE_SUSPEND
case LINUX_REBOOT_CMD_SW_SUSPEND:
- if (!software_suspend_enabled) {
+ {
+ int ret = software_suspend();
unlock_kernel();
- return -EAGAIN;
+ return ret;
}
- software_suspend();
- do_exit(0);
- break;
#endif

default:
Index: linux/Documentation/power/swsusp.txt
===================================================================
--- linux.orig/Documentation/power/swsusp.txt 2003-11-24 11:30:30.000000000 +0100
+++ linux/Documentation/power/swsusp.txt 2003-10-26 12:49:22.000000000 +0100
@@ -17,13 +17,30 @@
You need to append resume=/dev/your_swap_partition to kernel command
line. Then you suspend by echo 4 > /proc/acpi/sleep.

-[Notice. Rest docs is pretty outdated (see date!) It should be safe to
-use swsusp on ext3/reiserfs these days.]
+Pavel's unreliable guide to swsusp mess
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are currently two versions of swap suspend in the kernel, the old
+"Pavel's" version in kernel/power/swsusp.c and the new "Patrick's"
+version in kernel/power/pmdisk.c. They provide the same functionality;
+the old version looks ugly but was tested, while the new version looks
+nicer but did not receive so much testing. echo 4 > /proc/acpi/sleep
+calls the old version, echo disk > /sys/power/state calls the new one.
+
+[In the future, when the new version is stable enough, two things can
+happen:
+
+* the new version is moved into swsusp.c, and swsusp is renamed to swap
+ suspend (Pavel prefers this)
+
+* pmdisk is kept as is and swsusp.c is removed from the kernel]
+

Article about goals and implementation of Software Suspend for Linux
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Author: G‚ábor Kuti
-Last revised: 2002-04-08
+Last revised: 2003-10-20 by Pavel Machek

Idea and goals to achieve

@@ -36,84 +53,23 @@
interrupt our programs so processes that are calculating something for a long
time shouldn't need to be written interruptible.

-On desk machines the power saving function isn't as important as it is in
-laptops but we really may benefit from the second one. Nowadays the number of
-desk machines supporting suspend function in their APM is going up but there
-are (and there will still be for a long time) machines that don't even support
-APM of any kind. On the other hand it is reported that using APM's suspend
-some irqs (e.g. ATA disk irq) is lost and it is annoying for the user until
-the Linux kernel resets the device.
-
-So I started thinking about implementing Software Suspend which doesn't need
-any APM support and - since it uses pretty near only high-level routines - is
-supposed to be architecture independent code.
-
Using the code

-The code is experimental right now - testers, extra eyes are welcome. To
-compile this support into the kernel, you need CONFIG_EXPERIMENTAL,
-and then CONFIG_SOFTWARE_SUSPEND in menu General Setup to be enabled. It
-cannot be used as a module and I don't think it will ever be needed.
-
-You have two ways to use this code. The first one is if you've compiled in
-sysrq support then you may press Sysrq-D to request suspend. The other way
-is with a patched SysVinit (my patch is against 2.76 and available at my
-home page). You might call 'swsusp' or 'shutdown -z '. Next way is to
-echo 4 > /proc/acpi/sleep.
+You have two ways to use this code. The first one is is with a patched
+SysVinit (my patch is against 2.76 and available at my home page). You
+might call 'swsusp' or 'shutdown -z '. Next way is to echo 4 >
+/proc/acpi/sleep.

Either way it saves the state of the machine into active swaps and then
reboots. You must explicitly specify the swap partition to resume from with
``resume='' kernel option. If signature is found it loads and restores saved
state. If the option ``noresume'' is specified as a boot parameter, it skips
-the resuming. Warning! Look at section ``Things to implement'' to see what
-isn't yet implemented. Also I strongly suggest you to list all active swaps
-in /etc/fstab. Firstly because you don't have to specify anything to resume
-and secondly if you have more than one swap area you can't decide which one
-has the 'root' signature.
+the resuming.

In the meantime while the system is suspended you should not touch any of the
hardware!

About the code
-Goals reached
-
-The code can be downloaded from
-http://falcon.sch.bme.hu/~seasons/linux/. It mainly works but there are still
-some of XXXs, TODOs, FIXMEs in the code which seem not to be too important. It
-should work all right except for the problems listed in ``Things to
-implement''. Notes about the code are really welcome.
-
-How the code works
-
-When suspending is triggered it immediately wakes up process bdflush. Bdflush
-checks whether we have anything in our run queue tq_bdflush. Since we queued up
-function do_software_suspend, it is called. Here we shrink everything including
-dcache, inodes, buffers and memory (here mainly processes are swapped out). We
-count how many pages we need to duplicate (we have to be atomical!) then we
-create an appropriate sized page directory. It will point to the original and
-the new (copied) address of the page. We get the free pages by
-__get_free_pages() but since it changes state we have to be able to track it
-later so it also flips in a bit in page's flags (a new Nosave flag). We
-duplicate pages and then mark them as used (so atomicity is ensured). After
-this we write out the image to swaps, do another sync and the machine may
-reboot. We also save registers to stack.
-
-By resuming an ``inverse'' method is executed. The image if exists is loaded,
-loadling is either triggered by ``resume='' kernel option. We
-change our task to bdflush (it is needed because if we don't do this init does
-an oops when it is waken up later) and then pages are copied back to their
-original location. We restore registers, free previously allocated memory,
-activate memory context and task information. Here we should restore hardware
-state but even without this the machine is restored and processes are continued
-to work. I think hardware state should be restored by some list (using
-notify_chain) and probably by some userland program (run-parts?) for users'
-pleasure. Check out my patch at the same location for the sysvinit patch.
-
-WARNINGS!
-- It does not like pcmcia cards. And this is logical: pcmcia cards need
- cardmgr to be initialized. they are not initialized during singleuser boot,
- but "resumed" kernel does expect them to be initialized. That leads to
- armagedon. You should eject any pcmcia cards before suspending.

Things to implement
- SMP support. I've done an SMP support but since I don't have access to a kind
@@ -122,34 +78,14 @@
interrupts AFAIK..
- We should only make a copy of data related to kernel segment, since any
process data won't be changed.
-- Hardware state restoring. Now there's support for notifying via the notify
- chain, event handlers are welcome. Some devices may have microcodes loaded
- into them. We should have event handlers for them as well.
-- We should support other architectures (There are really only some arch
- related functions..)
-- We should also restore original state of swaps if the ``noresume'' kernel
- option is specified.. Or do we need such a feature to save state for some
- other time? Do we need some kind of ``several saved states''? (Linux-HA
- people?). There's been some discussion about checkpointing on linux-future.
- Should make more sanity checks. Or are these enough?

Not so important ideas for implementing

- If a real time process is running then don't suspend the machine.
-- Support for power.conf file as in Solaris, autoshutdown, special
- devicetypes support, maybe in sysctl.
-- Introduce timeout for SMP locking. But first locking ought to work :O
-- Pre-detect if we don't have enough swap space or free it instead of
- calling panic.
- Support for adding/removing hardware while suspended?
- We should not free pages at the beginning so aggressively, most of them
go there anyway..
-- If X is active while suspending then by resuming calling svgatextmode
- corrupts the virtual console of X.. (Maybe this has been fixed AFAIK).
-
-Drivers we support
-- IDE disks are okay
-- vesafb

Drivers that need support
- pc_keyb -- perhaps we can wait for vojtech's input patches
Index: linux/Documentation/power/video.txt
===================================================================
--- linux.orig/Documentation/power/video.txt 2003-11-24 11:30:30.000000000 +0100
+++ linux/Documentation/power/video.txt 2003-10-26 12:49:28.000000000 +0100
@@ -0,0 +1,36 @@
+
+ Video issues with S3 resume
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 2003, Pavel Machek
+
+During S3 resume, hardware needs to be reinitialized. For most
+devices, this is easy, and kernel driver knows how to do
+it. Unfortunately there's one exception: video card. Those are usually
+initialized by BIOS, and kernel does not have enough information to
+boot video card. (Kernel usually does not even contain video card
+driver -- vesafb and vgacon are widely used).
+
+This is not problem for swsusp, because during swsusp resume, BIOS is
+run normally so video card is normally initialized.
+
+There are three types of systems where video works after S3 resume:
+
+* systems where video state is preserved over S3. (HP Omnibook xe3)
+
+* systems that initialize video card into vga text mode and where BIOS
+ works well enough to be able to set video mode. Use
+ acpi_sleep=s3_mode on these. (Toshiba 4030cdt)
+
+* systems where it is possible to call video bios during S3
+ resume. Unfortunately, it is not correct to call video BIOS at that
+ point, but it happens to work on some machines. Use
+ acpi_sleep=s3_bios (Athlon64 desktop system)
+
+Now, if you pass acpi_sleep=something, and it does not work with your
+bios, you'll get hard crash during resume. Be carefull.
+
+You may have system where none of above works. At that point you
+either invent another ugly hack that works, or write proper driver for
+your video card (good luck getting docs :-(). Maybe suspending from X
+(proper X, knowing your hardware, not XF68_FBcon) might have better
+chance of working.
Index: linux/arch/i386/kernel/acpi/wakeup.S
===================================================================
--- linux.orig/arch/i386/kernel/acpi/wakeup.S 2003-11-24 11:30:30.000000000 +0100
+++ linux/arch/i386/kernel/acpi/wakeup.S 2003-10-26 12:49:44.000000000 +0100
@@ -193,11 +193,6 @@
# and restore the stack ... but you need gdt for this to work
movl saved_context_esp, %esp

- movw $0x0e00 + 'W', 0xb8018
- outl %eax, $0x80
- outl %eax, $0x80
- movw $0x0e00 + 'O', 0xb8018
-
movl %cs:saved_magic, %eax
cmpl $0x12345678, %eax
jne bogus_magic
@@ -205,9 +200,6 @@
# jump to place where we left off
movl saved_eip,%eax
movw $0x0e00 + 'x', 0xb8018
- outl %eax, $0x80
- outl %eax, $0x80
- movw $0x0e00 + '!', 0xb801a
jmp *%eax

bogus_magic:
Index: linux/arch/i386/kernel/time.c
===================================================================
--- linux.orig/arch/i386/kernel/time.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/arch/i386/kernel/time.c 2003-10-26 13:08:50.000000000 +0100
@@ -280,16 +280,37 @@
unsigned long retval;

spin_lock(&rtc_lock);
-
retval = mach_get_cmos_time();
-
spin_unlock(&rtc_lock);

return retval;
}

+static long clock_cmos_diff;
+
+static int pit_suspend(struct sys_device *dev, u32 state)
+{
+ /*
+ * Estimate time zone so that set_time can update the clock
+ */
+ clock_cmos_diff = -get_cmos_time();
+ clock_cmos_diff += get_seconds();
+ return 0;
+}
+
+static int pit_resume(struct sys_device *dev)
+{
+ write_seqlock_irq(&xtime_lock);
+ xtime.tv_sec = get_cmos_time() + clock_cmos_diff;
+ xtime.tv_nsec = 0;
+ write_sequnlock_irq(&xtime_lock);
+ return 0;
+}
+
static struct sysdev_class pit_sysclass = {
set_kset_name("pit"),
+ .resume = pit_resume,
+ .suspend = pit_suspend,
};

/* XXX this driverfs stuff should probably go elsewhere later -john */
Index: linux/kernel/power/process.c
===================================================================
--- linux.orig/kernel/power/process.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/kernel/power/process.c 2003-10-26 12:50:56.000000000 +0100
@@ -49,10 +49,11 @@
pr_debug("%s entered refrigerator\n", current->comm);
printk("=");
current->flags &= ~PF_FREEZE;
- if (flag)
- flush_signals(current); /* We have signaled a kernel thread, which isn't normal behaviour
- and that may lead to 100%CPU sucking because those threads
- just don't manage signals. */
+
+ spin_lock_irq(&current->sighand->siglock);
+ recalc_sigpending(); /* We sent fake signal, clean it up */
+ spin_unlock_irq(&current->sighand->siglock);
+
current->flags |= PF_FROZEN;
while (current->flags & PF_FROZEN)
schedule();
Index: linux/drivers/input/keyboard/98kbd.c
===================================================================
--- linux.orig/drivers/input/keyboard/98kbd.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/keyboard/98kbd.c 2003-11-24 11:29:07.000000000 +0100
@@ -100,7 +100,7 @@
char phys[32];
unsigned char cmdbuf[4];
unsigned char cmdcnt;
- signed char ack;
+ atomic_t ack;
unsigned char shift;
struct {
unsigned char scancode;
@@ -118,10 +118,10 @@

switch (data) {
case KBD98_RET_ACK:
- kbd98->ack = 1;
+ atomic_set(&kbd98->ack, 1);
return;
case KBD98_RET_NAK:
- kbd98->ack = -1;
+ atomic_set(&kbd98->ack, 2);
return;
}

@@ -220,14 +220,14 @@
static int kbd98_sendbyte(struct kbd98 *kbd98, unsigned char byte)
{
int timeout = 10000; /* 100 msec */
- kbd98->ack = 0;
+ atomic_set(&kbd98->ack, 0);

if (serio_write(kbd98->serio, byte))
return -1;

- while (!kbd98->ack && timeout--) udelay(10);
+ while (!atomic_read(&kbd98->ack) && timeout--) udelay(10);

- return -(kbd98->ack <= 0);
+ return -(atomic_read(&kbd98->ack) != 1);
}

/*
Index: linux/drivers/input/mouse/psmouse-base.c
===================================================================
--- linux.orig/drivers/input/mouse/psmouse-base.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/mouse/psmouse-base.c 2003-11-24 11:29:30.000000000 +0100
@@ -39,8 +39,8 @@
#define PSMOUSE_LOGITECH_SMARTSCROLL 1

static int psmouse_noext;
-int psmouse_resolution;
-unsigned int psmouse_rate = 60;
+static int psmouse_resolution;
+static unsigned int psmouse_rate = 60;
int psmouse_smartscroll = PSMOUSE_LOGITECH_SMARTSCROLL;
unsigned int psmouse_resetafter;

@@ -121,13 +121,14 @@
if (psmouse->acking) {
switch (data) {
case PSMOUSE_RET_ACK:
- psmouse->ack = 1;
+ atomic_set(&psmouse->ack, 1);
break;
case PSMOUSE_RET_NAK:
- psmouse->ack = -1;
+ atomic_set(&psmouse->ack, 2);
break;
default:
- psmouse->ack = 1; /* Workaround for mice which don't ACK the Get ID command */
+ /* Workaround for mice which don't ACK the Get ID command */
+ atomic_set(&psmouse->ack, 1);
if (psmouse->cmdcnt)
psmouse->cmdbuf[--psmouse->cmdcnt] = data;
break;
@@ -197,7 +198,7 @@
static int psmouse_sendbyte(struct psmouse *psmouse, unsigned char byte)
{
int timeout = 10000; /* 100 msec */
- psmouse->ack = 0;
+ atomic_set(&psmouse->ack, 0);
psmouse->acking = 1;

if (serio_write(psmouse->serio, byte)) {
@@ -205,9 +206,9 @@
return -1;
}

- while (!psmouse->ack && timeout--) udelay(10);
+ while (!atomic_read(&psmouse->ack) && timeout--) udelay(10);

- return -(psmouse->ack <= 0);
+ return -(atomic_read(&psmouse->ack) != 1);
}

/*
@@ -439,8 +440,7 @@
param[0] = 2;
else if (psmouse_resolution >= 50)
param[0] = 1;
- else if (psmouse_resolution)
- param[0] = 0;
+ else param[0] = 0;

psmouse_command(psmouse, param, PSMOUSE_CMD_SETRES);
}
@@ -532,20 +532,11 @@
static int psmouse_pm_callback(struct pm_dev *dev, pm_request_t request, void *data)
{
struct psmouse *psmouse = dev->data;
- struct serio_dev *ser_dev = psmouse->serio->dev;
-
- synaptics_disconnect(psmouse);
-
- /* We need to reopen the serio port to reinitialize the i8042 controller */
- serio_close(psmouse->serio);
- serio_open(psmouse->serio, ser_dev);
-
- /* Probe and re-initialize the mouse */
- psmouse_probe(psmouse);
- psmouse_initialize(psmouse);
- synaptics_pt_init(psmouse);
- psmouse_activate(psmouse);

+ if (request == PM_RESUME) {
+ psmouse->state = PSMOUSE_IGNORE;
+ serio_reconnect(psmouse->serio);
+ }
return 0;
}

Index: linux/drivers/input/mouse/psmouse.h
===================================================================
--- linux.orig/drivers/input/mouse/psmouse.h 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/mouse/psmouse.h 2003-11-24 11:29:43.000000000 +0100
@@ -37,7 +37,7 @@
unsigned long last;
unsigned char state;
char acking;
- volatile char ack;
+ atomic_t ack; /* This is being accessed without locking, at least make sure we do not run into alignment problems */
char error;
char devname[64];
char phys[32];
Index: linux/drivers/input/serio/serio.c
===================================================================
--- linux.orig/drivers/input/serio/serio.c 2003-11-24 11:30:30.000000000 +0100
+++ linux/drivers/input/serio/serio.c 2003-11-24 11:30:06.000000000 +0100
@@ -83,6 +83,7 @@
}

#define SERIO_RESCAN 1
+#define SERIO_RECONNECT 2

static DECLARE_WAIT_QUEUE_HEAD(serio_wait);
static DECLARE_COMPLETION(serio_exited);
@@ -111,6 +112,38 @@
}
}

+static void serio_invalidate_pending_events(struct serio *serio)
+{
+ struct serio_event *event;
+
+ list_for_each_entry(event, &serio_event_list, node)
+ if (event->serio == serio)
+ event->serio = NULL;
+}
+
+static void serio_queue_event(struct serio *serio, int event_type)
+{
+ struct serio_event *event;
+
+ if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
+ event->type = event_type;
+ event->serio = serio;
+
+ list_add_tail(&event->node, &serio_event_list);
+ wake_up(&serio_wait);
+ }
+}
+
+void serio_rescan(struct serio *serio)
+{
+ serio_queue_event(serio, SERIO_RESCAN);
+}
+
+void serio_reconnect(struct serio *serio)
+{
+ serio_queue_event(serio, SERIO_RECONNECT);
+}
+
static int serio_thread(void *nothing)
{
lock_kernel();
@@ -130,20 +163,6 @@
complete_and_exit(&serio_exited, 0);
}

-void serio_rescan(struct serio *serio)
-{
- struct serio_event *event;
-
- if (!(event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC)))
- return;
-
- event->type = SERIO_RESCAN;
- event->serio = serio;
-
- list_add_tail(&event->node, &serio_event_list);
- wake_up(&serio_wait);
-}
-
irqreturn_t serio_interrupt(struct serio *serio,
unsigned char data, unsigned int flags, struct pt_regs *regs)
{

%diffstat
Documentation/power/swsusp.txt | 104 ++++++---------------------
Documentation/power/video.txt | 36 +++++++++
arch/i386/kernel/acpi/wakeup.S | 8 --
arch/i386/kernel/cpu/mtrr/main.c | 3
arch/i386/kernel/suspend_asm.S | 94 ------------------------
arch/i386/kernel/time.c | 25 ++++++
arch/x86_64/kernel/time.c | 25 +++++-
drivers/acpi/thermal.c | 1
drivers/base/power.c | 140 -------------------------------------
drivers/input/keyboard/98kbd.c | 12 +--
drivers/input/keyboard/atkbd.c | 72 +++++++++++--------
drivers/input/mouse/psmouse-base.c | 37 +++------
drivers/input/mouse/psmouse.h | 2
drivers/input/power.c | 12 +++
drivers/input/serio/i8042.c | 40 +++++++++-
drivers/input/serio/serio.c | 47 ++++++++----
include/linux/serio.h | 1
include/linux/suspend.h | 26 +++++-
kernel/power/process.c | 9 +-
kernel/power/swsusp.c | 91 +++++++++---------------
kernel/sys.c | 8 --
21 files changed, 317 insertions(+), 476 deletions(-)

--
When do you have a heart between your knees?
[Johanka's followup: and *two* hearts?]