For supporting efi runtime, several efi physical addresses fw_vendor, runtime, config tables, smbios and the whole runtime mapping info need to be used in kexec kernel. Thus introduce setup_data struct for passing these data. collect the varialbes from /sys/firmware/efi/systab and /sys/firmware/efi/runtime-map Tested on qemu+ovmf, dell laptop, lenovo laptop and HP workstation. v1->v2: HPA: use uint*_t instead of __uint*_t Simon: indention fix; fix a memory leak move offset change update to previous patch in setup header only passing setup_data when the bzImage support efi boot Vivek: export a value in bzImage probe so it can be used to check if we should pass acpi_rsdp. coding style v2->v3: code cleanup bail out if efi mm desc_version != 1 bhe: define macro for SETUP_EFI break loop if find matched string in systab. Signed-off-by: Dave Young --- kexec/arch/i386/x86-linux-setup.c | 189 +++++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 3 deletions(-) --- kexec-tools.orig/kexec/arch/i386/x86-linux-setup.c +++ kexec-tools/kexec/arch/i386/x86-linux-setup.c @@ -36,6 +36,8 @@ #include "x86-linux-setup.h" #include "../../kexec/kexec-syscall.h" +#define SETUP_EFI 4 + void init_linux_parameters(struct x86_linux_param_header *real_mode) { /* Fill in the values that are usually provided by the kernel. */ @@ -480,11 +482,192 @@ void setup_subarch(struct x86_linux_para get_bootparam(&real_mode->hardware_subarch, offset, sizeof(uint32_t)); } -static void setup_efi_info(struct x86_linux_param_header *real_mode) +struct efi_mem_descriptor { + uint32_t type; + uint32_t pad; + uint64_t phys_addr; + uint64_t virt_addr; + uint64_t num_pages; + uint64_t attribute; +}; + +struct efi_setup_data { + uint64_t fw_vendor; + uint64_t runtime; + uint64_t tables; + uint64_t smbios; + uint64_t reserved[8]; + struct efi_mem_descriptor map[0]; +}; + +struct setup_data { + uint64_t next; + uint32_t type; + uint32_t len; + uint8_t data[0]; +} __attribute__((packed)); + +static int __get_efi_value(char *line, const char *pattern, uint64_t *val) +{ + char *s, *end; + s = strstr(line, pattern); + if (s) + *val = strtoull(s + strlen(pattern), &end, 16); + + if (!s || *val == ULONG_MAX) + return 1; + return 0; +} + +static void _get_efi_value(const char *filename, + const char *pattern, uint64_t *val) +{ + FILE *fp; + char line[1024]; + int ret; + + fp = fopen(filename, "r"); + if (!fp) + return; + + while (fgets(line, sizeof(line), fp) != 0) { + ret = __get_efi_value(line, pattern, val); + if (!ret) + break; + } + + fclose(fp); +} + +static void get_efi_values(struct efi_setup_data *esd) { + _get_efi_value("/sys/firmware/efi/systab", "SMBIOS=0x", + &esd->smbios); + _get_efi_value("/sys/firmware/efi/fw_vendor", "0x", + &esd->fw_vendor); + _get_efi_value("/sys/firmware/efi/runtime", "0x", + &esd->runtime); + _get_efi_value("/sys/firmware/efi/config_table", "0x", + &esd->tables); +} + +static int get_efi_runtime_map(struct efi_setup_data **esd) +{ + DIR *dirp; + struct dirent *entry; + char filename[1024]; + struct efi_mem_descriptor md; + int nr_maps = 0; + + dirp = opendir("/sys/firmware/efi/runtime-map"); + if (!dirp) + return 0; + while ((entry = readdir(dirp)) != NULL) { + sprintf(filename, + "/sys/firmware/efi/runtime-map/%s", + (char *)entry->d_name); + if (*entry->d_name == '.') + continue; + file_scanf(filename, "type", "0x%x", (unsigned int *)&md.type); + file_scanf(filename, "phys_addr", "0x%llx", + (unsigned long long *)&md.phys_addr); + file_scanf(filename, "virt_addr", "0x%llx", + (unsigned long long *)&md.virt_addr); + file_scanf(filename, "num_pages", "0x%llx", + (unsigned long long *)&md.num_pages); + file_scanf(filename, "attribute", "0x%llx", + (unsigned long long *)&md.attribute); + *esd = realloc(*esd, sizeof(struct efi_setup_data) + + (nr_maps + 1) * sizeof(struct efi_mem_descriptor)); + *((*esd)->map + nr_maps) = md; + nr_maps++; + } + + closedir(dirp); + return nr_maps; +} + +static int setup_efi_setup_data(struct kexec_info *info, + struct x86_linux_param_header *real_mode) +{ + int nr_maps; + int64_t setup_data_paddr; + struct setup_data *sd; + struct efi_setup_data *esd; + int size, sdsize; + int has_efi = 0; + + has_efi = access("/sys/firmware/efi/systab", F_OK); + if (has_efi < 0) + return 1; + + esd = malloc(sizeof(struct efi_setup_data)); + if (!esd) + return 1; + memset(esd, 0, sizeof(struct efi_setup_data)); + get_efi_values(esd); + nr_maps = get_efi_runtime_map(&esd); + if (!nr_maps) { + free(esd); + return 1; + } + size = nr_maps * sizeof(struct efi_mem_descriptor) + + sizeof(struct efi_setup_data); + sd = malloc(sizeof(struct setup_data) + size); + if (!sd) { + free(esd); + return 1; + } + + memset(sd, 0, sizeof(struct setup_data) + size); + sd->next = 0; + sd->type = SETUP_EFI; + sd->len = size; + memcpy(sd->data, esd, size); + free(esd); + sdsize = sd->len + sizeof(struct setup_data); + setup_data_paddr = add_buffer(info, sd, sdsize, sdsize, getpagesize(), + 0x100000, ULONG_MAX, INT_MAX); + + real_mode->setup_data = setup_data_paddr; + + return 0; +} + +struct efi_info { + uint32_t pad[3]; + uint32_t efi_memdesc_version; + uint32_t pad1[4]; +}; + +static int +get_efi_mem_desc_version(struct x86_linux_param_header *real_mode) +{ + struct efi_info *ei = (struct efi_info *)real_mode->efi_info; + + return ei->efi_memdesc_version; +} + +static void setup_efi_info(struct kexec_info *info, + struct x86_linux_param_header *real_mode) +{ + int ret, desc_version; off_t offset = offsetof(typeof(*real_mode), efi_info); - get_bootparam(&real_mode->efi_info, offset, 32); + ret = get_bootparam(&real_mode->efi_info, offset, 32); + if (ret) + return; + desc_version = get_efi_mem_desc_version(real_mode); + if (desc_version != 1) { + fprintf(stderr, + "efi memory descriptor version %d is not supported!\n", + desc_version); + memset(&real_mode->efi_info, 0, 32); + return; + } + ret = setup_efi_setup_data(info, real_mode); + if (ret) + memset(&real_mode->efi_info, 0, 32); } void setup_linux_system_parameters(struct kexec_info *info, @@ -497,7 +680,7 @@ void setup_linux_system_parameters(struc /* get subarch from running kernel */ setup_subarch(real_mode); if (bzImage_support_efi_boot) - setup_efi_info(real_mode); + setup_efi_info(info, real_mode); /* Default screen size */ real_mode->orig_x = 0;