Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S262120AbTH2Vr7 (ORCPT ); Fri, 29 Aug 2003 17:47:59 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S262079AbTH2Vrr (ORCPT ); Fri, 29 Aug 2003 17:47:47 -0400 Received: from fmr06.intel.com ([134.134.136.7]:24058 "EHLO caduceus.jf.intel.com") by vger.kernel.org with ESMTP id S261682AbTH2Vo3 (ORCPT ); Fri, 29 Aug 2003 17:44:29 -0400 Date: Fri, 29 Aug 2003 13:19:06 -0700 From: Matt Tolentino Message-Id: <200308292019.h7TKJ6FK000649@snoqualmie.dp.intel.com> To: akpm@osdl.org, linux-kernel@vger.kernel.org, torvalds@osdl.org Subject: [UPDATED PATCH] EFI support for ia32 kernels Cc: matthew.e.tolentino@intel.com Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 44274 Lines: 1500 Hi, Attached is an updated patch against 2.6.0-test4 that enables Extensible Firmware Interface (EFI) awareness in ia32 Linux kernels. I've incorporated the feedback I've received since my initial posting (http://marc.theaimsgroup.com/?l=linux-kernel&m=105848983307228&w=2) including: - reorganized initialization code to minimize indentation changes to existing code path. - removed proc version of efivars driver - this driver has been rewritten and will be sent via a separate patch shortly. - reserve memory for memmap using bootmem allocator to ensure that EFI call set_virtual_address_map() functions properly. This patch adds/modifies the following files: arch/i386/kernel/Makefile | 2 arch/i386/kernel/acpi/boot.c | 10 arch/i386/kernel/efi.c | 626 +++++++++++++++++++++++++++++++++++++++++++ arch/i386/kernel/efi_stub.S | 125 ++++++++ arch/i386/kernel/reboot.c | 12 arch/i386/kernel/setup.c | 212 +++++++++++--- arch/i386/kernel/time.c | 60 +++- drivers/acpi/Kconfig | 11 drivers/acpi/osl.c | 1 include/asm-i386/setup.h | 28 + include/linux/efi.h | 28 + init/main.c | 3 12 files changed, 1066 insertions(+), 52 deletions(-) I've been able to successfully boot kernels on EFI systems with this patch using version 3.4 of the ELILO boot loader released last week by Stephane Eranian as well as using GRUB on ia32 systems with legacy BIOS. Special thanks to Bjorn for providing valuable feedback on the initial patch. Please consider applying. thanks, matt diff -urN linux-2.6.0-test4/arch/i386/kernel/acpi/boot.c linux-2.6.0-test4-efi/arch/i386/kernel/acpi/boot.c --- linux-2.6.0-test4/arch/i386/kernel/acpi/boot.c 2003-08-22 16:59:02.000000000 -0700 +++ linux-2.6.0-test4-efi/arch/i386/kernel/acpi/boot.c 2003-08-28 16:05:49.000000000 -0700 @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -274,7 +275,14 @@ acpi_find_rsdp (void) { unsigned long rsdp_phys = 0; - + extern int efi_enabled; + + if (efi_enabled) { + if (efi.acpi20) + return __pa(efi.acpi20); + else if (efi.acpi) + return __pa(efi.acpi); + } /* * Scan memory looking for the RSDP signature. First search EBDA (low * memory) paragraphs and then search upper memory (E0000-FFFFF). diff -urN linux-2.6.0-test4/arch/i386/kernel/efi.c linux-2.6.0-test4-efi/arch/i386/kernel/efi.c --- linux-2.6.0-test4/arch/i386/kernel/efi.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.0-test4-efi/arch/i386/kernel/efi.c 2003-08-28 18:04:04.000000000 -0700 @@ -0,0 +1,626 @@ +/* + * Extensible Firmware Interface + * + * Based on Extensible Firmware Interface Specification version 1.0 + * + * Copyright (C) 1999 VA Linux Systems + * Copyright (C) 1999 Walt Drummond + * Copyright (C) 1999-2002 Hewlett-Packard Co. + * David Mosberger-Tang + * Stephane Eranian + * + * All EFI Runtime Services are not implemented yet as EFI only + * supports physical mode addressing on SoftSDV. This is to be fixed + * in a future version. --drummond 1999-07-20 + * + * Implemented EFI runtime services and virtual mode calls. --davidm + * + * Goutham Rao: + * Skip non-WB memory and ignore empty memory ranges. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define EFI_DEBUG 0 +#define PFX "EFI: " + +extern efi_status_t asmlinkage efi_call_phys(void *, ...); + +struct efi efi; +struct efi efi_phys __initdata; +struct efi_memory_map memmap __initdata; + +static int efi_pte = 0; +static unsigned long efi_temp_page_table[1024] + __attribute__ ((aligned(4096))) __initdata ; +extern pgd_t swapper_pg_dir[1024]; + +/* + * efi_dir is allocated here, but the directory isn't created + * here, as proc_mkdir() doesn't work this early in the bootup + * process. Therefore, each module, like efivars, must test for + * if (!efi_dir) efi_dir = proc_mkdir("efi", NULL); + * prior to creating their own entries under /proc/efi. + */ +#ifdef CONFIG_PROC_FS +struct proc_dir_entry *efi_dir; +#endif + + +/* + * To make EFI call EFI runtime service in physical addressing mode we need + * prelog/epilog before/after the invocation to disable interrupt, to + * claim EFI runtime service handler exclusively and to duplicate a memory in + * low memory space say 0 - 3G. + */ + +static unsigned long efi_rt_eflags; +static spinlock_t efi_rt_lock = SPIN_LOCK_UNLOCKED; +static pgd_t efi_bak_pg_dir_pointer[2]; + +static void efi_call_phys_prelog(void) +{ + unsigned long cr4; + unsigned long temp; + + spin_lock(&efi_rt_lock); + local_irq_save(efi_rt_eflags); + + /* + * If I don't have PSE, I should just duplicate two entries in page + * directory. I I have PSE, I just need to duplicate one entry in + * page directory. + */ + __asm__ __volatile__("movl %%cr4, %0":"=r"(cr4)); + + if (cr4 & X86_CR4_PSE) { + efi_bak_pg_dir_pointer[0].pgd = + swapper_pg_dir[pgd_index(0)].pgd; + swapper_pg_dir[0].pgd = + swapper_pg_dir[pgd_index(PAGE_OFFSET)].pgd; + } else { + efi_bak_pg_dir_pointer[0].pgd = + swapper_pg_dir[pgd_index(0)].pgd; + efi_bak_pg_dir_pointer[1].pgd = + swapper_pg_dir[pgd_index(0x400000)].pgd; + swapper_pg_dir[pgd_index(0)].pgd = + swapper_pg_dir[pgd_index(PAGE_OFFSET)].pgd; + temp = PAGE_OFFSET + 0x400000; + swapper_pg_dir[pgd_index(0x400000)].pgd = + swapper_pg_dir[pgd_index(temp)].pgd; + } + + /* + * Only one processor can reach here. After the lock is + * released, the original page table is restored. + */ + local_flush_tlb(); + + cpu_gdt_descr[0].address = __pa(cpu_gdt_descr[0].address); + __asm__ __volatile__("lgdt %0":"=m" + (*(struct Xgt_desc_struct *) __pa(&cpu_gdt_descr[0]))); +} + +static void efi_call_phys_epilog(void) +{ + unsigned long cr4; + + cpu_gdt_descr[0].address = + (unsigned long) __va(cpu_gdt_descr[0].address); + __asm__ __volatile__("lgdt %0":"=m"(cpu_gdt_descr)); + __asm__ __volatile__("movl %%cr4, %0":"=r"(cr4)); + + if (cr4 & X86_CR4_PSE) { + swapper_pg_dir[pgd_index(0)].pgd = + efi_bak_pg_dir_pointer[0].pgd; + } else { + swapper_pg_dir[pgd_index(0)].pgd = + efi_bak_pg_dir_pointer[0].pgd; + swapper_pg_dir[pgd_index(0x400000)].pgd = + efi_bak_pg_dir_pointer[1].pgd; + } + + /* + * Because only one processor can reach here, after the lock is + * released the original page table is restored. + */ + local_flush_tlb(); + + local_irq_restore(efi_rt_eflags); + spin_unlock(&efi_rt_lock); +} + +static efi_status_t +phys_efi_set_virtual_address_map(unsigned long memory_map_size, + unsigned long descriptor_size, + u32 descriptor_version, + efi_memory_desc_t *virtual_map) +{ + efi_status_t status = EFI_NOT_FOUND; + + efi_call_phys_prelog(); + status = efi_call_phys(efi_phys.set_virtual_address_map, + memory_map_size, descriptor_size, + descriptor_version, virtual_map); + efi_call_phys_epilog(); + return status; +} + +efi_status_t +phys_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) +{ + efi_status_t status = EFI_NOT_FOUND; + + efi_call_phys_prelog(); + status = efi_call_phys(efi_phys.get_time, tm, tc); + efi_call_phys_epilog(); + return status; +} + +void efi_gettimeofday(struct timespec *tv) +{ + efi_time_t tm; + + memset(tv, 0, sizeof(tv)); + if ((*efi.get_time) (&tm, 0) != EFI_SUCCESS) + return; + + tv->tv_sec = mktime(tm.year, tm.month, tm.day, tm.hour, tm.minute, + tm.second); + tv->tv_nsec = tm.nanosecond; +} + +static int +is_available_memory(efi_memory_desc_t * md) +{ + if (!(md->attribute & EFI_MEMORY_WB)) + return 0; + + switch (md->type) { + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + case EFI_CONVENTIONAL_MEMORY: + return 1; + } + return 0; +} + +/* + * Walks the EFI memory map and calls CALLBACK once for each EFI + * memory descriptor that has memory that is available for kernel use. + */ +void efi_memmap_walk(efi_freemem_callback_t callback, void *arg) +{ + int prev_valid = 0; + struct range { + unsigned long start; + unsigned long end; + } prev, curr; + efi_memory_desc_t *md; + unsigned long start, end; + int i; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + + if (md->num_pages == 0) /* no pages means nothing to do... */ + continue; + if (is_available_memory(md)) { + curr.start = md->phys_addr; + curr.end = curr.start + + (md->num_pages << EFI_PAGE_SHIFT); + + if (!prev_valid) { + prev = curr; + prev_valid = 1; + } else { + if (curr.start < prev.start) + printk(PFX "Unordered memory map\n"); + if (prev.end == curr.start) + prev.end = curr.end; + else { + start = + (unsigned long) (PAGE_ALIGN(prev.start)); + end = (unsigned long) (prev.end & PAGE_MASK); + if ((end > start) + && (*callback) (start, end, arg) < 0) + return; + prev = curr; + } + } + } else + continue; + } + if (prev_valid) { + start = (unsigned long) PAGE_ALIGN(prev.start); + end = (unsigned long) (prev.end & PAGE_MASK); + if (end > start) + (*callback) (start, end, arg); + } +} + +/* + * mem_start is a physical address. + */ +unsigned long __init +efi_setup_temp_page_table(unsigned long mem_start, unsigned long size) +{ + unsigned long region_start_addr = (mem_start & 0xfffff000); + unsigned long region_end_addr = mem_start + size - 1; + unsigned long virt_start_addr = 0; + + if (region_start_addr > region_end_addr) + BUG(); + + virt_start_addr = (unsigned long) __va(MAXMEM) + + (efi_pte << EFI_PAGE_SHIFT) + + (mem_start & 0xfff); + + while (region_start_addr < region_end_addr) { + if (efi_pte == 1024) + printk(PFX "EFI Page Table is full!\n"); + + efi_temp_page_table[efi_pte] = (region_start_addr | 7); + + if (efi_pte == 0) + swapper_pg_dir[((unsigned long) ((unsigned long) + __va(MAXMEM) + (efi_pte << EFI_PAGE_SHIFT))) >> 22].pgd = + (unsigned long) (__pa(&(efi_temp_page_table[0])) | 7); + + region_start_addr += 0x1000; + efi_pte++; + } + local_flush_tlb(); + return virt_start_addr; +} + +void __init efi_init(void) +{ + efi_config_table_t *config_tables; + efi_char16_t *c16; + char vendor[100] = "unknown"; + int i = 0; + + /* + * Set up the page tables for EFI system table. + */ + memset(&efi, 0, sizeof(efi) ); + memset(&efi_phys, 0, sizeof(efi_phys)); + + efi_phys.systab = EFI_SYSTAB; + memmap.phys_map = EFI_MEMMAP; + memmap.nr_map= EFI_MEMMAP_SIZE/EFI_MEMDESC_SIZE; + + efi.systab = + (efi_system_table_t *) + efi_setup_temp_page_table((unsigned long) efi_phys.systab, + sizeof(efi_system_table_t)); + /* + * Verify the EFI Table + */ + if (efi.systab == NULL) + printk(PFX "Woah! Can't find EFI system table.\n"); + if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) + printk(PFX "Woah! EFI system table signature incorrect\n"); + if ((efi.systab->hdr.revision ^ EFI_SYSTEM_TABLE_REVISION) >> 16 != 0) + printk(PFX + "Warning: EFI system table major version mismatch: " + "got %d.%02d, expected %d.%02d\n", + efi.systab->hdr.revision >> 16, + efi.systab->hdr.revision & 0xffff, + EFI_SYSTEM_TABLE_REVISION >> 16, + EFI_SYSTEM_TABLE_REVISION & 0xffff); + + /* Show what we know for posterity */ + c16 = (efi_char16_t *) efi_setup_temp_page_table(efi.systab->fw_vendor, 2); + if (c16) { + /* + * Set up the page tables for fw_vendor. + */ + for (i = 0; i < sizeof(vendor) && *c16; ++i) { + vendor[i] = *c16++; + /* + * If I cross the boundary of a page, then map more. + */ + if ((((unsigned long) c16) & 0xfff) == 0) + c16 = + (efi_char16_t *) efi_setup_temp_page_table( + ((unsigned long) (efi.systab->fw_vendor)) + i, 4096); + } + vendor[i] = '\0'; + } + + printk(PFX "EFI v%u.%.02u by %s \n", + efi.systab->hdr.revision >> 16, + efi.systab->hdr.revision & 0xffff, vendor); + + /* + * Set up the page tables for config_tables. + */ + config_tables = (efi_config_table_t *) + efi_setup_temp_page_table(efi.systab->tables, + efi.systab->nr_tables * sizeof (efi_config_table_t)); + + for (i = 0; i < efi.systab->nr_tables; i++) { + if (efi_guidcmp(config_tables[i].guid, MPS_TABLE_GUID) == 0) { + efi.mps = (void *)config_tables[i].table; + printk(" MPS=0x%lx ", config_tables[i].table); + } else + if (efi_guidcmp(config_tables[i].guid, ACPI_20_TABLE_GUID) == 0) { + efi.acpi20 = __va(config_tables[i].table); + printk(" ACPI 2.0=0x%lx ", config_tables[i].table); + } else + if (efi_guidcmp(config_tables[i].guid, ACPI_TABLE_GUID) == 0) { + efi.acpi = __va(config_tables[i].table); + printk(" ACPI=0x%lx ", config_tables[i].table); + } else + if (efi_guidcmp(config_tables[i].guid, SMBIOS_TABLE_GUID) == 0) { + efi.smbios = (void *) config_tables[i].table; + printk(" SMBIOS=0x%lx ", config_tables[i].table); + } else + if (efi_guidcmp(config_tables[i].guid, HCDP_TABLE_GUID) == 0) { + efi.hcdp = (void *)config_tables[i].table; + printk(" HCDP=0x%lx ", config_tables[i].table); + } else + if (efi_guidcmp(config_tables[i].guid, UGA_IO_PROTOCOL_GUID) == 0) { + efi.uga = (void *)config_tables[i].table; + printk(" UGA=0x%lx ", config_tables[i].table); + } + } + printk("\n"); + + /* + * Set up the page tables for runtime services. We need to map + * the runtime services table so that we can grab the physical + * address of the EFI runtime functions. + */ + + efi.systab->runtime = + (efi_runtime_services_t *) efi_setup_temp_page_table( + (unsigned long) efi.systab->runtime, + sizeof(efi_runtime_services_t)); + + /* + * We will only need *early* access to the following two EFI RT + * services before set_virtual_address_map is invoked. + */ + efi_phys.get_time = (efi_get_time_t *) efi.systab->runtime->get_time; + efi_phys.set_virtual_address_map = + (efi_set_virtual_address_map_t *) efi.systab->runtime->set_virtual_address_map; + + memmap.map = (efi_memory_desc_t *) + efi_setup_temp_page_table((unsigned long) EFI_MEMMAP, + EFI_MEMMAP_SIZE); + if (EFI_MEMDESC_SIZE != sizeof(efi_memory_desc_t)) { + printk(PFX "Warning! Kernel-defined memdesc doesn't " + "match the one from EFI!\n"); + } +} + +void __init efi_enter_virtual_mode(void) +{ + int i; + efi_memory_desc_t *md; + efi_status_t status; + + memmap.map = ioremap((unsigned long) memmap.phys_map, EFI_MEMMAP_SIZE); + + if (!memmap.map) + printk(PFX "ioremap of memmap.map failed \n"); + /* + * start to set up the permanent virtual mapping. + */ + efi.systab = NULL; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + + if (md->attribute & EFI_MEMORY_RUNTIME) { + md->virt_addr = + (u64) ioremap((unsigned long) md->phys_addr, + (unsigned long) (md->num_pages + << EFI_PAGE_SHIFT)); + if (!(unsigned long) md->virt_addr) { + printk(PFX "ioremap of md: 0x%lX failed \n", + (unsigned long) md->phys_addr); + } + + if (((unsigned long)md->phys_addr <= (unsigned long)efi_phys.systab) && ((unsigned long)efi_phys.systab < md->phys_addr + ((unsigned long) md->num_pages << EFI_PAGE_SHIFT))) { + efi.systab = (efi_system_table_t *) + ((md->virt_addr - md->phys_addr) + + (u64)efi_phys.systab); + } + } + } + + if (!efi.systab) + BUG(); + + status = 0; + status = phys_efi_set_virtual_address_map( + EFI_MEMMAP_SIZE, + EFI_MEMDESC_SIZE, + EFI_MEMDESC_VERSION, + memmap.phys_map); + + if (status != EFI_SUCCESS) { + printk ("You are screwed! " + "Unable to switch EFI into virtual mode " + "(status=%lu)\n", status); + panic("EFI call SetVirtualAddressMap() failed!"); + } + + /* + * Now that EFI is in virtual mode, update the function + * pointers in the runtime service table to the new virtual addresses + * so they may be called directly: + */ + + efi.get_time = (efi_get_time_t *) efi.systab->runtime->get_time; + efi.set_time = (efi_set_time_t *) efi.systab->runtime->set_time; + efi.get_wakeup_time = (efi_get_wakeup_time_t *) + efi.systab->runtime->get_wakeup_time; + efi.set_wakeup_time = (efi_set_wakeup_time_t *) + efi.systab->runtime->set_wakeup_time; + efi.get_variable = (efi_get_variable_t *) + efi.systab->runtime->get_variable; + efi.get_next_variable = (efi_get_next_variable_t *) + efi.systab->runtime->get_next_variable; + efi.set_variable = (efi_set_variable_t *) + efi.systab->runtime->set_variable; + efi.get_next_high_mono_count = (efi_get_next_high_mono_count_t *) + efi.systab->runtime->get_next_high_mono_count; + efi.reset_system = (efi_reset_system_t *) + efi.systab->runtime->reset_system; + +} + +void __init +efi_initialize_iomem_resources(struct resource *code_resource, + struct resource *data_resource) +{ + struct resource *res; + efi_memory_desc_t *md; + int i; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + + if ((md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT)) > + 0x100000000ULL) + continue; + res = alloc_bootmem_low(sizeof(struct resource)); + switch (md->type) { + case EFI_RESERVED_TYPE: + res->name = "Reserved Memory"; + break; + case EFI_LOADER_CODE: + res->name = "Loader Code"; + break; + case EFI_LOADER_DATA: + res->name = "Loader Data"; + break; + case EFI_BOOT_SERVICES_DATA: + res->name = "BootServices Data"; + break; + case EFI_BOOT_SERVICES_CODE: + res->name = "BootServices Code"; + break; + case EFI_RUNTIME_SERVICES_CODE: + res->name = "Runtime Service Code"; + break; + case EFI_RUNTIME_SERVICES_DATA: + res->name = "Runtime Service Data"; + break; + case EFI_CONVENTIONAL_MEMORY: + res->name = "Conventional Memory"; + break; + case EFI_UNUSABLE_MEMORY: + res->name = "Unusable Memory"; + break; + case EFI_ACPI_RECLAIM_MEMORY: + res->name = "ACPI Reclaim"; + break; + case EFI_ACPI_MEMORY_NVS: + res->name = "ACPI NVS"; + break; + case EFI_MEMORY_MAPPED_IO: + res->name = "Memory Mapped IO"; + break; + case EFI_MEMORY_MAPPED_IO_PORT_SPACE: + res->name = "Memory Mapped IO Port Space"; + break; + default: + res->name = "Reserved"; + break; + } + res->start = md->phys_addr; + res->end = res->start + ((md->num_pages << EFI_PAGE_SHIFT) - 1); + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&iomem_resource, res)) + printk(PFX "Failed to allocate res %s : 0x%lx-0x%lx\n", + res->name, res->start, res->end); + + if (md->type == EFI_CONVENTIONAL_MEMORY) { + request_resource(res, code_resource); + request_resource(res, data_resource); + } + } +} + +/* + * Reserve certain EFI related regions with the bootmem + * allocator - particularly the memmap. + */ +void __init efi_reserve_bootmem(void) +{ + reserve_bootmem((unsigned long)memmap.phys_map, + (memmap.nr_map * sizeof(efi_memory_desc_t))); +} + +/* + * Convenience functions to obtain memory types and attributes + */ + +u32 efi_mem_type(unsigned long phys_addr) +{ + efi_memory_desc_t *md; + int i; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + if ((md->phys_addr <= phys_addr) && (phys_addr < + (md->phys_addr + (md-> num_pages << EFI_PAGE_SHIFT)) )) + return md->type; + } + return 0; +} + +u64 efi_mem_attributes(unsigned long phys_addr) +{ + efi_memory_desc_t *md; + int i; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + if ((md->phys_addr <= phys_addr) && (phys_addr < + (md->phys_addr + (md-> num_pages << EFI_PAGE_SHIFT)) )) + return md->attribute; + } + return 0; +} + +void print_efi_memmap(void) +{ + efi_memory_desc_t *md; + int i; + + for (i = 0; i < memmap.nr_map; i++) { + md = &memmap.map[i]; + printk("mem%02u: type=%u, attr=0x%llx, range=[0x%016llx-0x%016llx) (%lluMB)\n", + i, md->type, md->attribute, md->phys_addr, + md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT), + (md->num_pages >> (20 - EFI_PAGE_SHIFT))); + } +} + diff -urN linux-2.6.0-test4/arch/i386/kernel/efi_stub.S linux-2.6.0-test4-efi/arch/i386/kernel/efi_stub.S --- linux-2.6.0-test4/arch/i386/kernel/efi_stub.S 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.0-test4-efi/arch/i386/kernel/efi_stub.S 2003-08-28 16:05:49.000000000 -0700 @@ -0,0 +1,125 @@ +/* + * EFI call stub for IA32. + * + * This stub allows us to make EFI calls in physical mode with interrupts + * turned off. + */ + +#include +#include +#include +#include + +/* + * efi_call_phys(void *, ...) is a function with variable parameters. + * All the callers of this function assure that all the parameters are 4-bytes. + */ + +/* + * In gcc calling convention, EBX, ESP, EBP, ESI and EDI are all callee save. + * So we'd better save all of them at the beginning of this function and restore + * at the end no matter how many we use, because we can not assure EFI runtime + * service functions will comply with gcc calling convention, too. + */ + +.text +.section .text, "a" +ENTRY(efi_call_phys) + /* + * 0. The function can only be called in Linux kernel. So CS has been + * set to 0x0010, DS and SS have been set to 0x0018. In EFI, I found + * the values of these registers are the same. And, the corresponding + * GDT entries are identical. So I will do nothing about segment reg + * and GDT, but change GDT base register in prelog and epilog. + */ + + /* + * 1. Now I am running with EIP = + PAGE_OFFSET. + * But to make it smoothly switch from virtual mode to flat mode. + * The mapping of lower virtual memory has been created in prelog and + * epilog. + */ + movl $1f, %edx + subl $__PAGE_OFFSET, %edx + jmp *%edx +1: + + /* + * 2. Now on the top of stack is the return + * address in the caller of efi_call_phys(), then parameter 1, + * parameter 2, ..., param n. To make things easy, we save the return + * address of efi_call_phys in a global variable. + */ + popl %edx + movl %edx, saved_return_addr + /* get the function pointer into ECX*/ + popl %ecx + movl %ecx, efi_rt_function_ptr + movl $2f, %edx + subl $__PAGE_OFFSET, %edx + pushl %edx + + /* + * 3. Clear PG bit in %CR0. + */ + movl %cr0, %edx + andl $0x7fffffff, %edx + movl %edx, %cr0 + jmp 1f +1: + + /* + * 4. Adjust stack pointer. + */ + subl $__PAGE_OFFSET, %esp + + /* + * 5. Call the physical function. + */ + jmp *%ecx + +2: + /* + * 6. After EFI runtime service returns, control will return to + * following instruction. We'd better readjust stack pointer first. + */ + addl $__PAGE_OFFSET, %esp + + /* + * 7. Restore PG bit + */ + movl %cr0, %edx + orl $0x80000000, %edx + movl %edx, %cr0 + jmp 1f +1: + /* + * 8. Now restore the virtual mode from flat mode by + * adding EIP with PAGE_OFFSET. + */ + movl $1f, %edx + jmp *%edx +1: + + /* + * 9. Balance the stack. And because EAX contain the return value, + * we'd better not clobber it. + */ + leal efi_rt_function_ptr, %edx + movl (%edx), %ecx + pushl %ecx + + /* + * 10. Push the saved return address onto the stack and return. + */ + leal saved_return_addr, %edx + movl (%edx), %ecx + pushl %ecx + ret +.previous + +.data +saved_return_addr: + .long 0 +efi_rt_function_ptr: + .long 0 diff -urN linux-2.6.0-test4/arch/i386/kernel/Makefile linux-2.6.0-test4-efi/arch/i386/kernel/Makefile --- linux-2.6.0-test4/arch/i386/kernel/Makefile 2003-08-22 16:52:57.000000000 -0700 +++ linux-2.6.0-test4-efi/arch/i386/kernel/Makefile 2003-08-28 16:05:49.000000000 -0700 @@ -7,7 +7,7 @@ obj-y := process.o semaphore.o signal.o entry.o traps.o irq.o vm86.o \ ptrace.o i8259.o ioport.o ldt.o setup.o time.o sys_i386.o \ pci-dma.o i386_ksyms.o i387.o dmi_scan.o bootflag.o \ - doublefault.o + doublefault.o efi.o efi_stub.o obj-y += cpu/ obj-y += timers/ diff -urN linux-2.6.0-test4/arch/i386/kernel/reboot.c linux-2.6.0-test4-efi/arch/i386/kernel/reboot.c --- linux-2.6.0-test4/arch/i386/kernel/reboot.c 2003-08-22 16:53:47.000000000 -0700 +++ linux-2.6.0-test4-efi/arch/i386/kernel/reboot.c 2003-08-28 16:05:49.000000000 -0700 @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include "mach_reboot.h" @@ -262,7 +263,12 @@ disable_IO_APIC(); #endif - if(!reboot_thru_bios) { + if (!reboot_thru_bios) { + if (efi_enabled) { + efi.reset_system(EFI_RESET_COLD, EFI_SUCCESS, 0, 0); + __asm__ __volatile__("lidt %0": :"m" (no_idt)); + __asm__ __volatile__("int3"); + } /* rebooting needs to touch the page at absolute addr 0 */ *((unsigned short *)__va(0x472)) = reboot_mode; for (;;) { @@ -272,6 +278,8 @@ __asm__ __volatile__("int3"); } } + if (efi_enabled) + efi.reset_system(EFI_RESET_WARM, EFI_SUCCESS, 0, 0); machine_real_restart(jump_to_bios, sizeof(jump_to_bios)); } @@ -282,6 +290,8 @@ void machine_power_off(void) { + if (efi_enabled) + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, 0); if (pm_power_off) pm_power_off(); } diff -urN linux-2.6.0-test4/arch/i386/kernel/setup.c linux-2.6.0-test4-efi/arch/i386/kernel/setup.c --- linux-2.6.0-test4/arch/i386/kernel/setup.c 2003-08-22 16:55:38.000000000 -0700 +++ linux-2.6.0-test4-efi/arch/i386/kernel/setup.c 2003-08-28 16:47:40.000000000 -0700 @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include