1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2020 Western Digital Corporation or its affiliates. 4 */ 5 6 #include <linux/efi.h> 7 #include <linux/libfdt.h> 8 9 #include <asm/efi.h> 10 #include <asm/sections.h> 11 #include <asm/unaligned.h> 12 13 #include "efistub.h" 14 15 typedef void __noreturn (*jump_kernel_func)(unsigned long, unsigned long); 16 17 static unsigned long hartid; 18 19 static int get_boot_hartid_from_fdt(void) 20 { 21 const void *fdt; 22 int chosen_node, len; 23 const void *prop; 24 25 fdt = get_efi_config_table(DEVICE_TREE_GUID); 26 if (!fdt) 27 return -EINVAL; 28 29 chosen_node = fdt_path_offset(fdt, "/chosen"); 30 if (chosen_node < 0) 31 return -EINVAL; 32 33 prop = fdt_getprop((void *)fdt, chosen_node, "boot-hartid", &len); 34 if (!prop) 35 return -EINVAL; 36 37 if (len == sizeof(u32)) 38 hartid = (unsigned long) fdt32_to_cpu(*(fdt32_t *)prop); 39 else if (len == sizeof(u64)) 40 hartid = (unsigned long) fdt64_to_cpu(__get_unaligned_t(fdt64_t, prop)); 41 else 42 return -EINVAL; 43 44 return 0; 45 } 46 47 static efi_status_t get_boot_hartid_from_efi(void) 48 { 49 efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID; 50 struct riscv_efi_boot_protocol *boot_protocol; 51 efi_status_t status; 52 53 status = efi_bs_call(locate_protocol, &boot_protocol_guid, NULL, 54 (void **)&boot_protocol); 55 if (status != EFI_SUCCESS) 56 return status; 57 return efi_call_proto(boot_protocol, get_boot_hartid, &hartid); 58 } 59 60 efi_status_t check_platform_features(void) 61 { 62 efi_status_t status; 63 int ret; 64 65 status = get_boot_hartid_from_efi(); 66 if (status != EFI_SUCCESS) { 67 ret = get_boot_hartid_from_fdt(); 68 if (ret) { 69 efi_err("Failed to get boot hartid!\n"); 70 return EFI_UNSUPPORTED; 71 } 72 } 73 return EFI_SUCCESS; 74 } 75 76 void __noreturn efi_enter_kernel(unsigned long entrypoint, unsigned long fdt, 77 unsigned long fdt_size) 78 { 79 unsigned long stext_offset = _start_kernel - _start; 80 unsigned long kernel_entry = entrypoint + stext_offset; 81 jump_kernel_func jump_kernel = (jump_kernel_func)kernel_entry; 82 83 /* 84 * Jump to real kernel here with following constraints. 85 * 1. MMU should be disabled. 86 * 2. a0 should contain hartid 87 * 3. a1 should DT address 88 */ 89 csr_write(CSR_SATP, 0); 90 jump_kernel(hartid, fdt); 91 } 92 93 efi_status_t handle_kernel_image(unsigned long *image_addr, 94 unsigned long *image_size, 95 unsigned long *reserve_addr, 96 unsigned long *reserve_size, 97 efi_loaded_image_t *image, 98 efi_handle_t image_handle) 99 { 100 unsigned long kernel_size = 0; 101 unsigned long preferred_addr; 102 efi_status_t status; 103 104 kernel_size = _edata - _start; 105 *image_addr = (unsigned long)_start; 106 *image_size = kernel_size + (_end - _edata); 107 108 /* 109 * RISC-V kernel maps PAGE_OFFSET virtual address to the same physical 110 * address where kernel is booted. That's why kernel should boot from 111 * as low as possible to avoid wastage of memory. Currently, dram_base 112 * is occupied by the firmware. So the preferred address for kernel to 113 * boot is next aligned address. If preferred address is not available, 114 * relocate_kernel will fall back to efi_low_alloc_above to allocate 115 * lowest possible memory region as long as the address and size meets 116 * the alignment constraints. 117 */ 118 preferred_addr = EFI_KIMG_PREFERRED_ADDRESS; 119 status = efi_relocate_kernel(image_addr, kernel_size, *image_size, 120 preferred_addr, efi_get_kimg_min_align(), 121 0x0); 122 123 if (status != EFI_SUCCESS) { 124 efi_err("Failed to relocate kernel\n"); 125 *image_size = 0; 126 } 127 return status; 128 } 129