diff --git a/stand/arm64/libarm64/cache.c b/stand/arm64/libarm64/cache.c index 105e08cb4257..04fae36e0fc9 100644 --- a/stand/arm64/libarm64/cache.c +++ b/stand/arm64/libarm64/cache.c @@ -1,152 +1,151 @@ /*- * Copyright (c) 2014 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "bootstrap.h" #include "cache.h" static long cache_flags; #define CACHE_FLAG_DIC_OFF (1<<0) #define CACHE_FLAG_IDC_OFF (1<<1) static bool get_cache_dic(uint64_t ctr) { if ((cache_flags & CACHE_FLAG_DIC_OFF) != 0) { return (false); } return (CTR_DIC_VAL(ctr) != 0); } static bool get_cache_idc(uint64_t ctr) { if ((cache_flags & CACHE_FLAG_IDC_OFF) != 0) { return (false); } return (CTR_IDC_VAL(ctr) != 0); } static unsigned int get_dcache_line_size(uint64_t ctr) { unsigned int dcl_size; /* * Relevant field [19:16] is LOG2 * of the number of words in DCache line */ dcl_size = CTR_DLINE_SIZE(ctr); /* Size of word shifted by cache line size */ return (sizeof(int) << dcl_size); } void cpu_flush_dcache(const void *ptr, size_t len) { uint64_t cl_size, ctr; vm_offset_t addr, end; /* Accessible from all security levels */ ctr = READ_SPECIALREG(ctr_el0); if (get_cache_idc(ctr)) { dsb(ishst); } else { cl_size = get_dcache_line_size(ctr); /* Calculate end address to clean */ end = (vm_offset_t)ptr + (vm_offset_t)len; /* Align start address to cache line */ addr = (vm_offset_t)ptr; addr = rounddown2(addr, cl_size); for (; addr < end; addr += cl_size) __asm __volatile("dc civac, %0" : : "r" (addr) : "memory"); /* Full system DSB */ dsb(ish); } } void cpu_inval_icache(void) { uint64_t ctr; /* Accessible from all security levels */ ctr = READ_SPECIALREG(ctr_el0); if (get_cache_dic(ctr)) { isb(); } else { __asm __volatile( "ic ialluis \n" "dsb ish \n" "isb \n" : : : "memory"); } } static int command_cache_flags(int argc, char *argv[]) { char *cp; long new_flags; if (argc == 3) { if (strcmp(argv[1], "set") == 0) { new_flags = strtol(argv[2], &cp, 0); if (cp[0] != '\0') { printf("Invalid flags\n"); } else { printf("Setting cache flags to %#lx\n", new_flags); cache_flags = new_flags; return (CMD_OK); } } } printf("usage: cache_flags set \n"); return (CMD_ERROR); } COMMAND_SET(cache_flags, "cache_flags", "Set cache flags", command_cache_flags); diff --git a/stand/arm64/libarm64/cache.h b/stand/arm64/libarm64/cache.h index 5e560c4d578d..9778a28837cb 100644 --- a/stand/arm64/libarm64/cache.h +++ b/stand/arm64/libarm64/cache.h @@ -1,38 +1,37 @@ /*- * Copyright (c) 2014 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _CACHE_H_ #define _CACHE_H_ /* cache.c */ void cpu_flush_dcache(const void *, size_t); void cpu_inval_icache(void); #endif /* _CACHE_H_ */ diff --git a/stand/efi/fdt/efi_fdt.c b/stand/efi/fdt/efi_fdt.c index cf3cbabee722..21107bb2ccbf 100644 --- a/stand/efi/fdt/efi_fdt.c +++ b/stand/efi/fdt/efi_fdt.c @@ -1,70 +1,69 @@ /*- * Copyright (c) 2014 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Andrew Turner under * sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "bootstrap.h" static EFI_GUID fdtdtb = FDT_TABLE_GUID; int fdt_platform_load_dtb(void) { struct fdt_header *hdr; hdr = efi_get_table(&fdtdtb); if (hdr == NULL) return (1); if (fdt_load_dtb_addr(hdr) != 0) return (1); printf("Using DTB provided by EFI at %p.\n", hdr); return (0); } void fdt_platform_load_overlays(void) { fdt_load_dtb_overlays(NULL); } void fdt_platform_fixups(void) { fdt_apply_overlays(); } diff --git a/stand/efi/loader/arch/amd64/amd64_tramp.S b/stand/efi/loader/arch/amd64/amd64_tramp.S index 877705407f92..7e15eeb4a035 100644 --- a/stand/efi/loader/arch/amd64/amd64_tramp.S +++ b/stand/efi/loader/arch/amd64/amd64_tramp.S @@ -1,76 +1,75 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #define ASM_FILE #include "multiboot2.h" .text .globl amd64_tramp /* * void amd64_tramp(uint64_t stack, void *copy_finish, uint64_t kernend, * uint64_t modulep, uint64_t pagetable, uint64_t entry) */ amd64_tramp: cli /* Make sure we don't get interrupted. */ movq %rdi,%rsp /* Switch to our temporary stack. */ movq %rdx,%r12 /* Stash the kernel values for later. */ movq %rcx,%r13 movq %r8,%r14 movq %r9,%r15 callq *%rsi /* Call copy_finish so we're all ready to go. */ pushq %r12 /* Push kernend. */ salq $32,%r13 /* Shift modulep and push it. */ pushq %r13 pushq %r15 /* Push the entry address. */ movq %r14,%cr3 /* Switch page tables. */ ret /* "Return" to kernel entry. */ ALIGN_TEXT amd64_tramp_end: /* void multiboot2_exec(uint64_t entry, uint64_t multiboot_info, uint64_t stack) */ .globl multiboot2_exec multiboot2_exec: movq %rdx,%rsp pushq %rdi movq %rsi,%rbx movq $MULTIBOOT2_BOOTLOADER_MAGIC,%rax ret .data .globl amd64_tramp_size amd64_tramp_size: .long amd64_tramp_end-amd64_tramp diff --git a/stand/efi/loader/arch/amd64/exc.S b/stand/efi/loader/arch/amd64/exc.S index 0035d4a37e20..19981e6e7152 100644 --- a/stand/efi/loader/arch/amd64/exc.S +++ b/stand/efi/loader/arch/amd64/exc.S @@ -1,165 +1,164 @@ /*- * Copyright (c) 2016 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Konstantin Belousov under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ .macro EH N, err=1 .align 8 .globl EXC\N\()_handler EXC\N\()_handler: .if \err != 1 pushq $0 .endif pushq %rax pushq %rdx pushq %rcx movl $\N,%ecx jmp all_handlers .endm .text EH 0,0 EH 1,0 EH 2,0 EH 3,0 EH 4,0 EH 5,0 EH 6,0 EH 7,0 EH 8 EH 9,0 EH 10 EH 11 EH 12 EH 13 EH 14 EH 16,0 EH 17 EH 18,0 EH 19,0 EH 20,0 .globl exc_rsp all_handlers: cmpq %rsp,exc_rsp(%rip) je exception /* * Interrupt, not exception. * First, copy the hardware interrupt frame to the previous stack. * Our handler always has private IST stack. */ movq (6*8)(%rsp),%rax /* saved %rsp value, AKA old stack */ subq (5*8),%rax movq (3*8)(%rsp),%rdx /* copy %rip to old stack */ movq %rdx,(%rax) movq (4*8)(%rsp),%rdx /* copy %cs */ movq %rdx,(1*8)(%rax) movq (5*8)(%rsp),%rdx /* copy %rflags */ movq %rdx,(2*8)(%rax) movq (6*8)(%rsp),%rdx /* copy %rsp */ movq %rdx,(3*8)(%rax) movq (7*8)(%rsp),%rdx /* copy %ss */ movq %rdx,(4*8)(%rax) /* * Now simulate invocation of the original interrupt handler * with retq. We switch stacks and execute retq from the old * stack since there is no free registers at the last moment. */ subq $16,%rax leaq fw_intr_handlers(%rip),%rdx movq (%rdx,%rcx,8),%rdx /* push intr handler address on old stack */ movq %rdx,8(%rax) movq (2*8)(%rsp),%rcx /* saved %rax is put on top of old stack */ movq %rcx,(%rax) movq (%rsp),%rcx movq 8(%rsp),%rdx movq 32(%rsp),%rsp /* switch to old stack */ popq %rax retq exception: /* * Form the struct trapframe on our IST stack. * Skip three words, which are currently busy with temporal * saves. */ pushq %r15 pushq %r14 pushq %r13 pushq %r12 pushq %r11 pushq %r10 pushq %rbp pushq %rbx pushq $0 /* %rax */ pushq %r9 pushq %r8 pushq $0 /* %rcx */ pushq $0 /* %rdx */ pushq %rsi pushq %rdi /* * Move %rax, %rdx, %rcx values into the final location, * from the three words which were skipped above. */ movq 0x88(%rsp),%rax movq %rax,0x30(%rsp) /* tf_rax */ movq 0x78(%rsp),%rax movq %rax,0x18(%rsp) /* tf_rcx */ movq 0x80(%rsp),%rax movq %rax,0x10(%rsp) /* tf_rdx */ /* * And fill the three words themself. */ movq %cr2,%rax movq %rax,0x80(%rsp) /* tf_addr */ movl %ecx,0x78(%rsp) /* tf_trapno */ movw %ds,0x8e(%rsp) movw %es,0x8c(%rsp) movw %fs,0x7c(%rsp) movw %gs,0x7e(%rsp) movw $0,0x88(%rsp) /* tf_flags */ /* * Call dump routine. */ movq %rsp,%rdi callq report_exc /* * Hang after reporting. Interrupts are already disabled. */ 1: hlt jmp 1b diff --git a/stand/efi/loader/arch/amd64/trap.c b/stand/efi/loader/arch/amd64/trap.c index e8cf188cf22f..1a4306b00e0a 100644 --- a/stand/efi/loader/arch/amd64/trap.c +++ b/stand/efi/loader/arch/amd64/trap.c @@ -1,408 +1,407 @@ /*- * Copyright (c) 2016 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Konstantin Belousov under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include "bootstrap.h" #include "loader_efi.h" #define NUM_IST 8 #define NUM_EXC 32 /* * This code catches exceptions but forwards hardware interrupts to * handlers installed by firmware. It differentiates exceptions * vs. interrupts by presence of the error code on the stack, which * causes different stack pointer value on trap handler entry. * * Use kernel layout for the trapframe just to not be original. * * Use free IST slot in existing TSS, or create our own TSS if * firmware did not configured any, to have stack switched to * IST-specified one, e.g. to handle #SS. If hand-off cannot find * unused IST slot, or create a new descriptor in GDT, we bail out. */ static struct region_descriptor fw_idt; /* Descriptor for pristine fw IDT */ static struct region_descriptor loader_idt;/* Descriptor for loader shadow IDT */ static EFI_PHYSICAL_ADDRESS lidt_pa; /* Address of loader shadow IDT */ static EFI_PHYSICAL_ADDRESS tss_pa; /* Address of TSS */ static EFI_PHYSICAL_ADDRESS exc_stack_pa;/* Address of IST stack for loader */ EFI_PHYSICAL_ADDRESS exc_rsp; /* %rsp value on our IST stack when exception happens */ EFI_PHYSICAL_ADDRESS fw_intr_handlers[NUM_EXC]; /* fw handlers for < 32 IDT vectors */ static int intercepted[NUM_EXC]; static int ist; /* IST for exception handlers */ static uint32_t tss_fw_seg; /* Fw TSS segment */ static uint32_t loader_tss; /* Loader TSS segment */ static struct region_descriptor fw_gdt; /* Descriptor of pristine GDT */ static EFI_PHYSICAL_ADDRESS loader_gdt_pa; /* Address of loader shadow GDT */ void report_exc(struct trapframe *tf); void report_exc(struct trapframe *tf) { /* * printf() depends on loader runtime and UEFI firmware health * to produce the console output, in case of exception, the * loader or firmware runtime may fail to support the printf(). */ printf("====================================================" "============================\n"); printf("Exception %u\n", tf->tf_trapno); printf("ss 0x%04hx cs 0x%04hx ds 0x%04hx es 0x%04hx fs 0x%04hx " "gs 0x%04hx\n", (uint16_t)tf->tf_ss, (uint16_t)tf->tf_cs, (uint16_t)tf->tf_ds, (uint16_t)tf->tf_es, (uint16_t)tf->tf_fs, (uint16_t)tf->tf_gs); printf("err 0x%08x rfl 0x%08x addr 0x%016lx\n" "rsp 0x%016lx rip 0x%016lx\n", (uint32_t)tf->tf_err, (uint32_t)tf->tf_rflags, tf->tf_addr, tf->tf_rsp, tf->tf_rip); printf( "rdi 0x%016lx rsi 0x%016lx rdx 0x%016lx\n" "rcx 0x%016lx r8 0x%016lx r9 0x%016lx\n" "rax 0x%016lx rbx 0x%016lx rbp 0x%016lx\n" "r10 0x%016lx r11 0x%016lx r12 0x%016lx\n" "r13 0x%016lx r14 0x%016lx r15 0x%016lx\n", tf->tf_rdi, tf->tf_rsi, tf->tf_rdx, tf->tf_rcx, tf->tf_r8, tf->tf_r9, tf->tf_rax, tf->tf_rbx, tf->tf_rbp, tf->tf_r10, tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15); printf("Machine stopped.\n"); } static void prepare_exception(unsigned idx, uint64_t my_handler, int ist_use_table[static NUM_IST]) { struct gate_descriptor *fw_idt_e, *loader_idt_e; fw_idt_e = &((struct gate_descriptor *)fw_idt.rd_base)[idx]; loader_idt_e = &((struct gate_descriptor *)loader_idt.rd_base)[idx]; fw_intr_handlers[idx] = fw_idt_e->gd_looffset + (fw_idt_e->gd_hioffset << 16); intercepted[idx] = 1; ist_use_table[fw_idt_e->gd_ist]++; loader_idt_e->gd_looffset = my_handler; loader_idt_e->gd_hioffset = my_handler >> 16; /* * We reuse uefi selector for the code segment for the exception * handler code, while the reason for the fault might be the * corruption of that gdt entry. On the other hand, allocating * our own descriptor might be not much better, if gdt is corrupted. */ loader_idt_e->gd_selector = fw_idt_e->gd_selector; loader_idt_e->gd_ist = 0; loader_idt_e->gd_type = SDT_SYSIGT; loader_idt_e->gd_dpl = 0; loader_idt_e->gd_p = 1; loader_idt_e->gd_xx = 0; loader_idt_e->sd_xx1 = 0; } #define PREPARE_EXCEPTION(N) \ extern char EXC##N##_handler[]; \ prepare_exception(N, (uintptr_t)EXC##N##_handler, ist_use_table); static void free_tables(void) { if (lidt_pa != 0) { BS->FreePages(lidt_pa, EFI_SIZE_TO_PAGES(fw_idt.rd_limit)); lidt_pa = 0; } if (exc_stack_pa != 0) { BS->FreePages(exc_stack_pa, 1); exc_stack_pa = 0; } if (tss_pa != 0 && tss_fw_seg == 0) { BS->FreePages(tss_pa, EFI_SIZE_TO_PAGES(sizeof(struct amd64tss))); tss_pa = 0; } if (loader_gdt_pa != 0) { BS->FreePages(tss_pa, 2); loader_gdt_pa = 0; } ist = 0; loader_tss = 0; } static int efi_setup_tss(struct region_descriptor *gdt, uint32_t loader_tss_idx, struct amd64tss **tss) { EFI_STATUS status; struct system_segment_descriptor *tss_desc; tss_desc = (struct system_segment_descriptor *)(gdt->rd_base + (loader_tss_idx << 3)); status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(sizeof(struct amd64tss)), &tss_pa); if (EFI_ERROR(status)) { printf("efi_setup_tss: AllocatePages tss error %lu\n", EFI_ERROR_CODE(status)); return (0); } *tss = (struct amd64tss *)tss_pa; bzero(*tss, sizeof(**tss)); tss_desc->sd_lolimit = sizeof(struct amd64tss); tss_desc->sd_lobase = tss_pa; tss_desc->sd_type = SDT_SYSTSS; tss_desc->sd_dpl = 0; tss_desc->sd_p = 1; tss_desc->sd_hilimit = sizeof(struct amd64tss) >> 16; tss_desc->sd_gran = 0; tss_desc->sd_hibase = tss_pa >> 24; tss_desc->sd_xx0 = 0; tss_desc->sd_xx1 = 0; tss_desc->sd_mbz = 0; tss_desc->sd_xx2 = 0; return (1); } static int efi_redirect_exceptions(void) { int ist_use_table[NUM_IST]; struct gate_descriptor *loader_idt_e; struct system_segment_descriptor *tss_desc, *gdt_desc; struct amd64tss *tss; struct region_descriptor *gdt_rd, loader_gdt; uint32_t i; EFI_STATUS status; register_t rfl; sidt(&fw_idt); status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(fw_idt.rd_limit), &lidt_pa); if (EFI_ERROR(status)) { printf("efi_redirect_exceptions: AllocatePages IDT error %lu\n", EFI_ERROR_CODE(status)); lidt_pa = 0; return (0); } status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, &exc_stack_pa); if (EFI_ERROR(status)) { printf("efi_redirect_exceptions: AllocatePages stk error %lu\n", EFI_ERROR_CODE(status)); exc_stack_pa = 0; free_tables(); return (0); } loader_idt.rd_limit = fw_idt.rd_limit; bcopy((void *)fw_idt.rd_base, (void *)loader_idt.rd_base, loader_idt.rd_limit); bzero(ist_use_table, sizeof(ist_use_table)); bzero(fw_intr_handlers, sizeof(fw_intr_handlers)); bzero(intercepted, sizeof(intercepted)); sgdt(&fw_gdt); tss_fw_seg = read_tr(); gdt_rd = NULL; if (tss_fw_seg == 0) { for (i = 2; (i << 3) + sizeof(*gdt_desc) <= fw_gdt.rd_limit; i += 2) { gdt_desc = (struct system_segment_descriptor *)( fw_gdt.rd_base + (i << 3)); if (gdt_desc->sd_type == 0 && gdt_desc->sd_mbz == 0) { gdt_rd = &fw_gdt; break; } } if (gdt_rd == NULL) { if (i >= 8190) { printf("efi_redirect_exceptions: all slots " "in gdt are used\n"); free_tables(); return (0); } loader_gdt.rd_limit = roundup2(fw_gdt.rd_limit + sizeof(struct system_segment_descriptor), sizeof(struct system_segment_descriptor)) - 1; i = (loader_gdt.rd_limit + 1 - sizeof(struct system_segment_descriptor)) / sizeof(struct system_segment_descriptor) * 2; status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(loader_gdt.rd_limit), &loader_gdt_pa); if (EFI_ERROR(status)) { printf("efi_setup_tss: AllocatePages gdt error " "%lu\n", EFI_ERROR_CODE(status)); loader_gdt_pa = 0; free_tables(); return (0); } loader_gdt.rd_base = loader_gdt_pa; bzero((void *)loader_gdt.rd_base, loader_gdt.rd_limit); bcopy((void *)fw_gdt.rd_base, (void *)loader_gdt.rd_base, fw_gdt.rd_limit); gdt_rd = &loader_gdt; } loader_tss = i << 3; if (!efi_setup_tss(gdt_rd, i, &tss)) { tss_pa = 0; free_tables(); return (0); } } else { tss_desc = (struct system_segment_descriptor *)((char *) fw_gdt.rd_base + tss_fw_seg); if (tss_desc->sd_type != SDT_SYSTSS && tss_desc->sd_type != SDT_SYSBSY) { printf("LTR points to non-TSS descriptor\n"); free_tables(); return (0); } tss_pa = tss_desc->sd_lobase + (tss_desc->sd_hibase << 16); tss = (struct amd64tss *)tss_pa; tss_desc->sd_type = SDT_SYSTSS; /* unbusy */ } PREPARE_EXCEPTION(0); PREPARE_EXCEPTION(1); PREPARE_EXCEPTION(2); PREPARE_EXCEPTION(3); PREPARE_EXCEPTION(4); PREPARE_EXCEPTION(5); PREPARE_EXCEPTION(6); PREPARE_EXCEPTION(7); PREPARE_EXCEPTION(8); PREPARE_EXCEPTION(9); PREPARE_EXCEPTION(10); PREPARE_EXCEPTION(11); PREPARE_EXCEPTION(12); PREPARE_EXCEPTION(13); PREPARE_EXCEPTION(14); PREPARE_EXCEPTION(16); PREPARE_EXCEPTION(17); PREPARE_EXCEPTION(18); PREPARE_EXCEPTION(19); PREPARE_EXCEPTION(20); exc_rsp = exc_stack_pa + PAGE_SIZE - (6 /* hw exception frame */ + 3 /* scratch regs */) * 8; /* Find free IST and use it */ for (ist = 1; ist < NUM_IST; ist++) { if (ist_use_table[ist] == 0) break; } if (ist == NUM_IST) { printf("efi_redirect_exceptions: all ISTs used\n"); free_tables(); lidt_pa = 0; return (0); } for (i = 0; i < NUM_EXC; i++) { loader_idt_e = &((struct gate_descriptor *)loader_idt. rd_base)[i]; if (intercepted[i]) loader_idt_e->gd_ist = ist; } (&(tss->tss_ist1))[ist - 1] = exc_stack_pa + PAGE_SIZE; /* Switch to new IDT */ rfl = intr_disable(); if (loader_gdt_pa != 0) bare_lgdt(&loader_gdt); if (loader_tss != 0) ltr(loader_tss); lidt(&loader_idt); intr_restore(rfl); return (1); } static void efi_unredirect_exceptions(void) { register_t rfl; if (lidt_pa == 0) return; rfl = intr_disable(); if (ist != 0) (&(((struct amd64tss *)tss_pa)->tss_ist1))[ist - 1] = 0; if (loader_gdt_pa != 0) bare_lgdt(&fw_gdt); if (loader_tss != 0) ltr(tss_fw_seg); lidt(&fw_idt); intr_restore(rfl); free_tables(); } static int command_grab_faults(int argc, char *argv[]) { int res; res = efi_redirect_exceptions(); if (!res) printf("failed\n"); return (CMD_OK); } COMMAND_SET(grap_faults, "grab_faults", "grab faults", command_grab_faults); static int command_ungrab_faults(int argc, char *argv[]) { efi_unredirect_exceptions(); return (CMD_OK); } COMMAND_SET(ungrab_faults, "ungrab_faults", "ungrab faults", command_ungrab_faults); static int command_fault(int argc, char *argv[]) { __asm("ud2"); return (CMD_OK); } COMMAND_SET(fault, "fault", "generate fault", command_fault); diff --git a/stand/efi/loader/copy.c b/stand/efi/loader/copy.c index 47e613ccc2f3..78159dfff4e3 100644 --- a/stand/efi/loader/copy.c +++ b/stand/efi/loader/copy.c @@ -1,539 +1,538 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "loader_efi.h" #define M(x) ((x) * 1024 * 1024) #define G(x) (1UL * (x) * 1024 * 1024 * 1024) #if defined(__i386__) || defined(__amd64__) #include #include #include /* * The code is excerpted from sys/x86/x86/identcpu.c: identify_cpu(), * identify_hypervisor(), and dev/hyperv/vmbus/hyperv.c: hyperv_identify(). */ #define CPUID_LEAF_HV_MAXLEAF 0x40000000 #define CPUID_LEAF_HV_INTERFACE 0x40000001 #define CPUID_LEAF_HV_FEATURES 0x40000003 #define CPUID_LEAF_HV_LIMITS 0x40000005 #define CPUID_HV_IFACE_HYPERV 0x31237648 /* HV#1 */ #define CPUID_HV_MSR_TIME_REFCNT 0x0002 /* MSR_HV_TIME_REF_COUNT */ #define CPUID_HV_MSR_HYPERCALL 0x0020 static int running_on_hyperv(void) { char hv_vendor[16]; uint32_t regs[4]; do_cpuid(1, regs); if ((regs[2] & CPUID2_HV) == 0) return (0); do_cpuid(CPUID_LEAF_HV_MAXLEAF, regs); if (regs[0] < CPUID_LEAF_HV_LIMITS) return (0); ((uint32_t *)&hv_vendor)[0] = regs[1]; ((uint32_t *)&hv_vendor)[1] = regs[2]; ((uint32_t *)&hv_vendor)[2] = regs[3]; hv_vendor[12] = '\0'; if (strcmp(hv_vendor, "Microsoft Hv") != 0) return (0); do_cpuid(CPUID_LEAF_HV_INTERFACE, regs); if (regs[0] != CPUID_HV_IFACE_HYPERV) return (0); do_cpuid(CPUID_LEAF_HV_FEATURES, regs); if ((regs[0] & CPUID_HV_MSR_HYPERCALL) == 0) return (0); if ((regs[0] & CPUID_HV_MSR_TIME_REFCNT) == 0) return (0); return (1); } static void efi_verify_staging_size(unsigned long *nr_pages) { UINTN sz; EFI_MEMORY_DESCRIPTOR *map = NULL, *p; EFI_PHYSICAL_ADDRESS start, end; UINTN key, dsz; UINT32 dver; EFI_STATUS status; int i, ndesc; unsigned long available_pages = 0; sz = 0; for (;;) { status = BS->GetMemoryMap(&sz, map, &key, &dsz, &dver); if (!EFI_ERROR(status)) break; if (status != EFI_BUFFER_TOO_SMALL) { printf("Can't read memory map: %lu\n", EFI_ERROR_CODE(status)); goto out; } free(map); /* Allocate 10 descriptors more than the size reported, * to allow for any fragmentation caused by calling * malloc */ map = malloc(sz + (10 * dsz)); if (map == NULL) { printf("Unable to allocate memory\n"); goto out; } } ndesc = sz / dsz; for (i = 0, p = map; i < ndesc; i++, p = NextMemoryDescriptor(p, dsz)) { start = p->PhysicalStart; end = start + p->NumberOfPages * EFI_PAGE_SIZE; if (KERNLOAD < start || KERNLOAD >= end) continue; available_pages = p->NumberOfPages - ((KERNLOAD - start) >> EFI_PAGE_SHIFT); break; } if (available_pages == 0) { printf("Can't find valid memory map for staging area!\n"); goto out; } i++; p = NextMemoryDescriptor(p, dsz); for ( ; i < ndesc; i++, p = NextMemoryDescriptor(p, dsz)) { if (p->Type != EfiConventionalMemory && p->Type != EfiLoaderData) break; if (p->PhysicalStart != end) break; end = p->PhysicalStart + p->NumberOfPages * EFI_PAGE_SIZE; available_pages += p->NumberOfPages; } if (*nr_pages > available_pages) { printf("Staging area's size is reduced: %ld -> %ld!\n", *nr_pages, available_pages); *nr_pages = available_pages; } out: free(map); } #endif /* __i386__ || __amd64__ */ #if defined(__arm__) #define DEFAULT_EFI_STAGING_SIZE 32 #else #define DEFAULT_EFI_STAGING_SIZE 64 #endif #ifndef EFI_STAGING_SIZE #define EFI_STAGING_SIZE DEFAULT_EFI_STAGING_SIZE #endif #if defined(__aarch64__) || defined(__amd64__) || defined(__arm__) || \ defined(__riscv) #define EFI_STAGING_2M_ALIGN 1 #else #define EFI_STAGING_2M_ALIGN 0 #endif #if defined(__amd64__) #define EFI_STAGING_SLOP M(8) #else #define EFI_STAGING_SLOP 0 #endif static u_long staging_slop = EFI_STAGING_SLOP; EFI_PHYSICAL_ADDRESS staging, staging_end, staging_base; int stage_offset_set = 0; ssize_t stage_offset; static void efi_copy_free(void) { BS->FreePages(staging_base, (staging_end - staging_base) / EFI_PAGE_SIZE); stage_offset_set = 0; stage_offset = 0; } #ifdef __amd64__ int copy_staging = COPY_STAGING_AUTO; static int command_copy_staging(int argc, char *argv[]) { static const char *const mode[3] = { [COPY_STAGING_ENABLE] = "enable", [COPY_STAGING_DISABLE] = "disable", [COPY_STAGING_AUTO] = "auto", }; int prev, res; res = CMD_OK; if (argc > 2) { res = CMD_ERROR; } else if (argc == 2) { prev = copy_staging; if (strcmp(argv[1], "enable") == 0) copy_staging = COPY_STAGING_ENABLE; else if (strcmp(argv[1], "disable") == 0) copy_staging = COPY_STAGING_DISABLE; else if (strcmp(argv[1], "auto") == 0) copy_staging = COPY_STAGING_AUTO; else { printf("usage: copy_staging enable|disable|auto\n"); res = CMD_ERROR; } if (res == CMD_OK && prev != copy_staging) { printf("changed copy_staging, unloading kernel\n"); unload(); efi_copy_free(); efi_copy_init(); } } else { printf("copy staging: %s\n", mode[copy_staging]); } return (res); } COMMAND_SET(copy_staging, "copy_staging", "copy staging", command_copy_staging); #endif static int command_staging_slop(int argc, char *argv[]) { char *endp; u_long new, prev; int res; res = CMD_OK; if (argc > 2) { res = CMD_ERROR; } else if (argc == 2) { new = strtoul(argv[1], &endp, 0); if (*endp != '\0') { printf("invalid slop value\n"); res = CMD_ERROR; } if (res == CMD_OK && staging_slop != new) { printf("changed slop, unloading kernel\n"); unload(); efi_copy_free(); efi_copy_init(); } } else { printf("staging slop %#lx\n", staging_slop); } return (res); } COMMAND_SET(staging_slop, "staging_slop", "set staging slop", command_staging_slop); #if defined(__i386__) || defined(__amd64__) /* * The staging area must reside in the the first 1GB or 4GB physical * memory: see elf64_exec() in * boot/efi/loader/arch/amd64/elf64_freebsd.c. */ static EFI_PHYSICAL_ADDRESS get_staging_max(void) { EFI_PHYSICAL_ADDRESS res; #if defined(__i386__) res = G(1); #elif defined(__amd64__) res = copy_staging == COPY_STAGING_ENABLE ? G(1) : G(4); #endif return (res); } #define EFI_ALLOC_METHOD AllocateMaxAddress #else #define EFI_ALLOC_METHOD AllocateAnyPages #endif int efi_copy_init(void) { EFI_STATUS status; unsigned long nr_pages; vm_offset_t ess; ess = EFI_STAGING_SIZE; if (ess < DEFAULT_EFI_STAGING_SIZE) ess = DEFAULT_EFI_STAGING_SIZE; nr_pages = EFI_SIZE_TO_PAGES(M(1) * ess); #if defined(__i386__) || defined(__amd64__) /* * We'll decrease nr_pages, if it's too big. Currently we only * apply this to FreeBSD VM running on Hyper-V. Why? Please see * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=211746#c28 */ if (running_on_hyperv()) efi_verify_staging_size(&nr_pages); staging = get_staging_max(); #endif status = BS->AllocatePages(EFI_ALLOC_METHOD, EfiLoaderData, nr_pages, &staging); if (EFI_ERROR(status)) { printf("failed to allocate staging area: %lu\n", EFI_ERROR_CODE(status)); return (status); } staging_base = staging; staging_end = staging + nr_pages * EFI_PAGE_SIZE; #if EFI_STAGING_2M_ALIGN /* * Round the kernel load address to a 2MiB value. This is needed * because the kernel builds a page table based on where it has * been loaded in physical address space. As the kernel will use * either a 1MiB or 2MiB page for this we need to make sure it * is correctly aligned for both cases. */ staging = roundup2(staging, M(2)); #endif return (0); } static bool efi_check_space(vm_offset_t end) { EFI_PHYSICAL_ADDRESS addr, new_base, new_staging; EFI_STATUS status; unsigned long nr_pages; end = roundup2(end, EFI_PAGE_SIZE); /* There is already enough space */ if (end + staging_slop <= staging_end) return (true); if (!boot_services_active) { if (end <= staging_end) return (true); panic("efi_check_space: cannot expand staging area " "after boot services were exited\n"); } /* * Add slop at the end: * 1. amd64 kernel expects to do some very early allocations * by carving out memory after kernend. Slop guarantees * that it does not ovewrite anything useful. * 2. It seems that initial calculation of the staging size * could be somewhat smaller than actually copying in after * boot services are exited. Slop avoids calling * BS->AllocatePages() when it cannot work. */ end += staging_slop; nr_pages = EFI_SIZE_TO_PAGES(end - staging_end); #if defined(__i386__) || defined(__amd64__) /* * i386 needs all memory to be allocated under the 1G boundary. * amd64 needs all memory to be allocated under the 1G or 4G boundary. */ if (end > get_staging_max()) goto before_staging; #endif /* Try to allocate more space after the previous allocation */ addr = staging_end; status = BS->AllocatePages(AllocateAddress, EfiLoaderData, nr_pages, &addr); if (!EFI_ERROR(status)) { staging_end = staging_end + nr_pages * EFI_PAGE_SIZE; return (true); } before_staging: /* Try allocating space before the previous allocation */ if (staging < nr_pages * EFI_PAGE_SIZE) goto expand; addr = staging - nr_pages * EFI_PAGE_SIZE; #if EFI_STAGING_2M_ALIGN /* See efi_copy_init for why this is needed */ addr = rounddown2(addr, M(2)); #endif nr_pages = EFI_SIZE_TO_PAGES(staging_base - addr); status = BS->AllocatePages(AllocateAddress, EfiLoaderData, nr_pages, &addr); if (!EFI_ERROR(status)) { /* * Move the old allocation and update the state so * translation still works. */ staging_base = addr; memmove((void *)(uintptr_t)staging_base, (void *)(uintptr_t)staging, staging_end - staging); stage_offset -= staging - staging_base; staging = staging_base; return (true); } expand: nr_pages = EFI_SIZE_TO_PAGES(end - (vm_offset_t)staging); #if EFI_STAGING_2M_ALIGN nr_pages += M(2) / EFI_PAGE_SIZE; #endif #if defined(__i386__) || defined(__amd64__) new_base = get_staging_max(); #endif status = BS->AllocatePages(EFI_ALLOC_METHOD, EfiLoaderData, nr_pages, &new_base); if (!EFI_ERROR(status)) { #if EFI_STAGING_2M_ALIGN new_staging = roundup2(new_base, M(2)); #else new_staging = new_base; #endif /* * Move the old allocation and update the state so * translation still works. */ memcpy((void *)(uintptr_t)new_staging, (void *)(uintptr_t)staging, staging_end - staging); BS->FreePages(staging_base, (staging_end - staging_base) / EFI_PAGE_SIZE); stage_offset -= staging - new_staging; staging = new_staging; staging_end = new_base + nr_pages * EFI_PAGE_SIZE; staging_base = new_base; return (true); } printf("efi_check_space: Unable to expand staging area\n"); return (false); } void * efi_translate(vm_offset_t ptr) { return ((void *)(ptr + stage_offset)); } ssize_t efi_copyin(const void *src, vm_offset_t dest, const size_t len) { if (!stage_offset_set) { stage_offset = (vm_offset_t)staging - dest; stage_offset_set = 1; } /* XXX: Callers do not check for failure. */ if (!efi_check_space(dest + stage_offset + len)) { errno = ENOMEM; return (-1); } bcopy(src, (void *)(dest + stage_offset), len); return (len); } ssize_t efi_copyout(const vm_offset_t src, void *dest, const size_t len) { /* XXX: Callers do not check for failure. */ if (src + stage_offset + len > staging_end) { errno = ENOMEM; return (-1); } bcopy((void *)(src + stage_offset), dest, len); return (len); } ssize_t efi_readin(readin_handle_t fd, vm_offset_t dest, const size_t len) { if (!stage_offset_set) { stage_offset = (vm_offset_t)staging - dest; stage_offset_set = 1; } if (!efi_check_space(dest + stage_offset + len)) { errno = ENOMEM; return (-1); } return (VECTX_READ(fd, (void *)(dest + stage_offset), len)); } void efi_copy_finish(void) { uint64_t *src, *dst, *last; src = (uint64_t *)(uintptr_t)staging; dst = (uint64_t *)(uintptr_t)(staging - stage_offset); last = (uint64_t *)(uintptr_t)staging_end; while (src < last) *dst++ = *src++; } void efi_copy_finish_nop(void) { } diff --git a/stand/efi/loader/framebuffer.c b/stand/efi/loader/framebuffer.c index d5504c9cff35..afae18603f9e 100644 --- a/stand/efi/loader/framebuffer.c +++ b/stand/efi/loader/framebuffer.c @@ -1,942 +1,941 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "bootstrap.h" #include "framebuffer.h" static EFI_GUID conout_guid = EFI_CONSOLE_OUT_DEVICE_GUID; EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; static EFI_GUID pciio_guid = EFI_PCI_IO_PROTOCOL_GUID; static EFI_GUID uga_guid = EFI_UGA_DRAW_PROTOCOL_GUID; static EFI_GUID active_edid_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; static EFI_GUID discovered_edid_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID; static EFI_HANDLE gop_handle; /* Cached EDID. */ struct vesa_edid_info *edid_info = NULL; static EFI_GRAPHICS_OUTPUT *gop; static EFI_UGA_DRAW_PROTOCOL *uga; static struct named_resolution { const char *name; const char *alias; unsigned int width; unsigned int height; } resolutions[] = { { .name = "480p", .width = 640, .height = 480, }, { .name = "720p", .width = 1280, .height = 720, }, { .name = "1080p", .width = 1920, .height = 1080, }, { .name = "2160p", .alias = "4k", .width = 3840, .height = 2160, }, { .name = "5k", .width = 5120, .height = 2880, } }; static u_int efifb_color_depth(struct efi_fb *efifb) { uint32_t mask; u_int depth; mask = efifb->fb_mask_red | efifb->fb_mask_green | efifb->fb_mask_blue | efifb->fb_mask_reserved; if (mask == 0) return (0); for (depth = 1; mask != 1; depth++) mask >>= 1; return (depth); } static int efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt, EFI_PIXEL_BITMASK *pixinfo) { int result; result = 0; switch (pixfmt) { case PixelRedGreenBlueReserved8BitPerColor: case PixelBltOnly: efifb->fb_mask_red = 0x000000ff; efifb->fb_mask_green = 0x0000ff00; efifb->fb_mask_blue = 0x00ff0000; efifb->fb_mask_reserved = 0xff000000; break; case PixelBlueGreenRedReserved8BitPerColor: efifb->fb_mask_red = 0x00ff0000; efifb->fb_mask_green = 0x0000ff00; efifb->fb_mask_blue = 0x000000ff; efifb->fb_mask_reserved = 0xff000000; break; case PixelBitMask: efifb->fb_mask_red = pixinfo->RedMask; efifb->fb_mask_green = pixinfo->GreenMask; efifb->fb_mask_blue = pixinfo->BlueMask; efifb->fb_mask_reserved = pixinfo->ReservedMask; break; default: result = 1; break; } return (result); } static int efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode, EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info) { int result; efifb->fb_addr = mode->FrameBufferBase; efifb->fb_size = mode->FrameBufferSize; efifb->fb_height = info->VerticalResolution; efifb->fb_width = info->HorizontalResolution; efifb->fb_stride = info->PixelsPerScanLine; result = efifb_mask_from_pixfmt(efifb, info->PixelFormat, &info->PixelInformation); return (result); } static ssize_t efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, u_int line, EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size) { EFI_UGA_PIXEL pix0, pix1; uint8_t *data1, *data2; size_t count, maxcount = 1024; ssize_t ofs; EFI_STATUS status; u_int idx; status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer, 0, line, 0, 0, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (video->buffer)"); return (-1); } pix1.Red = ~pix0.Red; pix1.Green = ~pix0.Green; pix1.Blue = ~pix0.Blue; pix1.Reserved = 0; data1 = calloc(maxcount, 2); if (data1 == NULL) { printf("Unable to allocate memory"); return (-1); } data2 = data1 + maxcount; ofs = 0; while (size > 0) { count = min(size, maxcount); status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32, EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2, data1); if (EFI_ERROR(status)) { printf("Error reading frame buffer (before)"); goto fail; } status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo, 0, 0, 0, line, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (modify)"); goto fail; } status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32, EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2, data2); if (EFI_ERROR(status)) { printf("Error reading frame buffer (after)"); goto fail; } status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo, 0, 0, 0, line, 1, 1, 0); if (EFI_ERROR(status)) { printf("UGA BLT operation failed (restore)"); goto fail; } for (idx = 0; idx < count; idx++) { if (data1[idx] != data2[idx]) { free(data1); return (ofs + (idx & ~3)); } } ofs += count; size -= count; } printf("No change detected in frame buffer"); fail: printf(" -- error %lu\n", EFI_ERROR_CODE(status)); free(data1); return (-1); } static EFI_PCI_IO_PROTOCOL * efifb_uga_get_pciio(void) { EFI_PCI_IO_PROTOCOL *pciio; EFI_HANDLE *buf, *hp; EFI_STATUS status; UINTN bufsz; /* Get all handles that support the UGA protocol. */ bufsz = 0; status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, NULL); if (status != EFI_BUFFER_TOO_SMALL) return (NULL); buf = malloc(bufsz); status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, buf); if (status != EFI_SUCCESS) { free(buf); return (NULL); } bufsz /= sizeof(EFI_HANDLE); /* Get the PCI I/O interface of the first handle that supports it. */ pciio = NULL; for (hp = buf; hp < buf + bufsz; hp++) { status = OpenProtocolByHandle(*hp, &pciio_guid, (void **)&pciio); if (status == EFI_SUCCESS) { free(buf); return (pciio); } } free(buf); return (NULL); } static EFI_STATUS efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp, uint64_t *sizep) { uint8_t *resattr; uint64_t addr, size; EFI_STATUS status; u_int bar; if (pciio == NULL) return (EFI_DEVICE_ERROR); /* Attempt to get the frame buffer address (imprecise). */ *addrp = 0; *sizep = 0; for (bar = 0; bar < 6; bar++) { status = pciio->GetBarAttributes(pciio, bar, NULL, (void **)&resattr); if (status != EFI_SUCCESS) continue; /* XXX magic offsets and constants. */ if (resattr[0] == 0x87 && resattr[3] == 0) { /* 32-bit address space descriptor (MEMIO) */ addr = le32dec(resattr + 10); size = le32dec(resattr + 22); } else if (resattr[0] == 0x8a && resattr[3] == 0) { /* 64-bit address space descriptor (MEMIO) */ addr = le64dec(resattr + 14); size = le64dec(resattr + 38); } else { addr = 0; size = 0; } BS->FreePool(resattr); if (addr == 0 || size == 0) continue; /* We assume the largest BAR is the frame buffer. */ if (size > *sizep) { *addrp = addr; *sizep = size; } } return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0); } static int efifb_from_uga(struct efi_fb *efifb) { EFI_PCI_IO_PROTOCOL *pciio; char *ev, *p; EFI_STATUS status; ssize_t offset; uint64_t fbaddr; uint32_t horiz, vert, stride; uint32_t np, depth, refresh; status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh); if (EFI_ERROR(status)) return (1); efifb->fb_height = vert; efifb->fb_width = horiz; /* Paranoia... */ if (efifb->fb_height == 0 || efifb->fb_width == 0) return (1); /* The color masks are fixed AFAICT. */ efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor, NULL); /* pciio can be NULL on return! */ pciio = efifb_uga_get_pciio(); /* Try to find the frame buffer. */ status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr, &efifb->fb_size); if (EFI_ERROR(status)) { efifb->fb_addr = 0; efifb->fb_size = 0; } /* * There's no reliable way to detect the frame buffer or the * offset within the frame buffer of the visible region, nor * the stride. Our only option is to look at the system and * fill in the blanks based on that. Luckily, UGA was mostly * only used on Apple hardware. */ offset = -1; ev = getenv("smbios.system.maker"); if (ev != NULL && !strcmp(ev, "Apple Inc.")) { ev = getenv("smbios.system.product"); if (ev != NULL && !strcmp(ev, "iMac7,1")) { /* These are the expected values we should have. */ horiz = 1680; vert = 1050; fbaddr = 0xc0000000; /* These are the missing bits. */ offset = 0x10000; stride = 1728; } else if (ev != NULL && !strcmp(ev, "MacBook3,1")) { /* These are the expected values we should have. */ horiz = 1280; vert = 800; fbaddr = 0xc0000000; /* These are the missing bits. */ offset = 0x0; stride = 2048; } } /* * If this is hardware we know, make sure that it looks familiar * before we accept our hardcoded values. */ if (offset >= 0 && efifb->fb_width == horiz && efifb->fb_height == vert && efifb->fb_addr == fbaddr) { efifb->fb_addr += offset; efifb->fb_size -= offset; efifb->fb_stride = stride; return (0); } else if (offset >= 0) { printf("Hardware make/model known, but graphics not " "as expected.\n"); printf("Console may not work!\n"); } /* * The stride is equal or larger to the width. Often it's the * next larger power of two. We'll start with that... */ efifb->fb_stride = efifb->fb_width; do { np = efifb->fb_stride & (efifb->fb_stride - 1); if (np) { efifb->fb_stride |= (np - 1); efifb->fb_stride++; } } while (np); ev = getenv("hw.efifb.address"); if (ev == NULL) { if (efifb->fb_addr == 0) { printf("Please set hw.efifb.address and " "hw.efifb.stride.\n"); return (1); } /* * The visible part of the frame buffer may not start at * offset 0, so try to detect it. Note that we may not * always be able to read from the frame buffer, which * means that we may not be able to detect anything. In * that case, we would take a long time scanning for a * pixel change in the frame buffer, which would have it * appear that we're hanging, so we limit the scan to * 1/256th of the frame buffer. This number is mostly * based on PR 202730 and the fact that on a MacBoook, * where we can't read from the frame buffer the offset * of the visible region is 0. In short: we want to scan * enough to handle all adapters that have an offset * larger than 0 and we want to scan as little as we can * to not appear to hang when we can't read from the * frame buffer. */ offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr, efifb->fb_size >> 8); if (offset == -1) { printf("Unable to reliably detect frame buffer.\n"); } else if (offset > 0) { efifb->fb_addr += offset; efifb->fb_size -= offset; } } else { offset = 0; efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4; efifb->fb_addr = strtoul(ev, &p, 0); if (*p != '\0') return (1); } ev = getenv("hw.efifb.stride"); if (ev == NULL) { if (pciio != NULL && offset != -1) { /* Determine the stride. */ offset = efifb_uga_find_pixel(uga, 1, pciio, efifb->fb_addr, horiz * 8); if (offset != -1) efifb->fb_stride = offset >> 2; } else { printf("Unable to reliably detect the stride.\n"); } } else { efifb->fb_stride = strtoul(ev, &p, 0); if (*p != '\0') return (1); } /* * We finalized on the stride, so recalculate the size of the * frame buffer. */ efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4; return (0); } /* * Fetch EDID info. Caller must free the buffer. */ static struct vesa_edid_info * efifb_gop_get_edid(EFI_HANDLE h) { const uint8_t magic[] = EDID_MAGIC; EFI_EDID_ACTIVE_PROTOCOL *edid; struct vesa_edid_info *edid_infop; EFI_GUID *guid; EFI_STATUS status; size_t size; guid = &active_edid_guid; status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (status != EFI_SUCCESS || edid->SizeOfEdid == 0) { guid = &discovered_edid_guid; status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (status != EFI_SUCCESS || edid->SizeOfEdid == 0) return (NULL); } size = MAX(sizeof(*edid_infop), edid->SizeOfEdid); edid_infop = calloc(1, size); if (edid_infop == NULL) return (NULL); memcpy(edid_infop, edid->Edid, edid->SizeOfEdid); /* Validate EDID */ if (memcmp(edid_infop, magic, sizeof (magic)) != 0) goto error; if (edid_infop->header.version != 1) goto error; return (edid_infop); error: free(edid_infop); return (NULL); } static bool efifb_get_edid(edid_res_list_t *res) { bool rv = false; if (edid_info == NULL) edid_info = efifb_gop_get_edid(gop_handle); if (edid_info != NULL) rv = gfx_get_edid_resolution(edid_info, res); return (rv); } int efi_find_framebuffer(teken_gfx_t *gfx_state) { EFI_HANDLE *hlist; UINTN nhandles, i, hsize; struct efi_fb efifb; EFI_STATUS status; int rv; gfx_state->tg_fb_type = FB_TEXT; hsize = 0; hlist = NULL; status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize, hlist); if (status == EFI_BUFFER_TOO_SMALL) { hlist = malloc(hsize); if (hlist == NULL) return (ENOMEM); status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize, hlist); if (EFI_ERROR(status)) free(hlist); } if (EFI_ERROR(status)) return (efi_status_to_errno(status)); nhandles = hsize / sizeof(*hlist); /* * Search for ConOut protocol, if not found, use first handle. */ gop_handle = NULL; for (i = 0; i < nhandles; i++) { EFI_GRAPHICS_OUTPUT *tgop; void *dummy; status = OpenProtocolByHandle(hlist[i], &gop_guid, (void **)&tgop); if (status != EFI_SUCCESS) continue; if (tgop->Mode->Info->PixelFormat == PixelBltOnly || tgop->Mode->Info->PixelFormat >= PixelFormatMax) continue; status = OpenProtocolByHandle(hlist[i], &conout_guid, &dummy); if (status == EFI_SUCCESS) { gop_handle = hlist[i]; gop = tgop; break; } else if (gop_handle == NULL) { gop_handle = hlist[i]; gop = tgop; } } free(hlist); if (gop_handle != NULL) { gfx_state->tg_fb_type = FB_GOP; gfx_state->tg_private = gop; if (edid_info == NULL) edid_info = efifb_gop_get_edid(gop_handle); } else { status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga); if (status == EFI_SUCCESS) { gfx_state->tg_fb_type = FB_UGA; gfx_state->tg_private = uga; } else { return (1); } } switch (gfx_state->tg_fb_type) { case FB_GOP: rv = efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); break; case FB_UGA: rv = efifb_from_uga(&efifb); break; default: return (1); } gfx_state->tg_fb.fb_addr = efifb.fb_addr; gfx_state->tg_fb.fb_size = efifb.fb_size; gfx_state->tg_fb.fb_height = efifb.fb_height; gfx_state->tg_fb.fb_width = efifb.fb_width; gfx_state->tg_fb.fb_stride = efifb.fb_stride; gfx_state->tg_fb.fb_mask_red = efifb.fb_mask_red; gfx_state->tg_fb.fb_mask_green = efifb.fb_mask_green; gfx_state->tg_fb.fb_mask_blue = efifb.fb_mask_blue; gfx_state->tg_fb.fb_mask_reserved = efifb.fb_mask_reserved; gfx_state->tg_fb.fb_bpp = fls(efifb.fb_mask_red | efifb.fb_mask_green | efifb.fb_mask_blue | efifb.fb_mask_reserved); if (gfx_state->tg_shadow_fb != NULL) BS->FreePages((EFI_PHYSICAL_ADDRESS)gfx_state->tg_shadow_fb, gfx_state->tg_shadow_sz); gfx_state->tg_shadow_sz = EFI_SIZE_TO_PAGES(efifb.fb_height * efifb.fb_width * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL)); status = BS->AllocatePages(AllocateMaxAddress, EfiLoaderData, gfx_state->tg_shadow_sz, (EFI_PHYSICAL_ADDRESS *)&gfx_state->tg_shadow_fb); if (status != EFI_SUCCESS) gfx_state->tg_shadow_fb = NULL; return (0); } static void print_efifb(int mode, struct efi_fb *efifb, int verbose) { u_int depth; if (mode >= 0) printf("mode %d: ", mode); depth = efifb_color_depth(efifb); printf("%ux%ux%u, stride=%u", efifb->fb_width, efifb->fb_height, depth, efifb->fb_stride); if (verbose) { printf("\n frame buffer: address=%jx, size=%jx", (uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size); printf("\n color mask: R=%08x, G=%08x, B=%08x\n", efifb->fb_mask_red, efifb->fb_mask_green, efifb->fb_mask_blue); } } static bool efi_resolution_compare(struct named_resolution *res, const char *cmp) { if (strcasecmp(res->name, cmp) == 0) return (true); if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0) return (true); return (false); } static void efi_get_max_resolution(int *width, int *height) { struct named_resolution *res; char *maxres; char *height_start, *width_start; int idx; *width = *height = 0; maxres = getenv("efi_max_resolution"); /* No max_resolution set? Bail out; choose highest resolution */ if (maxres == NULL) return; /* See if it matches one of our known resolutions */ for (idx = 0; idx < nitems(resolutions); ++idx) { res = &resolutions[idx]; if (efi_resolution_compare(res, maxres)) { *width = res->width; *height = res->height; return; } } /* Not a known resolution, try to parse it; make a copy we can modify */ maxres = strdup(maxres); if (maxres == NULL) return; height_start = strchr(maxres, 'x'); if (height_start == NULL) { free(maxres); return; } width_start = maxres; *height_start++ = 0; /* Errors from this will effectively mean "no max" */ *width = (int)strtol(width_start, NULL, 0); *height = (int)strtol(height_start, NULL, 0); free(maxres); } static int gop_autoresize(void) { struct efi_fb efifb; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; EFI_STATUS status; UINTN infosz; UINT32 best_mode, currdim, maxdim, mode; int height, max_height, max_width, width; best_mode = maxdim = 0; efi_get_max_resolution(&max_width, &max_height); for (mode = 0; mode < gop->Mode->MaxMode; mode++) { status = gop->QueryMode(gop, mode, &infosz, &info); if (EFI_ERROR(status)) continue; efifb_from_gop(&efifb, gop->Mode, info); width = info->HorizontalResolution; height = info->VerticalResolution; currdim = width * height; if (currdim > maxdim) { if ((max_width != 0 && width > max_width) || (max_height != 0 && height > max_height)) continue; maxdim = currdim; best_mode = mode; } } if (maxdim != 0) { status = gop->SetMode(gop, best_mode); if (EFI_ERROR(status)) { snprintf(command_errbuf, sizeof(command_errbuf), "gop_autoresize: Unable to set mode to %u (error=%lu)", mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } (void) cons_update_mode(true); } return (CMD_OK); } static int text_autoresize() { SIMPLE_TEXT_OUTPUT_INTERFACE *conout; EFI_STATUS status; UINTN i, max_dim, best_mode, cols, rows; conout = ST->ConOut; max_dim = best_mode = 0; for (i = 0; i < conout->Mode->MaxMode; i++) { status = conout->QueryMode(conout, i, &cols, &rows); if (EFI_ERROR(status)) continue; if (cols * rows > max_dim) { max_dim = cols * rows; best_mode = i; } } if (max_dim > 0) conout->SetMode(conout, best_mode); (void) cons_update_mode(true); return (CMD_OK); } static int uga_autoresize(void) { return (text_autoresize()); } COMMAND_SET(efi_autoresize, "efi-autoresizecons", "EFI Auto-resize Console", command_autoresize); static int command_autoresize(int argc, char *argv[]) { char *textmode; textmode = getenv("hw.vga.textmode"); /* If it's set and non-zero, we'll select a console mode instead */ if (textmode != NULL && strcmp(textmode, "0") != 0) return (text_autoresize()); if (gop != NULL) return (gop_autoresize()); if (uga != NULL) return (uga_autoresize()); snprintf(command_errbuf, sizeof(command_errbuf), "%s: Neither Graphics Output Protocol nor Universal Graphics Adapter present", argv[0]); /* * Default to text_autoresize if we have neither GOP or UGA. This won't * give us the most ideal resolution, but it will at least leave us * functional rather than failing the boot for an objectively bad * reason. */ return (text_autoresize()); } COMMAND_SET(gop, "gop", "graphics output protocol", command_gop); static int command_gop(int argc, char *argv[]) { struct efi_fb efifb; EFI_STATUS status; u_int mode; if (gop == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Graphics Output Protocol not present", argv[0]); return (CMD_ERROR); } if (argc < 2) goto usage; if (!strcmp(argv[1], "set")) { char *cp; if (argc != 3) goto usage; mode = strtol(argv[2], &cp, 0); if (cp[0] != '\0') { sprintf(command_errbuf, "mode is an integer"); return (CMD_ERROR); } status = gop->SetMode(gop, mode); if (EFI_ERROR(status)) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Unable to set mode to %u (error=%lu)", argv[0], mode, EFI_ERROR_CODE(status)); return (CMD_ERROR); } (void) cons_update_mode(true); } else if (strcmp(argv[1], "off") == 0) { (void) cons_update_mode(false); } else if (strcmp(argv[1], "get") == 0) { edid_res_list_t res; if (argc != 2) goto usage; TAILQ_INIT(&res); efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); if (efifb_get_edid(&res)) { struct resolution *rp; printf("EDID"); while ((rp = TAILQ_FIRST(&res)) != NULL) { printf(" %dx%d", rp->width, rp->height); TAILQ_REMOVE(&res, rp, next); free(rp); } printf("\n"); } else { printf("no EDID information\n"); } print_efifb(gop->Mode->Mode, &efifb, 1); printf("\n"); } else if (!strcmp(argv[1], "list")) { EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; UINTN infosz; if (argc != 2) goto usage; pager_open(); for (mode = 0; mode < gop->Mode->MaxMode; mode++) { status = gop->QueryMode(gop, mode, &infosz, &info); if (EFI_ERROR(status)) continue; efifb_from_gop(&efifb, gop->Mode, info); print_efifb(mode, &efifb, 0); if (pager_output("\n")) break; } pager_close(); } return (CMD_OK); usage: snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s [list | get | set | off]", argv[0]); return (CMD_ERROR); } COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga); static int command_uga(int argc, char *argv[]) { struct efi_fb efifb; if (uga == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: UGA Protocol not present", argv[0]); return (CMD_ERROR); } if (argc != 1) goto usage; if (efifb_from_uga(&efifb) != CMD_OK) { snprintf(command_errbuf, sizeof(command_errbuf), "%s: Unable to get UGA information", argv[0]); return (CMD_ERROR); } print_efifb(-1, &efifb, 1); printf("\n"); return (CMD_OK); usage: snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s", argv[0]); return (CMD_ERROR); } diff --git a/stand/efi/loader/framebuffer.h b/stand/efi/loader/framebuffer.h index 008df7f6c167..6c47a136f7be 100644 --- a/stand/efi/loader/framebuffer.h +++ b/stand/efi/loader/framebuffer.h @@ -1,38 +1,37 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #ifndef _EFIFB_H_ #define _EFIFB_H_ int efi_find_framebuffer(teken_gfx_t *gfx_state); #endif /* _EFIFB_H_ */ diff --git a/stand/efi/loader/loader_efi.h b/stand/efi/loader/loader_efi.h index 8254d16b1592..d1958d62fe10 100644 --- a/stand/efi/loader/loader_efi.h +++ b/stand/efi/loader/loader_efi.h @@ -1,58 +1,57 @@ /*- * Copyright (c) 2013 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Benno Rice under sponsorship from * the FreeBSD Foundation. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _LOADER_EFI_COPY_H_ #define _LOADER_EFI_COPY_H_ #include #include #ifdef __amd64__ enum { COPY_STAGING_ENABLE, COPY_STAGING_DISABLE, COPY_STAGING_AUTO, }; extern int copy_staging; #endif int efi_autoload(void); int efi_copy_init(void); ssize_t efi_copyin(const void *src, vm_offset_t dest, const size_t len); ssize_t efi_copyout(const vm_offset_t src, void *dest, const size_t len); ssize_t efi_readin(readin_handle_t fd, vm_offset_t dest, const size_t len); void * efi_translate(vm_offset_t ptr); void efi_copy_finish(void); void efi_copy_finish_nop(void); #endif /* _LOADER_EFI_COPY_H_ */ diff --git a/stand/fdt/fdt_loader_cmd.c b/stand/fdt/fdt_loader_cmd.c index c14b99768d70..8703d5f05e3c 100644 --- a/stand/fdt/fdt_loader_cmd.c +++ b/stand/fdt/fdt_loader_cmd.c @@ -1,1957 +1,1956 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Semihalf under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "bootstrap.h" #include "fdt_platform.h" #ifdef DEBUG #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define debugf(fmt, args...) #endif #define FDT_CWD_LEN 256 #define FDT_MAX_DEPTH 12 #define FDT_PROP_SEP " = " #define COPYOUT(s,d,l) archsw.arch_copyout(s, d, l) #define COPYIN(s,d,l) archsw.arch_copyin(s, d, l) #define FDT_STATIC_DTB_SYMBOL "fdt_static_dtb" #define CMD_REQUIRES_BLOB 0x01 /* Location of FDT yet to be loaded. */ /* This may be in read-only memory, so can't be manipulated directly. */ static struct fdt_header *fdt_to_load = NULL; /* Location of FDT on heap. */ /* This is the copy we actually manipulate. */ static struct fdt_header *fdtp = NULL; /* Size of FDT blob */ static size_t fdtp_size = 0; /* Have we loaded all the needed overlays */ static int fdt_overlays_applied = 0; static int fdt_load_dtb(vm_offset_t va); static void fdt_print_overlay_load_error(int err, const char *filename); static int fdt_check_overlay_compatible(void *base_fdt, void *overlay_fdt); static int fdt_cmd_nyi(int argc, char *argv[]); static int fdt_load_dtb_overlays_string(const char * filenames); static int fdt_cmd_addr(int argc, char *argv[]); static int fdt_cmd_mkprop(int argc, char *argv[]); static int fdt_cmd_cd(int argc, char *argv[]); static int fdt_cmd_hdr(int argc, char *argv[]); static int fdt_cmd_ls(int argc, char *argv[]); static int fdt_cmd_prop(int argc, char *argv[]); static int fdt_cmd_pwd(int argc, char *argv[]); static int fdt_cmd_rm(int argc, char *argv[]); static int fdt_cmd_mknode(int argc, char *argv[]); static int fdt_cmd_mres(int argc, char *argv[]); typedef int cmdf_t(int, char *[]); struct cmdtab { const char *name; cmdf_t *handler; int flags; }; static const struct cmdtab commands[] = { { "addr", &fdt_cmd_addr, 0 }, { "alias", &fdt_cmd_nyi, 0 }, { "cd", &fdt_cmd_cd, CMD_REQUIRES_BLOB }, { "header", &fdt_cmd_hdr, CMD_REQUIRES_BLOB }, { "ls", &fdt_cmd_ls, CMD_REQUIRES_BLOB }, { "mknode", &fdt_cmd_mknode, CMD_REQUIRES_BLOB }, { "mkprop", &fdt_cmd_mkprop, CMD_REQUIRES_BLOB }, { "mres", &fdt_cmd_mres, CMD_REQUIRES_BLOB }, { "prop", &fdt_cmd_prop, CMD_REQUIRES_BLOB }, { "pwd", &fdt_cmd_pwd, CMD_REQUIRES_BLOB }, { "rm", &fdt_cmd_rm, CMD_REQUIRES_BLOB }, { NULL, NULL } }; static char cwd[FDT_CWD_LEN] = "/"; static vm_offset_t fdt_find_static_dtb() { Elf_Ehdr *ehdr; Elf_Shdr *shdr; Elf_Sym sym; vm_offset_t strtab, symtab, fdt_start; uint64_t offs; struct preloaded_file *kfp; struct file_metadata *md; char *strp; int i, sym_count; debugf("fdt_find_static_dtb()\n"); sym_count = symtab = strtab = 0; strp = NULL; offs = __elfN(relocation_offset); kfp = file_findfile(NULL, NULL); if (kfp == NULL) return (0); /* Locate the dynamic symbols and strtab. */ md = file_findmetadata(kfp, MODINFOMD_ELFHDR); if (md == NULL) return (0); ehdr = (Elf_Ehdr *)md->md_data; md = file_findmetadata(kfp, MODINFOMD_SHDR); if (md == NULL) return (0); shdr = (Elf_Shdr *)md->md_data; for (i = 0; i < ehdr->e_shnum; ++i) { if (shdr[i].sh_type == SHT_DYNSYM && symtab == 0) { symtab = shdr[i].sh_addr + offs; sym_count = shdr[i].sh_size / sizeof(Elf_Sym); } else if (shdr[i].sh_type == SHT_STRTAB && strtab == 0) { strtab = shdr[i].sh_addr + offs; } } /* * The most efficient way to find a symbol would be to calculate a * hash, find proper bucket and chain, and thus find a symbol. * However, that would involve code duplication (e.g. for hash * function). So we're using simpler and a bit slower way: we're * iterating through symbols, searching for the one which name is * 'equal' to 'fdt_static_dtb'. To speed up the process a little bit, * we are eliminating symbols type of which is not STT_NOTYPE, or(and) * those which binding attribute is not STB_GLOBAL. */ fdt_start = 0; while (sym_count > 0 && fdt_start == 0) { COPYOUT(symtab, &sym, sizeof(sym)); symtab += sizeof(sym); --sym_count; if (ELF_ST_BIND(sym.st_info) != STB_GLOBAL || ELF_ST_TYPE(sym.st_info) != STT_NOTYPE) continue; strp = strdupout(strtab + sym.st_name); if (strcmp(strp, FDT_STATIC_DTB_SYMBOL) == 0) fdt_start = (vm_offset_t)sym.st_value + offs; free(strp); } return (fdt_start); } static int fdt_load_dtb(vm_offset_t va) { struct fdt_header header; int err; debugf("fdt_load_dtb(0x%08jx)\n", (uintmax_t)va); COPYOUT(va, &header, sizeof(header)); err = fdt_check_header(&header); if (err < 0) { if (err == -FDT_ERR_BADVERSION) { snprintf(command_errbuf, sizeof(command_errbuf), "incompatible blob version: %d, should be: %d", fdt_version(fdtp), FDT_LAST_SUPPORTED_VERSION); } else { snprintf(command_errbuf, sizeof(command_errbuf), "error validating blob: %s", fdt_strerror(err)); } return (1); } /* * Release previous blob */ if (fdtp) free(fdtp); fdtp_size = fdt_totalsize(&header); fdtp = malloc(fdtp_size); if (fdtp == NULL) { command_errmsg = "can't allocate memory for device tree copy"; return (1); } COPYOUT(va, fdtp, fdtp_size); debugf("DTB blob found at 0x%jx, size: 0x%jx\n", (uintmax_t)va, (uintmax_t)fdtp_size); return (0); } int fdt_load_dtb_addr(struct fdt_header *header) { int err; debugf("fdt_load_dtb_addr(%p)\n", header); fdtp_size = fdt_totalsize(header); err = fdt_check_header(header); if (err < 0) { snprintf(command_errbuf, sizeof(command_errbuf), "error validating blob: %s", fdt_strerror(err)); return (err); } free(fdtp); if ((fdtp = malloc(fdtp_size)) == NULL) { command_errmsg = "can't allocate memory for device tree copy"; return (1); } bcopy(header, fdtp, fdtp_size); return (0); } int fdt_load_dtb_file(const char * filename) { struct preloaded_file *bfp, *oldbfp; int err; debugf("fdt_load_dtb_file(%s)\n", filename); oldbfp = file_findfile(NULL, "dtb"); /* Attempt to load and validate a new dtb from a file. */ if ((bfp = file_loadraw(filename, "dtb", 1)) == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "failed to load file '%s'", filename); return (1); } if ((err = fdt_load_dtb(bfp->f_addr)) != 0) { file_discard(bfp); return (err); } /* A new dtb was validated, discard any previous file. */ if (oldbfp) file_discard(oldbfp); return (0); } static int fdt_load_dtb_overlay(const char * filename) { struct preloaded_file *bfp; struct fdt_header header; int err; debugf("fdt_load_dtb_overlay(%s)\n", filename); /* Attempt to load and validate a new dtb from a file. FDT_ERR_NOTFOUND * is normally a libfdt error code, but libfdt would actually return * -FDT_ERR_NOTFOUND. We re-purpose the error code here to convey a * similar meaning: the file itself was not found, which can still be * considered an error dealing with FDT pieces. */ if ((bfp = file_loadraw(filename, "dtbo", 1)) == NULL) return (FDT_ERR_NOTFOUND); COPYOUT(bfp->f_addr, &header, sizeof(header)); err = fdt_check_header(&header); if (err < 0) { file_discard(bfp); return (err); } return (0); } static void fdt_print_overlay_load_error(int err, const char *filename) { switch (err) { case FDT_ERR_NOTFOUND: printf("%s: failed to load file\n", filename); break; case -FDT_ERR_BADVERSION: printf("%s: incompatible blob version: %d, should be: %d\n", filename, fdt_version(fdtp), FDT_LAST_SUPPORTED_VERSION); break; default: /* libfdt errs are negative */ if (err < 0) printf("%s: error validating blob: %s\n", filename, fdt_strerror(err)); else printf("%s: unknown load error\n", filename); break; } } static int fdt_load_dtb_overlays_string(const char * filenames) { char *names; char *name, *name_ext; char *comaptr; int err, namesz; debugf("fdt_load_dtb_overlays_string(%s)\n", filenames); names = strdup(filenames); if (names == NULL) return (1); name = names; do { comaptr = strchr(name, ','); if (comaptr) *comaptr = '\0'; err = fdt_load_dtb_overlay(name); if (err == FDT_ERR_NOTFOUND) { /* Allocate enough to append ".dtbo" */ namesz = strlen(name) + 6; name_ext = malloc(namesz); if (name_ext == NULL) { fdt_print_overlay_load_error(err, name); name = comaptr + 1; continue; } snprintf(name_ext, namesz, "%s.dtbo", name); err = fdt_load_dtb_overlay(name_ext); free(name_ext); } /* Catch error with either initial load or fallback load */ if (err != 0) fdt_print_overlay_load_error(err, name); name = comaptr + 1; } while(comaptr); free(names); return (0); } /* * fdt_check_overlay_compatible - check that the overlay_fdt is compatible with * base_fdt before we attempt to apply it. It will need to re-calculate offsets * in the base every time, rather than trying to cache them earlier in the * process, because the overlay application process can/will invalidate a lot of * offsets. */ static int fdt_check_overlay_compatible(void *base_fdt, void *overlay_fdt) { const char *compat; int compat_len, ocompat_len; int oroot_offset, root_offset; int slidx, sllen; oroot_offset = fdt_path_offset(overlay_fdt, "/"); if (oroot_offset < 0) return (oroot_offset); /* * If /compatible in the overlay does not exist or if it is empty, then * we're automatically compatible. We do this for the sake of rapid * overlay development for overlays that aren't intended to be deployed. * The user assumes the risk of using an overlay without /compatible. */ if (fdt_get_property(overlay_fdt, oroot_offset, "compatible", &ocompat_len) == NULL || ocompat_len == 0) return (0); root_offset = fdt_path_offset(base_fdt, "/"); if (root_offset < 0) return (root_offset); /* * However, an empty or missing /compatible on the base is an error, * because allowing this offers no advantages. */ if (fdt_get_property(base_fdt, root_offset, "compatible", &compat_len) == NULL) return (compat_len); else if(compat_len == 0) return (1); slidx = 0; compat = fdt_stringlist_get(overlay_fdt, oroot_offset, "compatible", slidx, &sllen); while (compat != NULL) { if (fdt_stringlist_search(base_fdt, root_offset, "compatible", compat) >= 0) return (0); ++slidx; compat = fdt_stringlist_get(overlay_fdt, oroot_offset, "compatible", slidx, &sllen); }; /* We've exhausted the overlay's /compatible property... no match */ return (1); } /* * Returns the number of overlays successfully applied */ int fdt_apply_overlays() { struct preloaded_file *fp; size_t max_overlay_size, next_fdtp_size; size_t current_fdtp_size; void *current_fdtp; void *next_fdtp; void *overlay; int overlays_applied, rv; if ((fdtp == NULL) || (fdtp_size == 0)) return (0); if (fdt_overlays_applied) return (0); max_overlay_size = 0; for (fp = file_findfile(NULL, "dtbo"); fp != NULL; fp = fp->f_next) { if (max_overlay_size < fp->f_size) max_overlay_size = fp->f_size; } /* Nothing to apply */ if (max_overlay_size == 0) return (0); overlay = malloc(max_overlay_size); if (overlay == NULL) { printf("failed to allocate memory for DTB blob with overlays\n"); return (0); } current_fdtp = fdtp; current_fdtp_size = fdtp_size; overlays_applied = 0; for (fp = file_findfile(NULL, "dtbo"); fp != NULL; fp = fp->f_next) { if (strcmp(fp->f_type, "dtbo") != 0) continue; COPYOUT(fp->f_addr, overlay, fp->f_size); /* Check compatible first to avoid unnecessary allocation */ rv = fdt_check_overlay_compatible(current_fdtp, overlay); if (rv != 0) { printf("DTB overlay '%s' not compatible\n", fp->f_name); continue; } printf("applying DTB overlay '%s'\n", fp->f_name); next_fdtp_size = current_fdtp_size + fp->f_size; next_fdtp = malloc(next_fdtp_size); if (next_fdtp == NULL) { /* * Output warning, then move on to applying other * overlays in case this one is simply too large. */ printf("failed to allocate memory for overlay base\n"); continue; } rv = fdt_open_into(current_fdtp, next_fdtp, next_fdtp_size); if (rv != 0) { free(next_fdtp); printf("failed to open base dtb into overlay base\n"); continue; } /* Both overlay and next_fdtp may be modified in place */ rv = fdt_overlay_apply(next_fdtp, overlay); if (rv == 0) { /* Rotate next -> current */ if (current_fdtp != fdtp) free(current_fdtp); current_fdtp = next_fdtp; fdt_pack(current_fdtp); current_fdtp_size = fdt_totalsize(current_fdtp); overlays_applied++; } else { /* * Assume here that the base we tried to apply on is * either trashed or in an inconsistent state. Trying to * load it might work, but it's better to discard it and * play it safe. */ free(next_fdtp); printf("failed to apply overlay: %s\n", fdt_strerror(rv)); } } /* We could have failed to apply all overlays; then we do nothing */ if (current_fdtp != fdtp) { free(fdtp); fdtp = current_fdtp; fdtp_size = current_fdtp_size; } free(overlay); fdt_overlays_applied = 1; return (overlays_applied); } int fdt_pad_dtb(size_t padding) { void *padded_fdtp; size_t padded_fdtp_size; padded_fdtp_size = fdtp_size + padding; padded_fdtp = malloc(padded_fdtp_size); if (padded_fdtp == NULL) return (1); if (fdt_open_into(fdtp, padded_fdtp, padded_fdtp_size) != 0) { free(padded_fdtp); return (1); } fdtp = padded_fdtp; fdtp_size = padded_fdtp_size; return (0); } int fdt_is_setup(void) { if (fdtp != NULL) return (1); return (0); } int fdt_setup_fdtp() { struct preloaded_file *bfp; vm_offset_t va; debugf("fdt_setup_fdtp()\n"); /* If we already loaded a file, use it. */ if ((bfp = file_findfile(NULL, "dtb")) != NULL) { if (fdt_load_dtb(bfp->f_addr) == 0) { printf("Using DTB from loaded file '%s'.\n", bfp->f_name); fdt_platform_load_overlays(); return (0); } } /* If we were given the address of a valid blob in memory, use it. */ if (fdt_to_load != NULL) { if (fdt_load_dtb_addr(fdt_to_load) == 0) { printf("Using DTB from memory address %p.\n", fdt_to_load); fdt_platform_load_overlays(); return (0); } } if (fdt_platform_load_dtb() == 0) { fdt_platform_load_overlays(); return (0); } /* If there is a dtb compiled into the kernel, use it. */ if ((va = fdt_find_static_dtb()) != 0) { if (fdt_load_dtb(va) == 0) { printf("Using DTB compiled into kernel.\n"); return (0); } } command_errmsg = "No device tree blob found!\n"; return (1); } #define fdt_strtovect(str, cellbuf, lim, cellsize) _fdt_strtovect((str), \ (cellbuf), (lim), (cellsize), 0); /* Force using base 16 */ #define fdt_strtovectx(str, cellbuf, lim, cellsize) _fdt_strtovect((str), \ (cellbuf), (lim), (cellsize), 16); static int _fdt_strtovect(const char *str, void *cellbuf, int lim, unsigned char cellsize, uint8_t base) { const char *buf = str; const char *end = str + strlen(str) - 2; uint32_t *u32buf = NULL; uint8_t *u8buf = NULL; int cnt = 0; if (cellsize == sizeof(uint32_t)) u32buf = (uint32_t *)cellbuf; else u8buf = (uint8_t *)cellbuf; if (lim == 0) return (0); while (buf < end) { /* Skip white whitespace(s)/separators */ while (!isxdigit(*buf) && buf < end) buf++; if (u32buf != NULL) u32buf[cnt] = cpu_to_fdt32((uint32_t)strtol(buf, NULL, base)); else u8buf[cnt] = (uint8_t)strtol(buf, NULL, base); if (cnt + 1 <= lim - 1) cnt++; else break; buf++; /* Find another number */ while ((isxdigit(*buf) || *buf == 'x') && buf < end) buf++; } return (cnt); } void fdt_fixup_ethernet(const char *str, char *ethstr, int len) { uint8_t tmp_addr[6]; /* Convert macaddr string into a vector of uints */ fdt_strtovectx(str, &tmp_addr, 6, sizeof(uint8_t)); /* Set actual property to a value from vect */ fdt_setprop(fdtp, fdt_path_offset(fdtp, ethstr), "local-mac-address", &tmp_addr, 6 * sizeof(uint8_t)); } void fdt_fixup_cpubusfreqs(unsigned long cpufreq, unsigned long busfreq) { int lo, o = 0, o2, maxo = 0, depth; const uint32_t zero = 0; /* We want to modify every subnode of /cpus */ o = fdt_path_offset(fdtp, "/cpus"); if (o < 0) return; /* maxo should contain offset of node next to /cpus */ depth = 0; maxo = o; while (depth != -1) maxo = fdt_next_node(fdtp, maxo, &depth); /* Find CPU frequency properties */ o = fdt_node_offset_by_prop_value(fdtp, o, "clock-frequency", &zero, sizeof(uint32_t)); o2 = fdt_node_offset_by_prop_value(fdtp, o, "bus-frequency", &zero, sizeof(uint32_t)); lo = MIN(o, o2); while (o != -FDT_ERR_NOTFOUND && o2 != -FDT_ERR_NOTFOUND) { o = fdt_node_offset_by_prop_value(fdtp, lo, "clock-frequency", &zero, sizeof(uint32_t)); o2 = fdt_node_offset_by_prop_value(fdtp, lo, "bus-frequency", &zero, sizeof(uint32_t)); /* We're only interested in /cpus subnode(s) */ if (lo > maxo) break; fdt_setprop_inplace_cell(fdtp, lo, "clock-frequency", (uint32_t)cpufreq); fdt_setprop_inplace_cell(fdtp, lo, "bus-frequency", (uint32_t)busfreq); lo = MIN(o, o2); } } #ifdef notyet static int fdt_reg_valid(uint32_t *reg, int len, int addr_cells, int size_cells) { int cells_in_tuple, i, tuples, tuple_size; uint32_t cur_start, cur_size; cells_in_tuple = (addr_cells + size_cells); tuple_size = cells_in_tuple * sizeof(uint32_t); tuples = len / tuple_size; if (tuples == 0) return (EINVAL); for (i = 0; i < tuples; i++) { if (addr_cells == 2) cur_start = fdt64_to_cpu(reg[i * cells_in_tuple]); else cur_start = fdt32_to_cpu(reg[i * cells_in_tuple]); if (size_cells == 2) cur_size = fdt64_to_cpu(reg[i * cells_in_tuple + 2]); else cur_size = fdt32_to_cpu(reg[i * cells_in_tuple + 1]); if (cur_size == 0) return (EINVAL); debugf(" reg#%d (start: 0x%0x size: 0x%0x) valid!\n", i, cur_start, cur_size); } return (0); } #endif void fdt_fixup_memory(struct fdt_mem_region *region, size_t num) { struct fdt_mem_region *curmr; uint32_t addr_cells, size_cells; uint32_t *addr_cellsp, *size_cellsp; int err, i, len, memory, root; size_t realmrno; uint8_t *buf, *sb; uint64_t rstart, rsize; int reserved; root = fdt_path_offset(fdtp, "/"); if (root < 0) { sprintf(command_errbuf, "Could not find root node !"); return; } memory = fdt_path_offset(fdtp, "/memory"); if (memory <= 0) { /* Create proper '/memory' node. */ memory = fdt_add_subnode(fdtp, root, "memory"); if (memory <= 0) { snprintf(command_errbuf, sizeof(command_errbuf), "Could not fixup '/memory' " "node, error code : %d!\n", memory); return; } err = fdt_setprop(fdtp, memory, "device_type", "memory", sizeof("memory")); if (err < 0) return; } addr_cellsp = (uint32_t *)fdt_getprop(fdtp, root, "#address-cells", NULL); size_cellsp = (uint32_t *)fdt_getprop(fdtp, root, "#size-cells", NULL); if (addr_cellsp == NULL || size_cellsp == NULL) { snprintf(command_errbuf, sizeof(command_errbuf), "Could not fixup '/memory' node : " "%s %s property not found in root node!\n", (!addr_cellsp) ? "#address-cells" : "", (!size_cellsp) ? "#size-cells" : ""); return; } addr_cells = fdt32_to_cpu(*addr_cellsp); size_cells = fdt32_to_cpu(*size_cellsp); /* * Convert memreserve data to memreserve property * Check if property already exists */ reserved = fdt_num_mem_rsv(fdtp); if (reserved && (fdt_getprop(fdtp, root, "memreserve", NULL) == NULL)) { len = (addr_cells + size_cells) * reserved * sizeof(uint32_t); sb = buf = (uint8_t *)malloc(len); if (!buf) return; bzero(buf, len); for (i = 0; i < reserved; i++) { if (fdt_get_mem_rsv(fdtp, i, &rstart, &rsize)) break; if (rsize) { /* Ensure endianness, and put cells into a buffer */ if (addr_cells == 2) *(uint64_t *)buf = cpu_to_fdt64(rstart); else *(uint32_t *)buf = cpu_to_fdt32(rstart); buf += sizeof(uint32_t) * addr_cells; if (size_cells == 2) *(uint64_t *)buf = cpu_to_fdt64(rsize); else *(uint32_t *)buf = cpu_to_fdt32(rsize); buf += sizeof(uint32_t) * size_cells; } } /* Set property */ if ((err = fdt_setprop(fdtp, root, "memreserve", sb, len)) < 0) printf("Could not fixup 'memreserve' property.\n"); free(sb); } /* Count valid memory regions entries in sysinfo. */ realmrno = num; for (i = 0; i < num; i++) if (region[i].start == 0 && region[i].size == 0) realmrno--; if (realmrno == 0) { sprintf(command_errbuf, "Could not fixup '/memory' node : " "sysinfo doesn't contain valid memory regions info!\n"); return; } len = (addr_cells + size_cells) * realmrno * sizeof(uint32_t); sb = buf = (uint8_t *)malloc(len); if (!buf) return; bzero(buf, len); for (i = 0; i < num; i++) { curmr = ®ion[i]; if (curmr->size != 0) { /* Ensure endianness, and put cells into a buffer */ if (addr_cells == 2) *(uint64_t *)buf = cpu_to_fdt64(curmr->start); else *(uint32_t *)buf = cpu_to_fdt32(curmr->start); buf += sizeof(uint32_t) * addr_cells; if (size_cells == 2) *(uint64_t *)buf = cpu_to_fdt64(curmr->size); else *(uint32_t *)buf = cpu_to_fdt32(curmr->size); buf += sizeof(uint32_t) * size_cells; } } /* Set property */ if ((err = fdt_setprop(fdtp, memory, "reg", sb, len)) < 0) sprintf(command_errbuf, "Could not fixup '/memory' node.\n"); free(sb); } void fdt_fixup_stdout(const char *str) { char *ptr; int len, no, sero; const struct fdt_property *prop; char *tmp[10]; ptr = (char *)str + strlen(str) - 1; while (ptr > str && isdigit(*(str - 1))) str--; if (ptr == str) return; no = fdt_path_offset(fdtp, "/chosen"); if (no < 0) return; prop = fdt_get_property(fdtp, no, "stdout", &len); /* If /chosen/stdout does not extist, create it */ if (prop == NULL || (prop != NULL && len == 0)) { bzero(tmp, 10 * sizeof(char)); strcpy((char *)&tmp, "serial"); if (strlen(ptr) > 3) /* Serial number too long */ return; strncpy((char *)tmp + 6, ptr, 3); sero = fdt_path_offset(fdtp, (const char *)tmp); if (sero < 0) /* * If serial device we're trying to assign * stdout to doesn't exist in DT -- return. */ return; fdt_setprop(fdtp, no, "stdout", &tmp, strlen((char *)&tmp) + 1); fdt_setprop(fdtp, no, "stdin", &tmp, strlen((char *)&tmp) + 1); } } void fdt_load_dtb_overlays(const char *extras) { const char *s; /* Any extra overlays supplied by pre-loader environment */ if (extras != NULL && *extras != '\0') { printf("Loading DTB overlays: '%s'\n", extras); fdt_load_dtb_overlays_string(extras); } /* Any overlays supplied by loader environment */ s = getenv("fdt_overlays"); if (s != NULL && *s != '\0') { printf("Loading DTB overlays: '%s'\n", s); fdt_load_dtb_overlays_string(s); } } /* * Locate the blob, fix it up and return its location. */ static int fdt_fixup(void) { int chosen; debugf("fdt_fixup()\n"); if (fdtp == NULL && fdt_setup_fdtp() != 0) return (0); /* Create /chosen node (if not exists) */ if ((chosen = fdt_subnode_offset(fdtp, 0, "chosen")) == -FDT_ERR_NOTFOUND) chosen = fdt_add_subnode(fdtp, 0, "chosen"); /* Value assigned to fixup-applied does not matter. */ if (fdt_getprop(fdtp, chosen, "fixup-applied", NULL)) return (1); fdt_platform_fixups(); /* * Re-fetch the /chosen subnode; our fixups may apply overlays or add * nodes/properties that invalidate the offset we grabbed or created * above, so we can no longer trust it. */ chosen = fdt_subnode_offset(fdtp, 0, "chosen"); fdt_setprop(fdtp, chosen, "fixup-applied", NULL, 0); return (1); } /* * Copy DTB blob to specified location and return size */ int fdt_copy(vm_offset_t va) { int err; debugf("fdt_copy va 0x%08x\n", va); if (fdtp == NULL) { err = fdt_setup_fdtp(); if (err) { printf("No valid device tree blob found!\n"); return (0); } } if (fdt_fixup() == 0) return (0); COPYIN(fdtp, va, fdtp_size); return (fdtp_size); } int command_fdt_internal(int argc, char *argv[]) { cmdf_t *cmdh; int flags; int i, err; if (argc < 2) { command_errmsg = "usage is 'fdt []"; return (CMD_ERROR); } /* * Validate fdt . */ i = 0; cmdh = NULL; while (!(commands[i].name == NULL)) { if (strcmp(argv[1], commands[i].name) == 0) { /* found it */ cmdh = commands[i].handler; flags = commands[i].flags; break; } i++; } if (cmdh == NULL) { command_errmsg = "unknown command"; return (CMD_ERROR); } if (flags & CMD_REQUIRES_BLOB) { /* * Check if uboot env vars were parsed already. If not, do it now. */ if (fdt_fixup() == 0) return (CMD_ERROR); } /* * Call command handler. */ err = (*cmdh)(argc, argv); return (err); } static int fdt_cmd_addr(int argc, char *argv[]) { struct preloaded_file *fp; struct fdt_header *hdr; const char *addr; char *cp; fdt_to_load = NULL; if (argc > 2) addr = argv[2]; else { sprintf(command_errbuf, "no address specified"); return (CMD_ERROR); } hdr = (struct fdt_header *)strtoul(addr, &cp, 16); if (cp == addr) { snprintf(command_errbuf, sizeof(command_errbuf), "Invalid address: %s", addr); return (CMD_ERROR); } while ((fp = file_findfile(NULL, "dtb")) != NULL) { file_discard(fp); } fdt_to_load = hdr; return (CMD_OK); } static int fdt_cmd_cd(int argc, char *argv[]) { char *path; char tmp[FDT_CWD_LEN]; int len, o; path = (argc > 2) ? argv[2] : "/"; if (path[0] == '/') { len = strlen(path); if (len >= FDT_CWD_LEN) goto fail; } else { /* Handle path specification relative to cwd */ len = strlen(cwd) + strlen(path) + 1; if (len >= FDT_CWD_LEN) goto fail; strcpy(tmp, cwd); strcat(tmp, "/"); strcat(tmp, path); path = tmp; } o = fdt_path_offset(fdtp, path); if (o < 0) { snprintf(command_errbuf, sizeof(command_errbuf), "could not find node: '%s'", path); return (CMD_ERROR); } strcpy(cwd, path); return (CMD_OK); fail: snprintf(command_errbuf, sizeof(command_errbuf), "path too long: %d, max allowed: %d", len, FDT_CWD_LEN - 1); return (CMD_ERROR); } static int fdt_cmd_hdr(int argc __unused, char *argv[] __unused) { char line[80]; int ver; if (fdtp == NULL) { command_errmsg = "no device tree blob pointer?!"; return (CMD_ERROR); } ver = fdt_version(fdtp); pager_open(); sprintf(line, "\nFlattened device tree header (%p):\n", fdtp); if (pager_output(line)) goto out; sprintf(line, " magic = 0x%08x\n", fdt_magic(fdtp)); if (pager_output(line)) goto out; sprintf(line, " size = %d\n", fdt_totalsize(fdtp)); if (pager_output(line)) goto out; sprintf(line, " off_dt_struct = 0x%08x\n", fdt_off_dt_struct(fdtp)); if (pager_output(line)) goto out; sprintf(line, " off_dt_strings = 0x%08x\n", fdt_off_dt_strings(fdtp)); if (pager_output(line)) goto out; sprintf(line, " off_mem_rsvmap = 0x%08x\n", fdt_off_mem_rsvmap(fdtp)); if (pager_output(line)) goto out; sprintf(line, " version = %d\n", ver); if (pager_output(line)) goto out; sprintf(line, " last compatible version = %d\n", fdt_last_comp_version(fdtp)); if (pager_output(line)) goto out; if (ver >= 2) { sprintf(line, " boot_cpuid = %d\n", fdt_boot_cpuid_phys(fdtp)); if (pager_output(line)) goto out; } if (ver >= 3) { sprintf(line, " size_dt_strings = %d\n", fdt_size_dt_strings(fdtp)); if (pager_output(line)) goto out; } if (ver >= 17) { sprintf(line, " size_dt_struct = %d\n", fdt_size_dt_struct(fdtp)); if (pager_output(line)) goto out; } out: pager_close(); return (CMD_OK); } static int fdt_cmd_ls(int argc, char *argv[]) { const char *prevname[FDT_MAX_DEPTH] = { NULL }; const char *name; char *path; int i, o, depth; path = (argc > 2) ? argv[2] : NULL; if (path == NULL) path = cwd; o = fdt_path_offset(fdtp, path); if (o < 0) { snprintf(command_errbuf, sizeof(command_errbuf), "could not find node: '%s'", path); return (CMD_ERROR); } for (depth = 0; (o >= 0) && (depth >= 0); o = fdt_next_node(fdtp, o, &depth)) { name = fdt_get_name(fdtp, o, NULL); if (depth > FDT_MAX_DEPTH) { printf("max depth exceeded: %d\n", depth); continue; } prevname[depth] = name; /* Skip root (i = 1) when printing devices */ for (i = 1; i <= depth; i++) { if (prevname[i] == NULL) break; if (strcmp(cwd, "/") == 0) printf("/"); printf("%s", prevname[i]); } printf("\n"); } return (CMD_OK); } static __inline int isprint(int c) { return (c >= ' ' && c <= 0x7e); } static int fdt_isprint(const void *data, int len, int *count) { const char *d; char ch; int yesno, i; if (len == 0) return (0); d = (const char *)data; if (d[len - 1] != '\0') return (0); *count = 0; yesno = 1; for (i = 0; i < len; i++) { ch = *(d + i); if (isprint(ch) || (ch == '\0' && i > 0)) { /* Count strings */ if (ch == '\0') (*count)++; continue; } yesno = 0; break; } return (yesno); } static int fdt_data_str(const void *data, int len, int count, char **buf) { char *b, *tmp; const char *d; int buf_len, i, l; /* * Calculate the length for the string and allocate memory. * * Note that 'len' already includes at least one terminator. */ buf_len = len; if (count > 1) { /* * Each token had already a terminator buried in 'len', but we * only need one eventually, don't count space for these. */ buf_len -= count - 1; /* Each consecutive token requires a ", " separator. */ buf_len += count * 2; } /* Add some space for surrounding double quotes. */ buf_len += count * 2; /* Note that string being put in 'tmp' may be as big as 'buf_len'. */ b = (char *)malloc(buf_len); tmp = (char *)malloc(buf_len); if (b == NULL) goto error; if (tmp == NULL) { free(b); goto error; } b[0] = '\0'; /* * Now that we have space, format the string. */ i = 0; do { d = (const char *)data + i; l = strlen(d) + 1; sprintf(tmp, "\"%s\"%s", d, (i + l) < len ? ", " : ""); strcat(b, tmp); i += l; } while (i < len); *buf = b; free(tmp); return (0); error: return (1); } static int fdt_data_cell(const void *data, int len, char **buf) { char *b, *tmp; const uint32_t *c; int count, i, l; /* Number of cells */ count = len / 4; /* * Calculate the length for the string and allocate memory. */ /* Each byte translates to 2 output characters */ l = len * 2; if (count > 1) { /* Each consecutive cell requires a " " separator. */ l += (count - 1) * 1; } /* Each cell will have a "0x" prefix */ l += count * 2; /* Space for surrounding <> and terminator */ l += 3; b = (char *)malloc(l); tmp = (char *)malloc(l); if (b == NULL) goto error; if (tmp == NULL) { free(b); goto error; } b[0] = '\0'; strcat(b, "<"); for (i = 0; i < len; i += 4) { c = (const uint32_t *)((const uint8_t *)data + i); sprintf(tmp, "0x%08x%s", fdt32_to_cpu(*c), i < (len - 4) ? " " : ""); strcat(b, tmp); } strcat(b, ">"); *buf = b; free(tmp); return (0); error: return (1); } static int fdt_data_bytes(const void *data, int len, char **buf) { char *b, *tmp; const char *d; int i, l; /* * Calculate the length for the string and allocate memory. */ /* Each byte translates to 2 output characters */ l = len * 2; if (len > 1) /* Each consecutive byte requires a " " separator. */ l += (len - 1) * 1; /* Each byte will have a "0x" prefix */ l += len * 2; /* Space for surrounding [] and terminator. */ l += 3; b = (char *)malloc(l); tmp = (char *)malloc(l); if (b == NULL) goto error; if (tmp == NULL) { free(b); goto error; } b[0] = '\0'; strcat(b, "["); for (i = 0, d = data; i < len; i++) { sprintf(tmp, "0x%02x%s", d[i], i < len - 1 ? " " : ""); strcat(b, tmp); } strcat(b, "]"); *buf = b; free(tmp); return (0); error: return (1); } static int fdt_data_fmt(const void *data, int len, char **buf) { int count; if (len == 0) { *buf = NULL; return (1); } if (fdt_isprint(data, len, &count)) return (fdt_data_str(data, len, count, buf)); else if ((len % 4) == 0) return (fdt_data_cell(data, len, buf)); else return (fdt_data_bytes(data, len, buf)); } static int fdt_prop(int offset) { char *line, *buf; const struct fdt_property *prop; const char *name; const void *data; int len, rv; line = NULL; prop = fdt_offset_ptr(fdtp, offset, sizeof(*prop)); if (prop == NULL) return (1); name = fdt_string(fdtp, fdt32_to_cpu(prop->nameoff)); len = fdt32_to_cpu(prop->len); rv = 0; buf = NULL; if (len == 0) { /* Property without value */ line = (char *)malloc(strlen(name) + 2); if (line == NULL) { rv = 2; goto out2; } sprintf(line, "%s\n", name); goto out1; } /* * Process property with value */ data = prop->data; if (fdt_data_fmt(data, len, &buf) != 0) { rv = 3; goto out2; } line = (char *)malloc(strlen(name) + strlen(FDT_PROP_SEP) + strlen(buf) + 2); if (line == NULL) { sprintf(command_errbuf, "could not allocate space for string"); rv = 4; goto out2; } sprintf(line, "%s" FDT_PROP_SEP "%s\n", name, buf); out1: pager_open(); pager_output(line); pager_close(); out2: if (buf) free(buf); if (line) free(line); return (rv); } static int fdt_modprop(int nodeoff, char *propname, void *value, char mode) { uint32_t cells[100]; const char *buf; int len, rv; const struct fdt_property *p; p = fdt_get_property(fdtp, nodeoff, propname, NULL); if (p != NULL) { if (mode == 1) { /* Adding inexistant value in mode 1 is forbidden */ sprintf(command_errbuf, "property already exists!"); return (CMD_ERROR); } } else if (mode == 0) { sprintf(command_errbuf, "property does not exist!"); return (CMD_ERROR); } rv = 0; buf = value; switch (*buf) { case '&': /* phandles */ break; case '<': /* Data cells */ len = fdt_strtovect(buf, (void *)&cells, 100, sizeof(uint32_t)); rv = fdt_setprop(fdtp, nodeoff, propname, &cells, len * sizeof(uint32_t)); break; case '[': /* Data bytes */ len = fdt_strtovect(buf, (void *)&cells, 100, sizeof(uint8_t)); rv = fdt_setprop(fdtp, nodeoff, propname, &cells, len * sizeof(uint8_t)); break; case '"': default: /* Default -- string */ rv = fdt_setprop_string(fdtp, nodeoff, propname, value); break; } if (rv != 0) { if (rv == -FDT_ERR_NOSPACE) sprintf(command_errbuf, "Device tree blob is too small!\n"); else sprintf(command_errbuf, "Could not add/modify property!\n"); } return (rv); } /* Merge strings from argv into a single string */ static int fdt_merge_strings(int argc, char *argv[], int start, char **buffer) { char *buf; int i, idx, sz; *buffer = NULL; sz = 0; for (i = start; i < argc; i++) sz += strlen(argv[i]); /* Additional bytes for whitespaces between args */ sz += argc - start; buf = (char *)malloc(sizeof(char) * sz); if (buf == NULL) { sprintf(command_errbuf, "could not allocate space " "for string"); return (1); } bzero(buf, sizeof(char) * sz); idx = 0; for (i = start, idx = 0; i < argc; i++) { strcpy(buf + idx, argv[i]); idx += strlen(argv[i]); buf[idx] = ' '; idx++; } buf[sz - 1] = '\0'; *buffer = buf; return (0); } /* Extract offset and name of node/property from a given path */ static int fdt_extract_nameloc(char **pathp, char **namep, int *nodeoff) { int o; char *path = *pathp, *name = NULL, *subpath = NULL; subpath = strrchr(path, '/'); if (subpath == NULL) { o = fdt_path_offset(fdtp, cwd); name = path; path = (char *)&cwd; } else { *subpath = '\0'; if (strlen(path) == 0) path = cwd; name = subpath + 1; o = fdt_path_offset(fdtp, path); } if (strlen(name) == 0) { sprintf(command_errbuf, "name not specified"); return (1); } if (o < 0) { snprintf(command_errbuf, sizeof(command_errbuf), "could not find node: '%s'", path); return (1); } *namep = name; *nodeoff = o; *pathp = path; return (0); } static int fdt_cmd_prop(int argc, char *argv[]) { char *path, *propname, *value; int o, next, depth, rv; uint32_t tag; path = (argc > 2) ? argv[2] : NULL; value = NULL; if (argc > 3) { /* Merge property value strings into one */ if (fdt_merge_strings(argc, argv, 3, &value) != 0) return (CMD_ERROR); } else value = NULL; if (path == NULL) path = cwd; rv = CMD_OK; if (value) { /* If value is specified -- try to modify prop. */ if (fdt_extract_nameloc(&path, &propname, &o) != 0) return (CMD_ERROR); rv = fdt_modprop(o, propname, value, 0); if (rv) return (CMD_ERROR); return (CMD_OK); } /* User wants to display properties */ o = fdt_path_offset(fdtp, path); if (o < 0) { snprintf(command_errbuf, sizeof(command_errbuf), "could not find node: '%s'", path); rv = CMD_ERROR; goto out; } depth = 0; while (depth >= 0) { tag = fdt_next_tag(fdtp, o, &next); switch (tag) { case FDT_NOP: break; case FDT_PROP: if (depth > 1) /* Don't process properties of nested nodes */ break; if (fdt_prop(o) != 0) { sprintf(command_errbuf, "could not process " "property"); rv = CMD_ERROR; goto out; } break; case FDT_BEGIN_NODE: depth++; if (depth > FDT_MAX_DEPTH) { printf("warning: nesting too deep: %d\n", depth); goto out; } break; case FDT_END_NODE: depth--; if (depth == 0) /* * This is the end of our starting node, force * the loop finish. */ depth--; break; } o = next; } out: return (rv); } static int fdt_cmd_mkprop(int argc, char *argv[]) { int o; char *path, *propname, *value; path = (argc > 2) ? argv[2] : NULL; value = NULL; if (argc > 3) { /* Merge property value strings into one */ if (fdt_merge_strings(argc, argv, 3, &value) != 0) return (CMD_ERROR); } else value = NULL; if (fdt_extract_nameloc(&path, &propname, &o) != 0) return (CMD_ERROR); if (fdt_modprop(o, propname, value, 1)) return (CMD_ERROR); return (CMD_OK); } static int fdt_cmd_rm(int argc, char *argv[]) { int o, rv; char *path = NULL, *propname; if (argc > 2) path = argv[2]; else { sprintf(command_errbuf, "no node/property name specified"); return (CMD_ERROR); } o = fdt_path_offset(fdtp, path); if (o < 0) { /* If node not found -- try to find & delete property */ if (fdt_extract_nameloc(&path, &propname, &o) != 0) return (CMD_ERROR); if ((rv = fdt_delprop(fdtp, o, propname)) != 0) { snprintf(command_errbuf, sizeof(command_errbuf), "could not delete %s\n", (rv == -FDT_ERR_NOTFOUND) ? "(property/node does not exist)" : ""); return (CMD_ERROR); } else return (CMD_OK); } /* If node exists -- remove node */ rv = fdt_del_node(fdtp, o); if (rv) { sprintf(command_errbuf, "could not delete node"); return (CMD_ERROR); } return (CMD_OK); } static int fdt_cmd_mknode(int argc, char *argv[]) { int o, rv; char *path = NULL, *nodename = NULL; if (argc > 2) path = argv[2]; else { sprintf(command_errbuf, "no node name specified"); return (CMD_ERROR); } if (fdt_extract_nameloc(&path, &nodename, &o) != 0) return (CMD_ERROR); rv = fdt_add_subnode(fdtp, o, nodename); if (rv < 0) { if (rv == -FDT_ERR_NOSPACE) sprintf(command_errbuf, "Device tree blob is too small!\n"); else sprintf(command_errbuf, "Could not add node!\n"); return (CMD_ERROR); } return (CMD_OK); } static int fdt_cmd_pwd(int argc, char *argv[]) { char line[FDT_CWD_LEN]; pager_open(); sprintf(line, "%s\n", cwd); pager_output(line); pager_close(); return (CMD_OK); } static int fdt_cmd_mres(int argc, char *argv[]) { uint64_t start, size; int i, total; char line[80]; pager_open(); total = fdt_num_mem_rsv(fdtp); if (total > 0) { if (pager_output("Reserved memory regions:\n")) goto out; for (i = 0; i < total; i++) { fdt_get_mem_rsv(fdtp, i, &start, &size); sprintf(line, "reg#%d: (start: 0x%jx, size: 0x%jx)\n", i, start, size); if (pager_output(line)) goto out; } } else pager_output("No reserved memory regions\n"); out: pager_close(); return (CMD_OK); } static int fdt_cmd_nyi(int argc, char *argv[]) { printf("command not yet implemented\n"); return (CMD_ERROR); } const char * fdt_devmatch_next(int *tag, int *compatlen) { const struct fdt_property *p; const struct fdt_property *status; int o, len = -1; static int depth = 0; if (fdtp == NULL) { fdt_setup_fdtp(); fdt_apply_overlays(); } if (*tag != 0) { o = *tag; /* We are at the end of the DTB */ if (o < 0) return (NULL); } else { o = fdt_path_offset(fdtp, "/"); if (o < 0) { printf("Can't find dtb\n"); return (NULL); } depth = 0; } /* Find the next node with a compatible property */ while (1) { p = NULL; if (o >= 0 && depth >= 0) { /* skip disabled nodes */ status = fdt_get_property(fdtp, o, "status", &len); if (len > 0) { if (strcmp(status->data, "disabled") == 0) { o = fdt_next_node(fdtp, o, &depth); if (o < 0) /* End of tree */ return (NULL); continue; } } p = fdt_get_property(fdtp, o, "compatible", &len); } if (p) break; o = fdt_next_node(fdtp, o, &depth); if (o < 0) /* End of tree */ return (NULL); } /* Prepare next node for next call */ o = fdt_next_node(fdtp, o, &depth); *tag = o; if (len >= 0) { *compatlen = len; return (p->data); } return (NULL); } diff --git a/stand/uboot/uboot_fdt.c b/stand/uboot/uboot_fdt.c index 4b0a85e3aec0..697aa85ea01b 100644 --- a/stand/uboot/uboot_fdt.c +++ b/stand/uboot/uboot_fdt.c @@ -1,213 +1,212 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation - * All rights reserved. * * This software was developed by Semihalf under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "glue.h" #define STR(number) #number #define STRINGIFY(number) STR(number) static int fdt_platform_load_from_ubenv(const char *var) { struct fdt_header *hdr; const char *s; char *p; s = ub_env_get(var); if (s == NULL || *s == '\0') return (1); hdr = (struct fdt_header *)strtoul(s, &p, 16); if (*p != '\0') return (1); if (fdt_load_dtb_addr(hdr) == 0) { printf("Using DTB provided by U-Boot at " "address %p.\n", hdr); return (0); } return (1); } #define FDT_DTB_PADSZ 1024 int fdt_platform_load_dtb(void) { struct fdt_header *hdr; const char *s; char *p; int rv; /* * If the U-boot environment contains a variable giving the address of a * valid blob in memory, use it. The U-boot README says the right * variable for fdt data loaded into ram is fdt_addr_r, so try that * first. Board vendors also use both fdtaddr and fdt_addr names. */ if ((rv = fdt_platform_load_from_ubenv("fdt_addr_r")) == 0) goto exit; if ((rv = fdt_platform_load_from_ubenv("fdt_addr")) == 0) goto exit; if ((rv = fdt_platform_load_from_ubenv("fdtaddr")) == 0) goto exit; rv = 1; /* * Try to get FDT filename first from loader env and then from u-boot env */ s = getenv("fdt_file"); if (s == NULL) s = ub_env_get("fdtfile"); if (s == NULL) s = ub_env_get("fdt_file"); if (s != NULL && *s != '\0') { if (fdt_load_dtb_file(s) == 0) { printf("Loaded DTB from file '%s'.\n", s); rv = 0; goto exit; } } exit: return (rv); } void fdt_platform_load_overlays(void) { fdt_load_dtb_overlays(ub_env_get("fdt_overlays")); } void fdt_platform_fixups(void) { static struct fdt_mem_region regions[UB_MAX_MR]; const char *env, *str; char *end, *ethstr; int eth_no, i, len, n; struct sys_info *si; env = NULL; eth_no = 0; ethstr = NULL; /* Apply overlays before anything else */ if (fdt_apply_overlays() > 0) fdt_pad_dtb(FDT_DTB_PADSZ); /* Acquire sys_info */ si = ub_get_sys_info(); while ((env = ub_env_enum(env)) != NULL) { if (strncmp(env, "eth", 3) == 0 && strncmp(env + (strlen(env) - 4), "addr", 4) == 0) { /* * Handle Ethernet addrs: parse uboot env eth%daddr */ if (!eth_no) { /* * Check how many chars we will need to store * maximal eth iface number. */ len = strlen(STRINGIFY(TMP_MAX_ETH)) + strlen("ethernet") + 1; /* * Reserve mem for string "ethernet" and len * chars for iface no. */ ethstr = (char *)malloc(len * sizeof(char)); bzero(ethstr, len * sizeof(char)); strcpy(ethstr, "ethernet0"); } /* Extract interface number */ i = strtol(env + 3, &end, 10); if (end == (env + 3)) /* 'ethaddr' means interface 0 address */ n = 0; else n = i; if (n > TMP_MAX_ETH) continue; str = ub_env_get(env); if (n != 0) { /* * Find the length of the interface id by * taking in to account the first 3 and * last 4 characters. */ i = strlen(env) - 7; strncpy(ethstr + 8, env + 3, i); } /* Modify blob */ fdt_fixup_ethernet(str, ethstr, len); /* Clear ethernet..XXXX.. string */ bzero(ethstr + 8, len - 8); if (n + 1 > eth_no) eth_no = n + 1; } else if (strcmp(env, "consoledev") == 0) { str = ub_env_get(env); fdt_fixup_stdout(str); } } /* Modify cpu(s) and bus clock frequenties in /cpus node [Hz] */ fdt_fixup_cpubusfreqs(si->clk_cpu, si->clk_bus); /* Extract the DRAM regions into fdt_mem_region format. */ for (i = 0, n = 0; i < si->mr_no && n < nitems(regions); i++) { if (si->mr[i].flags == MR_ATTR_DRAM) { regions[n].start = si->mr[i].start; regions[n].size = si->mr[i].size; n++; } } /* Fixup memory regions */ fdt_fixup_memory(regions, n); }