To sleep, perchance to dream (try #3)
Jim Gettys
jg at laptop.org
Tue Jan 9 22:57:50 EST 2007
Absolutely wonderful, Jordan!
- Jim
On Tue, 2007-01-09 at 16:27 -0700, Jordan Crouse wrote:
> If at first you don't suspend, try, try again.
>
> After several aborted tries, we finally have some happily sleeping code.
> It even wakes up - though much like myself after a night in Vegas, it
> doesn't remember where it is. Luckily for me, my amnesia usually isn't
> permanent.. :)
>
> I won't bore you with the details unless you really want them, but suffice
> to say, VSA was setting up the descriptors wrong. Also, we had missed a
> GPIO setup to allow us to actually turn off VCORE_CPU.
>
> Attached is the kernel patch, and also a OFW patch for Mitch that plugs
> in the correct descriptor and GPIO values. The next step is to get the
> resume address written into memory and make LinuxBIOS/OFW do the
> right thing to bring us back to the kernel.
>
> Jordan
> plain text document attachment (olpc-suspend.patch)
> Index: git/arch/i386/kernel/Makefile
> ===================================================================
> --- git.orig/arch/i386/kernel/Makefile
> +++ git/arch/i386/kernel/Makefile
> @@ -47,7 +47,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
>
> # vsyscall.o contains the vsyscall DSO images as __initdata.
> # We must build both images before we can assemble it.
> Index: git/arch/i386/kernel/olpc-pm.c
> ===================================================================
> --- git.orig/arch/i386/kernel/olpc-pm.c
> +++ git/arch/i386/kernel/olpc-pm.c
> @@ -1,5 +1,6 @@
> /* olpc-pm.c
> * © 2006 Red Hat, Inc.
> + * Portions also copyright 2006 Advanced Micro Devices, Inc.
> * GPLv2
> */
>
> @@ -8,40 +9,180 @@
> #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
> +*/
> +
> extern int machine_is_olpc;
> #define PM_IRQ 3
>
> -
> +#define MSR_LBAR_GPIO 0x5140000C
> #define MSR_LBAR_ACPI 0x5140000E
> #define MSR_LBAR_PMS 0x5140000F
>
> +#define CS5536_PM_PWRBTN (1 << 8)
> +#define CS5536_PM_RTC (1 << 10)
> +
> +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);
> +
> + /* only accept the power button as an event */
>
> - /* It has to be 0x100 for now because we don't permit anything else */
> - if (sts & 0x100) {
> + if (sts & CS5536_PM_PWRBTN) {
> input_report_key(pm_inputdev, KEY_POWER, 1);
> input_sync(pm_inputdev);
> /* Do we need to delay this (and hence schedule_work)? */
> input_report_key(pm_inputdev, KEY_POWER, 0);
> input_sync(pm_inputdev);
> } else {
> - printk(KERN_WARNING "Strange PM1_STS %x\n", sts);
> + printk(KERN_WARNING "olcp-pm: Strange PM1_STS %x\n", sts);
> }
>
> 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;
> +}
> +
> +/* 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"
> + "or $0x2000, %%ax\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));
> +}
> +
> +/* [29:0] is the delay */
> +#define PM_SCLK_VAL 0x40000e00
> +
> +/* [29:0] is the delay */
> +#define PM_SED_VAL 0x40004601
> +
> +/* [19:0] is the delay */
> +#define PM_WKXD_VAL 0x040000a0
> +
> +int asmlinkage
> +olpc_enter_sleep_state(u8 sleep_state)
> +{
> + u32 reg;
> +
> + /* Set up the PMC sleep clock */
> + outl(PM_SCLK_VAL, pms_base + 0x10);
> +
> + /* Set up the Sleep End Delay */
> + outl(PM_SED_VAL, pms_base + 0x14);
> +
> + /* Set up the WORK_AUX delay */
> + outl(PM_WKXD_VAL, pms_base + 0x34);
> +
> + /* Clear PM_SSC */
> + outl(0x2FFFF, pms_base + 0x54);
> +
> + /* Save ourselves a read by setting the SCI events again here -
> + * we have to write the register anyway */
> +
> + /* FIXME: Set any other SCI events that we might want here */
> +
> + outl((CS5536_PM_PWRBTN << 16) | 0xFFFF, acpi_base);
> +
> + /* FIXME: Set any GPE events that we want here */
> + /* FIXME: Clear pending GPE events if we end up using any */
> +
> + /* Actually go to sleep */
> + olpc_sleep_asm();
> +
> + return 0;
> +}
> +
> +/* This code will slowly disappear as we fixup the issues in the BIOS */
> +
> +static void __init olpc_fixup_bios(void)
> +{
> + unsigned long hi, lo, base;
> +
> + /* The VSA aggressively sets up the ACPI and PM register for
> + * trapping - its not enough to force these values in the BIOS -
> + * they seem to be changed during PCI init as well.
> + */
> +
> + /* Change the PM registers to decode to the DD */
> +
> + hi = 0x80000001;
> + lo = 0x400fff80;
> + wrmsr(0x510100e2, lo, hi);
> +
> + /* Change the ACPI registers to decode to the DD */
> +
> + hi = 0x80000001;
> + lo = 0x840ffff0;
> + wrmsr(0x510100e3, lo, hi);
> +
> + /* GPIO24 needs to be set to its auxillary function as
> + * the WORK_AUX signal */
> +
> + rdmsr(MSR_LBAR_GPIO, lo, hi);
> +
> + base = lo & 0x0000ffff;
> + outl(0x100, base + 0x90);
> + outl(0x100, base + 0x84);
> +}
> +
> static int __init olpc_pm_init(void)
> {
> uint32_t lo, hi;
> @@ -51,8 +192,11 @@ static int __init olpc_pm_init(void)
> return -ENODEV;
>
> pm_inputdev = input_allocate_device();
> +
> if (!pm_inputdev)
> return -ENOMEM;
> +
> + olpc_fixup_bios();
>
> rdmsr(MSR_LBAR_ACPI, lo, hi);
> /* Check the mask and whether GPIO is enabled (sanity check) */
> @@ -71,7 +215,7 @@ static int __init olpc_pm_init(void)
> pms_base = lo & 0x0000ffff;
>
> lo = inl(pms_base + 0x40);
> - printk("PM_FSD was %08x\n", lo);
> +
> /* Lock, enable failsafe, 4 seconds */
> outl(0xc001f400, pms_base + 0x40);
>
> @@ -106,8 +250,12 @@ static int __init olpc_pm_init(void)
> return ret;
> }
>
> - /* Set only power button to generate SCI */
> - outw(0x100, acpi_base + 2);
> + /* Here we set up the SCI events we're interested in during
> + * real-time. We have no sleep button, and the RTC doesn't make
> + * sense, so set up the power button
> + */
> +
> + outl(inl(acpi_base) | ((CS5536_PM_PWRBTN) << 16), acpi_base);
>
> /* Select level triggered in PIC */
> if (sci < 8) {
> @@ -120,21 +268,40 @@ 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);
> +
> +#if 0
> + 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);
> + }
> +#endif
> +
> + 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);
>
> Index: git/arch/i386/kernel/olpc-wakeup.S
> ===================================================================
> --- /dev/null
> +++ git/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
> +
> plain text document attachment (fixup-descriptors.patch)
> Index: svn/cpu/x86/pc/olpc/chipinit.fth
> ===================================================================
> --- svn.orig/cpu/x86/pc/olpc/chipinit.fth 2007-01-03 12:58:36.000000000 -0700
> +++ svn/cpu/x86/pc/olpc/chipinit.fth 2007-01-09 10:42:45.000000000 -0700
> @@ -361,8 +361,8 @@
> msr: 5101.0024 400000fe.01bfffff. \ P2D_BMK Descriptor 1 UHCI
> msr: 5101.00e0 60000000.1f0ffff8. \ IOD_BM Descriptor 0 ATA IO address
> msr: 5101.00e1 a0000001.480fff80. \ IOD_BM Descriptor 1
> -msr: 5101.00e2 00000001.400fff80. \ IOD_BM Descriptor 2
> -msr: 5101.00e3 00000001.840ffff0. \ IOD_BM Descriptor 3
> +msr: 5101.00e2 80000001.400fff80. \ IOD_BM Descriptor 2
> +msr: 5101.00e3 80000001.840ffff0. \ IOD_BM Descriptor 3
> msr: 5101.00e4 00000001.858ffff8. \ IOD_BM Descriptor 4
> msr: 5101.00e5 60000001.8a0ffff0. \ IOD_BM Descriptor 5
> msr: 5101.00eb 00000000.f0301850. \ IOD_SC Descriptor 1
> @@ -538,10 +538,10 @@
> h# 0000 h# 107c pw! \ GPIO_05_EVENT_COUNT
> h# 6066 h# 107e pw! \ GPIO_05_EVENTCOMPARE_VALUE
> \ h# ffff0000 h# 1080 pl! \ GPIOH_ OUTPUT_VALUE
> - h# 660000a6 h# 1084 pl! \ GPIOH_OUTPUT_ENABLE
> + h# 660001a6 h# 1084 pl! \ GPIOH_OUTPUT_ENABLE
> h# ea0fb060 h# 1088 pl! \ GPIOH_OUT_OPENDRAIN
> h# 0000a6a8 h# 108c pl! \ GPIOH_OUTPUT_INVERT_ENABLE
> - h# 10b06066 h# 1090 pl! \ GPIOH_OUT_AUX1_SELECT
> + h# 10b06166 h# 1090 pl! \ GPIOH_OUT_AUX1_SELECT
> h# 00a6a8ea h# 1094 pl! \ GPIOH_OUT_AUX2_SELECT
> h# b0606600 h# 1098 pl! \ GPIOH_PULLUP_ENABLE
> h# a6a8ea11 h# 109c pl! \ GPIOH_PULLDOWN_ENABLE
> _______________________________________________
> Devel mailing list
> Devel at laptop.org
> http://mailman.laptop.org/mailman/listinfo/devel
--
Jim Gettys
One Laptop Per Child
More information about the Devel
mailing list