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