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
> [email protected]
> http://mailman.laptop.org/mailman/listinfo/devel
-- 
Jim Gettys
One Laptop Per Child


_______________________________________________
Devel mailing list
[email protected]
http://mailman.laptop.org/mailman/listinfo/devel

Reply via email to