--- linux.orig/Documentation/power/video.txt 2004-08-14 23:10:42.000000000 +0200 +++ linux/Documentation/power/video.txt 2004-11-01 14:29:51.000000000 +0100 @@ -26,12 +26,17 @@ point, but it happens to work on some machines. Use acpi_sleep=s3_bios (Athlon64 desktop system) +* systems where an excursion into real mode _after_ the pci subsystem + wakes up is sufficient. It has better chances of working than s3_bios. + Use acpi_sleep=s3_late_bios (Dell D600, Fujitsu P2120 ; both run radeon + boards, but not all radeons seem to like this). + * radeon systems, where X can soft-boot your video card. You'll need patched X, and plain text console (no vesafb or radeonfb), see http://www.doesi.gmxhome.de/linux/tm800s3/s3.html. (Acer TM 800) Now, if you pass acpi_sleep=something, and it does not work with your -bios, you'll get hard crash during resume. Be carefull. +bios, you'll get hard crash during resume. Be careful. You may have system where none of above works. At that point you either invent another ugly hack that works, or write proper driver for --- linux.orig/arch/i386/kernel/acpi/sleep.c 2004-10-31 10:55:40.000000000 +0100 +++ linux/arch/i386/kernel/acpi/sleep.c 2004-11-06 23:57:44.000000000 +0100 @@ -7,6 +7,8 @@ #include #include +#include +#include #include @@ -63,6 +65,86 @@ zap_low_mappings(); } +/* + * acpi_vgapost - trip into real mode in order to post the vga card. + * + * slot is typically pdev->bus->number << 8 | pdev->devfn + * where pdev is a struct pci_dev * + */ +extern struct Xgt_desc_struct go_real_gdtr; +extern struct Xgt_desc_struct go_real_idtr; +extern u16 go_real_jmp_seg; + +extern void do_vgapost_lowlevel (void); + +int acpi_vgapost (unsigned long slot) +{ + unsigned long flags; + unsigned long saved_video_flags; + u32 low_mapping; + struct user_desc info; + struct desc_struct gdt[3]; + + if ((acpi_video_flags & ACPI_SLEEP_S3_LATE_MASK) == 0) + return -1; + if (num_online_cpus() != 1) /* do you dare ? */ + return -EPERM; + + /* Load the required info for the jump to real mode. The code in + * here could maybe go to acpi_reserve_bootmem instead, so that + * it's done once and for all. + */ + + low_mapping = virt_to_phys((void*)acpi_wakeup_address); + + go_real_jmp_seg = low_mapping >> 4; + + /* ldt and gdt descriptors are the same, hence the ldt macros. + * however, we want the privilege flag be set to 0 (reboot is + * waiting at the corner otherwise). */ + memset(gdt,0,sizeof(gdt)); + + memset(&info,0,sizeof(info)); + info.limit = 0xffff; + info.base_addr = low_mapping; + info.seg_32bit = 0; + + info.contents = MODIFY_LDT_CONTENTS_CODE; + gdt[1].a = LDT_entry_a(&info); + gdt[1].b = LDT_entry_b(&info) & ~(0x6000); /* clear dpl */ + + info.contents = MODIFY_LDT_CONTENTS_DATA; + gdt[2].a = LDT_entry_a(&info); + gdt[2].b = LDT_entry_b(&info) & ~(0x6000); /* clear dpl */ + + go_real_gdtr.size = sizeof(gdt)-1; + go_real_gdtr.address = (u32) &gdt; + go_real_idtr.size = (1<<10)-1; + go_real_idtr.address = 0; + + /* Copy the wakeup code again (this time with the _late_ flags). */ + + saved_video_flags = acpi_video_flags; + acpi_video_flags = (slot & 0xffff) << 16 | + ACPI_SLEEP_S3_LATE_BITS(acpi_video_flags); + + acpi_save_state_mem(); + + /* Tunnel thru real mode, call the wakeup code, and return. We're + * typically called here from pci_default_resume, hence w/ irqs + * on. */ + local_irq_save(flags); + do_vgapost_lowlevel(); + local_irq_restore(flags); + + /* Restore mapping etc */ + acpi_restore_state_mem(); + + /* restore normal acpi_video_flags for next resume */ + acpi_video_flags = saved_video_flags; + return 0; +} + /** * acpi_reserve_bootmem - do _very_ early ACPI initialisation * @@ -87,9 +169,17 @@ { while ((str != NULL) && (*str != '\0')) { if (strncmp(str, "s3_bios", 7) == 0) - acpi_video_flags = 1; + acpi_video_flags = ACPI_SLEEP_S3_BIOS; if (strncmp(str, "s3_mode", 7) == 0) - acpi_video_flags |= 2; + acpi_video_flags |= ACPI_SLEEP_S3_MODE; + if (strncmp(str, "s3_late_bios", 12) == 0) + acpi_video_flags |= ACPI_SLEEP_S3_LATE_BIOS; + /* This last one is there for ``symmetry'', but frankly I + * don't think it's any useful. Well, for vesafb lovers maybe. + * Don't forget to celebrate if it works for you (???). + */ + if (strncmp(str, "s3_late_mode", 12) == 0) + acpi_video_flags |= ACPI_SLEEP_S3_LATE_MODE; str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); --- linux.orig/arch/i386/kernel/acpi/wakeup.S 2004-10-31 10:55:40.000000000 +0100 +++ linux/arch/i386/kernel/acpi/wakeup.S 2004-11-06 23:54:30.000000000 +0100 @@ -2,6 +2,7 @@ #include #include #include +#include # # wakeup_code runs in real mode, and at unknown address (determined at run-time). @@ -41,15 +42,16 @@ cmpl $0x12345678, %eax jne bogus_real_magic - testl $1, video_flags - wakeup_code + testl $ACPI_SLEEP_S3_BIOS, video_flags - wakeup_code jz 1f + movw video_flags - wakeup_code + 2, %ax lcall $0xc000,$3 movw %cs, %ax movw %ax, %ds # Bios might have played with that movw %ax, %ss 1: - testl $2, video_flags - wakeup_code + testl $ACPI_SLEEP_S3_MODE, video_flags - wakeup_code jz 1f mov video_mode - wakeup_code, %ax call mode_set @@ -159,6 +161,27 @@ _setbad: jmp setbad +# +# Real mode switch - verbatim from reboot.c +# +go_real: + movl %cr0, %eax + andl $0x00000011, %eax + orl $0x60000000, %eax + movl %eax, %cr0 + movl %eax, %cr3 + movl %cr0, %ebx + andl $0x60000000, %ebx + jz 1f + wbinvd +1: andb $0x10, %al + movl %eax, %cr0 + .byte 0xea + .word 0x0000 +.globl go_real_jmp_seg +go_real_jmp_seg: + .word 0x0000 + .code32 ALIGN @@ -244,6 +267,7 @@ ENTRY(saved_magic) .long 0 ENTRY(saved_eip) .long 0 +.text save_registers: leal 4(%esp), %eax movl %eax, saved_context_esp @@ -284,6 +308,42 @@ call acpi_enter_sleep_state_s4bios ret +ENTRY(do_vgapost_lowlevel) + # Save state and registers + call save_processor_state + call save_registers + # Reload page table with low mapping + movl $swapper_pg_dir-__PAGE_OFFSET, %eax + movl %eax, %cr3 + # Load IDTR and GDTR for real mode + lidt go_real_idtr + lgdt go_real_gdtr + # Load DS & al ; 0x10 = third segment desc. in gdt (data segment) + movl $0x0010, %eax + movl %eax, %ss + movl %eax, %ds + movl %eax, %es + movl %eax, %fs + movl %eax, %gs + # Load CS ; 0x08 = second segment desc. in gdt (code segment) + ljmpl $0x0008, $(go_real - wakeup_start) + # returns occurs from ret_point in do_suspend_lowlevel. cpu + # state and registers will be restored from there, and we'll + # eventually return to acpi_vgapost where we came from. + + .align 8 + .word 0 +.globl go_real_gdtr +go_real_gdtr: + .word 0 + .long 0 + + .word 0 +.globl go_real_idtr +go_real_idtr: + .word 0 + .long 0 + ALIGN # saved registers saved_gdt: .long 0,0 --- linux.orig/drivers/pci/pci-driver.c 2004-10-26 10:19:42.000000000 +0200 +++ linux/drivers/pci/pci-driver.c 2004-11-01 19:14:20.000000000 +0100 @@ -336,6 +336,19 @@ /* if the device was busmaster before the suspend, make it busmaster again */ if (pci_dev->is_busmaster) pci_set_master(pci_dev); +#if defined(CONFIG_X86) && defined(CONFIG_ACPI_SLEEP) + if ((pci_dev->class & 0xffff00) == (PCI_CLASS_DISPLAY_VGA << 8) + && (pci_dev->dev.power_state != 4)) + { + /* This is a no-op, and returns -1, in case s3_bios_late + * hasn't been specified on the kernel command line + */ + if (!acpi_vgapost(pci_dev->bus->number << 8 | pci_dev->devfn)) { + printk(KERN_INFO "resumed pci graphics adapter %s\n", + pci_name(pci_dev)); + } + } +#endif } static int pci_device_resume(struct device * dev) --- linux.orig/include/asm-i386/acpi.h 2004-10-26 10:19:50.000000000 +0200 +++ linux/include/asm-i386/acpi.h 2004-11-01 19:31:28.000000000 +0100 @@ -27,6 +27,7 @@ #define _ASM_ACPI_H #ifdef __KERNEL__ +#ifndef __ASSEMBLY__ #include /* defines cmpxchg */ @@ -187,10 +188,26 @@ /* early initialization routine */ extern void acpi_reserve_bootmem(void); +/* real mode excursion to post code from vga rom ; must come once pci is up. */ +extern int acpi_vgapost(unsigned long); + #endif /*CONFIG_ACPI_SLEEP*/ extern u8 x86_acpiid_to_apicid[]; +#endif /*__ASSEMBLY__*/ + +#ifdef CONFIG_ACPI_SLEEP + +#define ACPI_SLEEP_S3_BIOS 1 +#define ACPI_SLEEP_S3_MODE 2 +#define ACPI_SLEEP_S3_LATE_BIOS 4 +#define ACPI_SLEEP_S3_LATE_MODE 8 +#define ACPI_SLEEP_S3_LATE_MASK 12 +#define ACPI_SLEEP_S3_LATE_BITS(X) (((X)&12)>>2) + +#endif /*CONFIG_ACPI_SLEEP*/ + #endif /*__KERNEL__*/ #endif /*_ASM_ACPI_H*/