Skip to content
Snippets Groups Projects
efi_stub.c 8.85 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Copyright (c) 2015 Google, Inc
     *
     * SPDX-License-Identifier:	GPL-2.0+
     *
     * EFI information obtained here:
     * http://wiki.phoenix.com/wiki/index.php/EFI_BOOT_SERVICES
     *
    
     * Loads a payload (U-Boot) within the EFI environment. This is built as an
     * EFI application. It can be built either in 32-bit or 64-bit mode.
    
     */
    
    #include <common.h>
    #include <debug_uart.h>
    #include <efi.h>
    #include <efi_api.h>
    #include <errno.h>
    #include <ns16550.h>
    #include <asm/cpu.h>
    #include <asm/io.h>
    #include <linux/err.h>
    #include <linux/types.h>
    
    DECLARE_GLOBAL_DATA_PTR;
    
    #ifndef CONFIG_X86
    /*
     * Problem areas:
     * - putc() uses the ns16550 address directly and assumed I/O access. Many
     *	platforms will use memory access
     * get_codeseg32() is only meaningful on x86
     */
    #error "This file needs to be ported for use on architectures"
    #endif
    
    static struct efi_priv *global_priv;
    static bool use_uart;
    
    struct __packed desctab_info {
    	uint16_t limit;
    	uint64_t addr;
    	uint16_t pad;
    };
    
    /*
     * EFI uses Unicode and we don't. The easiest way to get a sensible output
     * function is to use the U-Boot debug UART. We use EFI's console output
     * function where available, and assume the built-in UART after that. We rely
     * on EFI to set up the UART for us and just bring in the functions here.
     * This last bit is a bit icky, but it's only for debugging anyway. We could
     * build in ns16550.c with some effort, but this is a payload loader after
     * all.
     *
     * Note: We avoid using printf() so we don't need to bring in lib/vsprintf.c.
     * That would require some refactoring since we already build this for U-Boot.
     * Building an EFI shared library version would have to be a separate stem.
     * That might push us to using the SPL framework to build this stub. However
     * that would involve a round of EFI-specific changes in SPL. Worth
     * considering if we start needing more U-Boot functionality. Note that we
     * could then move get_codeseg32() to arch/x86/cpu/cpu.c.
     */
    
    void _debug_uart_init(void)
    
    	if (use_uart) {
    		NS16550_t com_port = (NS16550_t)0x3f8;
    
    		while ((inb((ulong)&com_port->lsr) & UART_LSR_THRE) == 0)
    			;
    		outb(ch, (ulong)&com_port->thr);
    	} else {
    		efi_putc(global_priv, ch);
    	}
    }
    
    void puts(const char *str)
    {
    	while (*str)
    		putc(*str++);
    }
    
    static void _debug_uart_putc(int ch)
    {
    	putc(ch);
    }
    
    DEBUG_UART_FUNCS
    
    void *memcpy(void *dest, const void *src, size_t size)
    {
    	unsigned char *dptr = dest;
    	const unsigned char *ptr = src;
    	const unsigned char *end = src + size;
    
    	while (ptr < end)
    		*dptr++ = *ptr++;
    
    	return dest;
    }
    
    void *memset(void *inptr, int ch, size_t size)
    {
    	char *ptr = inptr;
    	char *end = ptr + size;
    
    	while (ptr < end)
    		*ptr++ = ch;
    
    	return ptr;
    }
    
    static void jump_to_uboot(ulong cs32, ulong addr, ulong info)
    {
    #ifdef CONFIG_EFI_STUB_32BIT
    	/*
    	 * U-Boot requires these parameters in registers, not on the stack.
    	 * See _x86boot_start() for this code.
    	 */
    	typedef void (*func_t)(int bist, int unused, ulong info)
    		__attribute__((regparm(3)));
    
    	((func_t)addr)(0, 0, info);
    #else
    
    	cpu_call32(cs32, CONFIG_SYS_TEXT_BASE, info);
    
    #ifdef CONFIG_EFI_STUB_64BIT
    
    static void get_gdt(struct desctab_info *info)
    {
    	asm volatile ("sgdt %0" : : "m"(*info) : "memory");
    }
    
    
    static inline unsigned long read_cr3(void)
    {
    	unsigned long val;
    
    	asm volatile("mov %%cr3,%0" : "=r" (val) : : "memory");
    	return val;
    }
    
    /**
     * get_codeseg32() - Find the code segment to use for 32-bit code
     *
     * U-Boot only works in 32-bit mode at present, so when booting from 64-bit
     * EFI we must first change to 32-bit mode. To do this we need to find the
     * correct code segment to use (an entry in the Global Descriptor Table).
     *
     * @return code segment GDT offset, or 0 for 32-bit EFI, -ENOENT if not found
     */
    static int get_codeseg32(void)
    {
    	int cs32 = 0;
    
    
    #ifdef CONFIG_EFI_STUB_64BIT
    	struct desctab_info gdt;
    	uint64_t *ptr;
    	int i;
    
    	get_gdt(&gdt);
    	for (ptr = (uint64_t *)(unsigned long)gdt.addr, i = 0; i < gdt.limit;
    	     i += 8, ptr++) {
    		uint64_t desc = *ptr;
    		uint64_t base, limit;
    
    		/*
    		 * Check that the target U-Boot jump address is within the
    		 * selector and that the selector is of the right type.
    		 */
    		base = ((desc >> GDT_BASE_LOW_SHIFT) & GDT_BASE_LOW_MASK) |
    			((desc >> GDT_BASE_HIGH_SHIFT) & GDT_BASE_HIGH_MASK)
    				<< 16;
    		limit = ((desc >> GDT_LIMIT_LOW_SHIFT) & GDT_LIMIT_LOW_MASK) |
    			((desc >> GDT_LIMIT_HIGH_SHIFT) & GDT_LIMIT_HIGH_MASK)
    				<< 16;
    		base <<= 12;	/* 4KB granularity */
    		limit <<= 12;
    		if ((desc & GDT_PRESENT) && (desc && GDT_NOTSYS) &&
    		    !(desc & GDT_LONG) && (desc & GDT_4KB) &&
    		    (desc & GDT_32BIT) && (desc & GDT_CODE) &&
    		    CONFIG_SYS_TEXT_BASE > base &&
    		    CONFIG_SYS_TEXT_BASE + CONFIG_SYS_MONITOR_LEN < limit
    		) {
    			cs32 = i;
    			break;
    		}
    	}
    
    #ifdef DEBUG
    	puts("\ngdt: ");
    	printhex8(gdt.limit);
    	puts(", addr: ");
    	printhex8(gdt.addr >> 32);
    	printhex8(gdt.addr);
    	for (i = 0; i < gdt.limit; i += 8) {
    		uint32_t *ptr = (uint32_t *)((unsigned long)gdt.addr + i);
    
    		puts("\n");
    		printhex2(i);
    		puts(": ");
    		printhex8(ptr[1]);
    		puts("  ");
    		printhex8(ptr[0]);
    	}
    	puts("\n ");
    	puts("32-bit code segment: ");
    	printhex2(cs32);
    	puts("\n ");
    
    	puts("page_table: ");
    	printhex8(read_cr3());
    	puts("\n ");
    #endif
    	if (!cs32) {
    		puts("Can't find 32-bit code segment\n");
    		return -ENOENT;
    	}
    #endif
    
    
    	return cs32;
    }
    
    static int setup_info_table(struct efi_priv *priv, int size)
    {
    	struct efi_info_hdr *info;
    	efi_status_t ret;
    
    	/* Get some memory for our info table */
    	priv->info_size = size;
    	info = efi_malloc(priv, priv->info_size, &ret);
    	if (ret) {
    		printhex2(ret);
    		puts(" No memory for info table: ");
    		return ret;
    	}
    
    	memset(info, '\0', sizeof(*info));
    	info->version = EFI_TABLE_VERSION;
    	info->hdr_size = sizeof(*info);
    	priv->info = info;
    	priv->next_hdr = (char *)info + info->hdr_size;
    
    	return 0;
    }
    
    static void add_entry_addr(struct efi_priv *priv, enum efi_entry_t type,
    			   void *ptr1, int size1, void *ptr2, int size2)
    {
    	struct efi_entry_hdr *hdr = priv->next_hdr;
    
    	hdr->type = type;
    	hdr->size = size1 + size2;
    	hdr->addr = 0;
    	hdr->link = ALIGN(sizeof(*hdr) + hdr->size, 16);
    	priv->next_hdr += hdr->link;
    	memcpy(hdr + 1, ptr1, size1);
    	memcpy((void *)(hdr + 1) + size1, ptr2, size2);
    	priv->info->total_size = (ulong)priv->next_hdr - (ulong)priv->info;
    }
    
    /**
     * efi_main() - Start an EFI image
     *
     * This function is called by our EFI start-up code. It handles running
     * U-Boot. If it returns, EFI will continue.
     */
    efi_status_t efi_main(efi_handle_t image, struct efi_system_table *sys_table)
    {
    	struct efi_priv local_priv, *priv = &local_priv;
    	struct efi_boot_services *boot = sys_table->boottime;
    	struct efi_mem_desc *desc;
    	struct efi_entry_memmap map;
    	ulong key, desc_size, size;
    	efi_status_t ret;
    	u32 version;
    	int cs32;
    
    	ret = efi_init(priv, "Payload", image, sys_table);
    	if (ret) {
    		printhex2(ret); puts(" efi_init() failed\n");
    		return ret;
    	}
    	global_priv = priv;
    
    	cs32 = get_codeseg32();
    	if (cs32 < 0)
    		return EFI_UNSUPPORTED;
    
    	/* Get the memory map so we can switch off EFI */
    	size = 0;
    	ret = boot->get_memory_map(&size, NULL, &key, &desc_size, &version);
    	if (ret != EFI_BUFFER_TOO_SMALL) {
    		printhex2(BITS_PER_LONG);
    		printhex2(ret);
    		puts(" No memory map\n");
    		return ret;
    	}
    	size += 1024;	/* Since doing a malloc() may change the memory map! */
    	desc = efi_malloc(priv, size, &ret);
    	if (!desc) {
    		printhex2(ret);
    		puts(" No memory for memory descriptor: ");
    		return ret;
    	}
    	ret = setup_info_table(priv, size + 128);
    	if (ret)
    		return ret;
    
    	ret = boot->get_memory_map(&size, desc, &key, &desc_size, &version);
    	if (ret) {
    		printhex2(ret);
    		puts(" Can't get memory map\n");
    		return ret;
    	}
    
    	ret = boot->exit_boot_services(image, key);
    	if (ret) {
    		/*
    		 * Unfortunately it happens that we cannot exit boot services
    		 * the first time. But the second time it work. I don't know
    		 * why but this seems to be a repeatable problem. To get
    		 * around it, just try again.
    		 */
    		printhex2(ret);
    		puts(" Can't exit boot services\n");
    		size = sizeof(desc);
    		ret = boot->get_memory_map(&size, desc, &key, &desc_size,
    					   &version);
    		if (ret) {
    			printhex2(ret);
    			puts(" Can't get memory map\n");
    			return ret;
    		}
    		ret = boot->exit_boot_services(image, key);
    		if (ret) {
    			printhex2(ret);
    			puts(" Can't exit boot services 2\n");
    			return ret;
    		}
    	}
    
    	map.version = version;
    	map.desc_size = desc_size;
    	add_entry_addr(priv, EFIET_MEMORY_MAP, &map, sizeof(map), desc, size);
    	add_entry_addr(priv, EFIET_END, NULL, 0, 0, 0);
    
    	/* The EFI UART won't work now, switch to a debug one */
    	use_uart = true;
    
    
    	memcpy((void *)CONFIG_SYS_TEXT_BASE, _binary_u_boot_bin_start,
    	       (ulong)_binary_u_boot_bin_end -
    	       (ulong)_binary_u_boot_bin_start);
    
    
    #ifdef DEBUG
    	puts("EFI table at ");
    	printhex8((ulong)priv->info);
    	puts(" size ");
    	printhex8(priv->info->total_size);
    #endif
    	putc('\n');
    	jump_to_uboot(cs32, CONFIG_SYS_TEXT_BASE, (ulong)priv->info);
    
    	return EFI_LOAD_ERROR;
    }