Entirely untested and not working suspend code

Jordan Crouse jordan.crouse at amd.com
Thu Jan 4 20:53:44 EST 2007


> This patch adds code which may just about manage to work in some 
> respect. Possibly. I wouldn't really expect it to, though. Assuming I 
> haven't done anything stupid, once this patch is applied, 
> /sys/power/state should show mem. Echoing mem into it may (or may not) 
> cause the machine to sequence into some sort of suspended state.
 
Following up on my action item from the call - I give you an extended
version of Matthew's patch - that is probably even less likely to work
then his disclaimer indicated.  It compiles, but thats about it.  I plan
to try it tomorrow - hopefully stuff turns off.  In the mean time,
I send it so the intelligentsia can look it over and comment accordingly.

Before you comment on my copious and bewildering use of outl/inl, please
read my comment at the top of the file, and read the eratta document.
Trust me - I really mean for it to be that way.

The other thing I want to do is quickly annotate the assembly code:

;; Here - we get the value of PM1_CTR and remember it for later
;; %%ebx is expected to hold the address of acpi_base at this point

"add $0x08, %%bx\n\t"
"movw %%bx, %%dx\n\t"
"inl %%dx, %%eax\n\t"
"movw %%ax, %%%di\n\t"

;; Invalidate the cache - at this point, we're hands off the memory
"wbinvd\n\t"

;; This is probably the confusing part - there is a bug in the GX that
;; says we need to disable the refresh interval before going into self
;; refresh.  I won't get into details, but suffice to say, bad things
;; happen.  Mitch - pay careful attention here, because you may need to
;; re-enable the refresh interval when you resume, and pull the memory
;; out of self-refresh

"movl $0x20000018, %%ecx\n\t"
"rdmsr\n\t"
"and $0xFF0000FF, %%eax\n\t"
"wrmsr\n\t"

;; Here we put the memory into self refresh

"movw $0x2004, %%cx\n\t"
"xor %%edx, %%edx\n\t"
"xor %%eax, %%eax\n\t"
"movb $0x04, %%al\n\t"
"wrmsr\n\t",

;; And finally, we whack PM1_CTR - sleepy time for us!
;; the outl is atomic, so shutdown should be immediate

"movw %%bx, %%dx\n\t"
"movzx %%di, %%eax\n\t"
"outl %%eax, %%dx\n\t"

And there you go.  The rest of the code should be pretty clear - I've
tried to comment it as best I can.  I'm sure future versions of this patch
will follow, but please comment if you can, or catch me on IRC.

Jordan
-------------- next part --------------
diff --git a/arch/i386/kernel/Makefile b/arch/i386/kernel/Makefile
index c8eee11..7ae7bd4 100644
--- a/arch/i386/kernel/Makefile
+++ b/arch/i386/kernel/Makefile
@@ -44,7 +44,7 @@ EXTRA_AFLAGS   := -traditional
 
 obj-$(CONFIG_SCx200)		+= scx200.o
 obj-$(CONFIG_OLPC)		+= olpc.o
-obj-$(CONFIG_OLPC_PM)		+= olpc-pm.o
+obj-$(CONFIG_OLPC_PM)           += olpc-pm.o olpc-wakeup.o
 obj-$(CONFIG_PROC_OFW)		+= callofw.o
 
 # vsyscall.o contains the vsyscall DSO images as __initdata.
diff --git a/arch/i386/kernel/olpc-pm.c b/arch/i386/kernel/olpc-pm.c
index 01c8adc..7f2c668 100644
--- a/arch/i386/kernel/olpc-pm.c
+++ b/arch/i386/kernel/olpc-pm.c
@@ -8,25 +8,47 @@ #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/delay.h>
 #include <linux/input.h>
+#include <linux/suspend.h>
+#include <linux/bootmem.h>
 #include <asm/io.h>
 
+/* A few words about accessing the ACPI and PM registers.  Long story short,
+   byte and word accesses of the ACPI and PM registers is broken.  The only
+   way to do it really correctly is to use dword accesses, which we do throughout
+   this code.  For more details, please consult Eratta 17 and 18 here:
+
+   http://www.amd.com/files/connectivitysolutions/geode/geode_gx/34472D_CS5536_B1_specupdate.pdf
+*/
+
+/* These are the wake sources that we will use to wake the system back up 
+   For now, just enable the power button - later we may do the RTC as well. 
+   We can also set up any number of the GPE0 bits too - though thats a different register 
+*/
+
+#define OLPC_PM1_WAKE_SOURCES 0x100
+
 extern int machine_is_olpc;
 #define PM_IRQ 3
 
-
 #define MSR_LBAR_ACPI		0x5140000E
 #define MSR_LBAR_PMS		0x5140000F
 
+unsigned long olpc_wakeup_address = 0;
+extern char wakeup_start, wakeup_end;
+extern void do_olpc_suspend_lowlevel(void);
+extern unsigned long FASTCALL(olpc_copy_wakeup_routine(unsigned long));
+
 static unsigned long acpi_base;
 static unsigned long pms_base;
 static int sci;
 
 static struct input_dev *pm_inputdev;
+static struct pm_ops olpc_pm_ops;
 
 static int olpc_pm_interrupt(int irq, void *id)
 {
-	uint16_t sts = inw(acpi_base);
-	outw(sts, acpi_base);
+	uint32_t sts = inl(acpi_base);
+	outl(sts | 0xFFFF, acpi_base);
 
 	/* It has to be 0x100 for now because we don't permit anything else */
 	if (sts & 0x100) {
@@ -42,6 +64,75 @@ static int olpc_pm_interrupt(int irq, vo
 	return IRQ_HANDLED;
 }
 
+static int olpc_pm_state_valid (suspend_state_t pm_state)
+{
+        if (pm_state == PM_SUSPEND_MEM)
+                return 1;
+        return 0;
+}
+
+static int olpc_pm_enter (suspend_state_t pm_state)
+{
+        /* Only STR is supported */
+        if (pm_state != PM_SUSPEND_MEM)
+                return -EINVAL;
+
+        /* Save CPU state */
+        do_olpc_suspend_lowlevel();
+	return 0;
+}
+
+#define PM_WKXD_VAL  0x400000a0
+
+/* Put the memory into self refresh and go to sleep */
+
+static inline void olpc_sleep_asm(void)
+{
+  __asm__ __volatile__( "add $0x08, %%bx\n\t"
+			"movw %%bx, %%dx\n\t"
+			"inl %%dx, %%eax\n\t"
+			"movw %%ax, %%di\n\t"
+			"wbinvd\n\t"
+			"movl $0x20000018, %%ecx\n\t"
+			"rdmsr\n\t"
+			"and $0xFF0000FF, %%eax\n\t"
+			"wrmsr\n\t"
+			"movw $0x2004, %%cx\n\t"
+			"xor %%edx, %%edx\n\t"
+			"xor %%eax, %%eax\n\t"
+			"movb $0x04, %%al\n\t"
+			"wrmsr\n\t"
+			"movw %%bx, %%dx\n\t"
+			"movzx %%di, %%eax\n\t"
+			"outl %%eax, %%dx\n\t"
+			: : "b" (acpi_base));
+}
+
+int asmlinkage
+olpc_enter_sleep_state(u8 sleep_state)
+{
+	u32 reg;
+
+	/* PM1_CTR is on standby, so its the most obvious place to put our sleep mode */
+
+	reg = inl(acpi_base + 0x08);
+	reg |= 0x3 << 12;
+	outl(reg, acpi_base + 0x08);
+
+	/* Set up the WORK_AUX rail to go away when we assert SLP_EN with a delay of 0xA0 */
+	outl(0x400000a0, pms_base + 0x34);
+	
+	/* FIXME: Clear the pending GPE events if we end up using any */
+       
+	/* Clear any pending PM1 events */
+	outl(inl(acpi_base) | 0xFFFF, acpi_base);
+
+	/* Acutally go to sleep */
+	olpc_sleep_asm();
+
+	return 0;
+}
+
 static int __init olpc_pm_init(void)
 {
 	uint32_t lo, hi;
@@ -106,8 +197,7 @@ static int __init olpc_pm_init(void)
 		return ret;
 	}
 
-	/* Set only power button to generate SCI */
-	outw(0x100, acpi_base + 2);
+	outl(inl(acpi_base) | OLPC_PM1_WAKE_SOURCES << 16, acpi_base);
 
 	/* Select level triggered in PIC */
 	if (sci < 8) {
@@ -120,21 +210,38 @@ static int __init olpc_pm_init(void)
 		outb(lo, 0x4d1);
 	}
 	/* Clear pending interrupt */
-	outw(inw(acpi_base), acpi_base);
+	outl(inl(acpi_base) | 0xFFFF, acpi_base);
+
+	olpc_wakeup_address = (unsigned long)alloc_bootmem_low(PAGE_SIZE);
+
+	/* FIXME: stuff this in a register so the firmware can read it
+	   at resume time */
+
+	if (olpc_wakeup_address) {
+		memcpy((void *)olpc_wakeup_address, &wakeup_start,
+		       &wakeup_end - &wakeup_start);
+		olpc_copy_wakeup_routine(olpc_wakeup_address);
+	}
+
+	pm_set_ops(&olpc_pm_ops);
 
 	return 0;
 }
 
-
 static void olpc_pm_exit(void)
 {
-	/* Disable all events */
-	outw(0, acpi_base+2);
+	/* Clear any pending events, and disable them */
+	outl(0xFFFF, acpi_base+2);
 
 	free_irq(sci, &acpi_base);
 	input_unregister_device(pm_inputdev);
 }
 
+static struct pm_ops olpc_pm_ops = {
+        .valid = olpc_pm_state_valid,
+        .enter = olpc_pm_enter,
+};
+
 module_init(olpc_pm_init);
 module_exit(olpc_pm_exit);
 
diff --git a/arch/i386/kernel/olpc-wakeup.S b/arch/i386/kernel/olpc-wakeup.S
new file mode 100644
index 0000000..21ef6b2
--- /dev/null
+++ b/arch/i386/kernel/olpc-wakeup.S
@@ -0,0 +1,228 @@
+.text
+#include <linux/linkage.h>
+#include <asm/segment.h>
+#include <asm/page.h>
+
+#
+# wakeup_code runs in real mode, and at unknown address (determined at run-time).
+# Therefore it must only use relative jumps/calls. 
+#
+# Do we need to deal with A20? It is okay: ACPI specs says A20 must be enabled
+#
+# If physical address of wakeup_code is 0x12345, BIOS should call us with
+# cs = 0x1234, eip = 0x05
+# 
+
+ALIGN
+	.align	4096
+ENTRY(wakeup_start)
+wakeup_code:
+	wakeup_code_start = .
+	.code16
+
+ 	movw	$0xb800, %ax
+	movw	%ax,%fs
+
+	cli
+	cld
+
+	# setup data segment
+	movw	%cs, %ax
+	movw	%ax, %ds					# Make ds:0 point to wakeup_start
+	movw	%ax, %ss
+	mov	$(wakeup_stack - wakeup_code), %sp		# Private stack is needed for ASUS board
+
+	pushl	$0						# Kill any dangerous flags
+	popfl
+
+	movl	real_magic - wakeup_code, %eax
+	cmpl	$0x12345678, %eax
+	jne	bogus_real_magic
+
+	# set up page table
+	movl	$swsusp_pg_dir-__PAGE_OFFSET, %eax
+	movl	%eax, %cr3
+
+	testl	$1, real_efer_save_restore - wakeup_code
+	jz	4f
+	# restore efer setting
+	movl	real_save_efer_edx - wakeup_code, %edx
+	movl	real_save_efer_eax - wakeup_code, %eax
+	mov     $0xc0000080, %ecx
+	wrmsr
+4:
+	# make sure %cr4 is set correctly (features, etc)
+	movl	real_save_cr4 - wakeup_code, %eax
+	movl	%eax, %cr4
+	movw	$0xb800, %ax
+	movw	%ax,%fs
+	
+	# need a gdt -- use lgdtl to force 32-bit operands, in case
+	# the GDT is located past 16 megabytes.
+	lgdtl	real_save_gdt - wakeup_code
+
+	movl	real_save_cr0 - wakeup_code, %eax
+	movl	%eax, %cr0
+	jmp 1f
+1:
+
+	movl	real_magic - wakeup_code, %eax
+	cmpl	$0x12345678, %eax
+	jne	bogus_real_magic
+
+	ljmpl	$__KERNEL_CS,$wakeup_pmode_return
+
+real_save_gdt:	.word 0
+		.long 0
+real_save_cr0:	.long 0
+real_save_cr3:	.long 0
+real_save_cr4:	.long 0
+real_magic:	.long 0
+real_efer_save_restore:	.long 0
+real_save_efer_edx: 	.long 0
+real_save_efer_eax: 	.long 0
+
+bogus_real_magic:
+	jmp bogus_real_magic
+
+setbad:	clc
+	ret
+
+_setbad: jmp setbad
+
+	.code32
+	ALIGN
+
+.org	0x800
+wakeup_stack_begin:	# Stack grows down
+
+.org	0xff0		# Just below end of page
+wakeup_stack:
+ENTRY(wakeup_end)
+	
+.org	0x1000
+
+wakeup_pmode_return:
+	movw	$__KERNEL_DS, %ax
+	movw	%ax, %ss
+	movw	%ax, %ds
+	movw	%ax, %es
+	movw	%ax, %fs
+	movw	%ax, %gs
+
+	# reload the gdt, as we need the full 32 bit address
+	lgdt	saved_gdt
+	lidt	saved_idt
+	lldt	saved_ldt
+	ljmp	$(__KERNEL_CS),$1f
+1:
+	movl	%cr3, %eax
+	movl	%eax, %cr3
+	wbinvd
+
+	# and restore the stack ... but you need gdt for this to work
+	movl	saved_context_esp, %esp
+
+	movl	%cs:saved_magic, %eax
+	cmpl	$0x12345678, %eax
+	jne	bogus_magic
+
+	# jump to place where we left off
+	movl	saved_eip,%eax
+	jmp	*%eax
+
+bogus_magic:
+	jmp	bogus_magic
+
+
+##
+# olpc_copy_wakeup_routine
+#
+# Copy the above routine to low memory.
+#
+# Parameters:
+# %eax:	place to copy wakeup routine to
+#
+# Returned address is location of code in low memory (past data and stack)
+#
+ENTRY(olpc_copy_wakeup_routine)
+
+	sgdt	saved_gdt
+	sidt	saved_idt
+	sldt	saved_ldt
+	str	saved_tss
+
+	movl	nx_enabled, %edx
+	movl	%edx, real_efer_save_restore - wakeup_start (%eax)
+	testl	$1, real_efer_save_restore - wakeup_start (%eax)
+	jz	2f
+	# save efer setting
+	pushl	%eax
+	movl	%eax, %ebx
+	mov     $0xc0000080, %ecx
+	rdmsr
+	movl	%edx, real_save_efer_edx - wakeup_start (%ebx)
+	movl	%eax, real_save_efer_eax - wakeup_start (%ebx)
+	popl	%eax
+2:
+
+	movl    %cr3, %edx
+	movl    %edx, real_save_cr3 - wakeup_start (%eax)
+	movl    %cr4, %edx
+	movl    %edx, real_save_cr4 - wakeup_start (%eax)
+	movl	%cr0, %edx
+	movl	%edx, real_save_cr0 - wakeup_start (%eax)
+	sgdt    real_save_gdt - wakeup_start (%eax)
+
+	movl	$0x12345678, real_magic - wakeup_start (%eax)
+	movl	$0x12345678, saved_magic
+	ret
+
+save_registers:
+	leal	4(%esp), %eax
+	movl	%eax, saved_context_esp
+	movl %ebx, saved_context_ebx
+	movl %ebp, saved_context_ebp
+	movl %esi, saved_context_esi
+	movl %edi, saved_context_edi
+	pushfl ; popl saved_context_eflags
+
+	movl $ret_point, saved_eip
+	ret
+
+
+restore_registers:
+	movl saved_context_ebp, %ebp
+	movl saved_context_ebx, %ebx
+	movl saved_context_esi, %esi
+	movl saved_context_edi, %edi
+	pushl saved_context_eflags ; popfl
+	ret	
+
+ENTRY(do_olpc_suspend_lowlevel)
+	call	save_processor_state
+	call	save_registers
+	pushl	$3
+	call	olpc_enter_sleep_state
+	addl	$4, %esp
+
+#	In case of S3 failure, we'll emerge here.  Jump
+# 	to ret_point to recover
+	jmp	ret_point
+	.p2align 4,,7
+ret_point:
+	call	restore_registers
+	call	restore_processor_state
+	ret
+
+.data
+ALIGN
+ENTRY(saved_magic)	.long	0
+ENTRY(saved_eip)	.long	0
+
+# saved registers
+saved_gdt:	.long	0,0
+saved_idt:	.long	0,0
+saved_ldt:	.long	0
+saved_tss:	.long	0
+


More information about the Devel mailing list