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