diff options
Diffstat (limited to 'drivers/firmware')
26 files changed, 1586 insertions, 331 deletions
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index ad67342313ed..0329d319d89a 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -9,6 +9,7 @@ # KASAN_SANITIZE_runtime-wrappers.o := n +obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o obj-$(CONFIG_EFI) += efi.o vars.o reboot.o memattr.o obj-$(CONFIG_EFI) += capsule.o memmap.o obj-$(CONFIG_EFI_VARS) += efivars.o diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c new file mode 100644 index 000000000000..04ca8764f0c0 --- /dev/null +++ b/drivers/firmware/efi/efi-bgrt.c @@ -0,0 +1,84 @@ +/* + * Copyright 2012 Intel Corporation + * Author: Josh Triplett <josh@joshtriplett.org> + * + * Based on the bgrt driver: + * Copyright 2012 Red Hat, Inc <mjg@redhat.com> + * Author: Matthew Garrett + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/efi.h> +#include <linux/efi-bgrt.h> + +struct acpi_table_bgrt bgrt_tab; +size_t __initdata bgrt_image_size; + +struct bmp_header { + u16 id; + u32 size; +} __packed; + +void __init efi_bgrt_init(struct acpi_table_header *table) +{ + void *image; + struct bmp_header bmp_header; + struct acpi_table_bgrt *bgrt = &bgrt_tab; + + if (acpi_disabled) + return; + + if (table->length < sizeof(bgrt_tab)) { + pr_notice("Ignoring BGRT: invalid length %u (expected %zu)\n", + table->length, sizeof(bgrt_tab)); + return; + } + *bgrt = *(struct acpi_table_bgrt *)table; + if (bgrt->version != 1) { + pr_notice("Ignoring BGRT: invalid version %u (expected 1)\n", + bgrt->version); + goto out; + } + if (bgrt->status & 0xfe) { + pr_notice("Ignoring BGRT: reserved status bits are non-zero %u\n", + bgrt->status); + goto out; + } + if (bgrt->image_type != 0) { + pr_notice("Ignoring BGRT: invalid image type %u (expected 0)\n", + bgrt->image_type); + goto out; + } + if (!bgrt->image_address) { + pr_notice("Ignoring BGRT: null image address\n"); + goto out; + } + + image = early_memremap(bgrt->image_address, sizeof(bmp_header)); + if (!image) { + pr_notice("Ignoring BGRT: failed to map image header memory\n"); + goto out; + } + + memcpy(&bmp_header, image, sizeof(bmp_header)); + early_memunmap(image, sizeof(bmp_header)); + if (bmp_header.id != 0x4d42) { + pr_notice("Ignoring BGRT: Incorrect BMP magic number 0x%x (expected 0x4d42)\n", + bmp_header.id); + goto out; + } + bgrt_image_size = bmp_header.size; + efi_mem_reserve(bgrt->image_address, bgrt_image_size); + + return; +out: + memset(bgrt, 0, sizeof(bgrt_tab)); +} diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c index f402ba2eed46..ed3137c1ceb0 100644 --- a/drivers/firmware/efi/efi-pstore.c +++ b/drivers/firmware/efi/efi-pstore.c @@ -28,26 +28,16 @@ static int efi_pstore_close(struct pstore_info *psi) return 0; } -struct pstore_read_data { - u64 *id; - enum pstore_type_id *type; - int *count; - struct timespec *timespec; - bool *compressed; - ssize_t *ecc_notice_size; - char **buf; -}; - static inline u64 generic_id(unsigned long timestamp, unsigned int part, int count) { return ((u64) timestamp * 100 + part) * 1000 + count; } -static int efi_pstore_read_func(struct efivar_entry *entry, void *data) +static int efi_pstore_read_func(struct efivar_entry *entry, + struct pstore_record *record) { efi_guid_t vendor = LINUX_EFI_CRASH_GUID; - struct pstore_read_data *cb_data = data; char name[DUMP_NAME_LEN], data_type; int i; int cnt; @@ -61,37 +51,37 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data) name[i] = entry->var.VariableName[i]; if (sscanf(name, "dump-type%u-%u-%d-%lu-%c", - cb_data->type, &part, &cnt, &time, &data_type) == 5) { - *cb_data->id = generic_id(time, part, cnt); - *cb_data->count = cnt; - cb_data->timespec->tv_sec = time; - cb_data->timespec->tv_nsec = 0; + &record->type, &part, &cnt, &time, &data_type) == 5) { + record->id = generic_id(time, part, cnt); + record->count = cnt; + record->time.tv_sec = time; + record->time.tv_nsec = 0; if (data_type == 'C') - *cb_data->compressed = true; + record->compressed = true; else - *cb_data->compressed = false; - *cb_data->ecc_notice_size = 0; + record->compressed = false; + record->ecc_notice_size = 0; } else if (sscanf(name, "dump-type%u-%u-%d-%lu", - cb_data->type, &part, &cnt, &time) == 4) { - *cb_data->id = generic_id(time, part, cnt); - *cb_data->count = cnt; - cb_data->timespec->tv_sec = time; - cb_data->timespec->tv_nsec = 0; - *cb_data->compressed = false; - *cb_data->ecc_notice_size = 0; + &record->type, &part, &cnt, &time) == 4) { + record->id = generic_id(time, part, cnt); + record->count = cnt; + record->time.tv_sec = time; + record->time.tv_nsec = 0; + record->compressed = false; + record->ecc_notice_size = 0; } else if (sscanf(name, "dump-type%u-%u-%lu", - cb_data->type, &part, &time) == 3) { + &record->type, &part, &time) == 3) { /* * Check if an old format, * which doesn't support holding * multiple logs, remains. */ - *cb_data->id = generic_id(time, part, 0); - *cb_data->count = 0; - cb_data->timespec->tv_sec = time; - cb_data->timespec->tv_nsec = 0; - *cb_data->compressed = false; - *cb_data->ecc_notice_size = 0; + record->id = generic_id(time, part, 0); + record->count = 0; + record->time.tv_sec = time; + record->time.tv_nsec = 0; + record->compressed = false; + record->ecc_notice_size = 0; } else return 0; @@ -99,7 +89,7 @@ static int efi_pstore_read_func(struct efivar_entry *entry, void *data) __efivar_entry_get(entry, &entry->var.Attributes, &entry->var.DataSize, entry->var.Data); size = entry->var.DataSize; - memcpy(*cb_data->buf, entry->var.Data, + memcpy(record->buf, entry->var.Data, (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size)); return size; @@ -164,7 +154,7 @@ static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos, /** * efi_pstore_sysfs_entry_iter * - * @data: function-specific data to pass to callback + * @record: pstore record to pass to callback * @pos: entry to begin iterating from * * You MUST call efivar_enter_iter_begin() before this function, and @@ -175,7 +165,8 @@ static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos, * the next entry of the last one passed to efi_pstore_read_func(). * To begin iterating from the beginning of the list @pos must be %NULL. */ -static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) +static int efi_pstore_sysfs_entry_iter(struct pstore_record *record, + struct efivar_entry **pos) { struct efivar_entry *entry, *n; struct list_head *head = &efivar_sysfs_list; @@ -186,7 +177,7 @@ static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) list_for_each_entry_safe(entry, n, head, list) { efi_pstore_scan_sysfs_enter(entry, n, head); - size = efi_pstore_read_func(entry, data); + size = efi_pstore_read_func(entry, record); ret = efi_pstore_scan_sysfs_exit(entry, n, head, size < 0); if (ret) @@ -201,7 +192,7 @@ static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) list_for_each_entry_safe_from((*pos), n, head, list) { efi_pstore_scan_sysfs_enter((*pos), n, head); - size = efi_pstore_read_func((*pos), data); + size = efi_pstore_read_func((*pos), record); ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0); if (ret) return ret; @@ -225,71 +216,57 @@ static int efi_pstore_sysfs_entry_iter(void *data, struct efivar_entry **pos) * size < 0: Failed to get data of entry logging via efi_pstore_write(), * and pstore will stop reading entry. */ -static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, - int *count, struct timespec *timespec, - char **buf, bool *compressed, - ssize_t *ecc_notice_size, - struct pstore_info *psi) +static ssize_t efi_pstore_read(struct pstore_record *record) { - struct pstore_read_data data; + struct efivar_entry *entry = (struct efivar_entry *)record->psi->data; ssize_t size; - data.id = id; - data.type = type; - data.count = count; - data.timespec = timespec; - data.compressed = compressed; - data.ecc_notice_size = ecc_notice_size; - data.buf = buf; - - *data.buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL); - if (!*data.buf) + record->buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL); + if (!record->buf) return -ENOMEM; if (efivar_entry_iter_begin()) { - kfree(*data.buf); - return -EINTR; + size = -EINTR; + goto out; } - size = efi_pstore_sysfs_entry_iter(&data, - (struct efivar_entry **)&psi->data); + size = efi_pstore_sysfs_entry_iter(record, &entry); efivar_entry_iter_end(); - if (size <= 0) - kfree(*data.buf); + +out: + if (size <= 0) { + kfree(record->buf); + record->buf = NULL; + } return size; } -static int efi_pstore_write(enum pstore_type_id type, - enum kmsg_dump_reason reason, u64 *id, - unsigned int part, int count, bool compressed, size_t size, - struct pstore_info *psi) +static int efi_pstore_write(struct pstore_record *record) { char name[DUMP_NAME_LEN]; efi_char16_t efi_name[DUMP_NAME_LEN]; efi_guid_t vendor = LINUX_EFI_CRASH_GUID; int i, ret = 0; - sprintf(name, "dump-type%u-%u-%d-%lu-%c", type, part, count, - get_seconds(), compressed ? 'C' : 'D'); + snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lu-%c", + record->type, record->part, record->count, + get_seconds(), record->compressed ? 'C' : 'D'); for (i = 0; i < DUMP_NAME_LEN; i++) efi_name[i] = name[i]; - efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES, - !pstore_cannot_block_path(reason), - size, psi->buf); + ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES, + !pstore_cannot_block_path(record->reason), + record->size, record->psi->buf); - if (reason == KMSG_DUMP_OOPS) + if (record->reason == KMSG_DUMP_OOPS) efivar_run_worker(); - *id = part; + record->id = record->part; return ret; }; struct pstore_erase_data { - u64 id; - enum pstore_type_id type; - int count; - struct timespec time; + struct pstore_record *record; efi_char16_t *name; }; @@ -315,8 +292,9 @@ static int efi_pstore_erase_func(struct efivar_entry *entry, void *data) * Check if an old format, which doesn't support * holding multiple logs, remains. */ - sprintf(name_old, "dump-type%u-%u-%lu", ed->type, - (unsigned int)ed->id, ed->time.tv_sec); + snprintf(name_old, sizeof(name_old), "dump-type%u-%u-%lu", + ed->record->type, (unsigned int)ed->record->id, + ed->record->time.tv_sec); for (i = 0; i < DUMP_NAME_LEN; i++) efi_name_old[i] = name_old[i]; @@ -341,8 +319,7 @@ static int efi_pstore_erase_func(struct efivar_entry *entry, void *data) return 1; } -static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, - struct timespec time, struct pstore_info *psi) +static int efi_pstore_erase(struct pstore_record *record) { struct pstore_erase_data edata; struct efivar_entry *entry = NULL; @@ -351,17 +328,16 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count, int found, i; unsigned int part; - do_div(id, 1000); - part = do_div(id, 100); - sprintf(name, "dump-type%u-%u-%d-%lu", type, part, count, time.tv_sec); + do_div(record->id, 1000); + part = do_div(record->id, 100); + snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lu", + record->type, record->part, record->count, + record->time.tv_sec); for (i = 0; i < DUMP_NAME_LEN; i++) efi_name[i] = name[i]; - edata.id = part; - edata.type = type; - edata.count = count; - edata.time = time; + edata.record = record; edata.name = efi_name; if (efivar_entry_iter_begin()) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index e7d404059b73..b372aad3b449 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -389,7 +389,6 @@ int __init efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md) return 0; } } - pr_err_once("requested map not found.\n"); return -ENOENT; } diff --git a/drivers/firmware/efi/esrt.c b/drivers/firmware/efi/esrt.c index 08b026864d4e..8554d7aec31c 100644 --- a/drivers/firmware/efi/esrt.c +++ b/drivers/firmware/efi/esrt.c @@ -254,7 +254,7 @@ void __init efi_esrt_init(void) rc = efi_mem_desc_lookup(efi.esrt, &md); if (rc < 0) { - pr_err("ESRT header is not in the memory map.\n"); + pr_warn("ESRT header is not in the memory map.\n"); return; } diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c index d4056c6be1ec..8181ac179d14 100644 --- a/drivers/firmware/efi/libstub/arm-stub.c +++ b/drivers/firmware/efi/libstub/arm-stub.c @@ -18,7 +18,27 @@ #include "efistub.h" -bool __nokaslr; +/* + * This is the base address at which to start allocating virtual memory ranges + * for UEFI Runtime Services. This is in the low TTBR0 range so that we can use + * any allocation we choose, and eliminate the risk of a conflict after kexec. + * The value chosen is the largest non-zero power of 2 suitable for this purpose + * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can + * be mapped efficiently. + * Since 32-bit ARM could potentially execute with a 1G/3G user/kernel split, + * map everything below 1 GB. (512 MB is a reasonable upper bound for the + * entire footprint of the UEFI runtime services memory regions) + */ +#define EFI_RT_VIRTUAL_BASE SZ_512M +#define EFI_RT_VIRTUAL_SIZE SZ_512M + +#ifdef CONFIG_ARM64 +# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE_64 +#else +# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE +#endif + +static u64 virtmap_base = EFI_RT_VIRTUAL_BASE; efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image, void **__fh) @@ -118,8 +138,6 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) goto fail; - pr_efi(sys_table, "Booting Linux Kernel...\n"); - status = check_platform_features(sys_table); if (status != EFI_SUCCESS) goto fail; @@ -153,17 +171,15 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, goto fail; } - /* check whether 'nokaslr' was passed on the command line */ - if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) { - static const u8 default_cmdline[] = CONFIG_CMDLINE; - const u8 *str, *cmdline = cmdline_ptr; + if (IS_ENABLED(CONFIG_CMDLINE_EXTEND) || + IS_ENABLED(CONFIG_CMDLINE_FORCE) || + cmdline_size == 0) + efi_parse_options(CONFIG_CMDLINE); - if (IS_ENABLED(CONFIG_CMDLINE_FORCE)) - cmdline = default_cmdline; - str = strstr(cmdline, "nokaslr"); - if (str == cmdline || (str > cmdline && *(str - 1) == ' ')) - __nokaslr = true; - } + if (!IS_ENABLED(CONFIG_CMDLINE_FORCE) && cmdline_size > 0) + efi_parse_options(cmdline_ptr); + + pr_efi(sys_table, "Booting Linux Kernel...\n"); si = setup_graphics(sys_table); @@ -176,10 +192,6 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, goto fail_free_cmdline; } - status = efi_parse_options(cmdline_ptr); - if (status != EFI_SUCCESS) - pr_efi_err(sys_table, "Failed to parse EFI cmdline options\n"); - secure_boot = efi_get_secureboot(sys_table); /* @@ -213,8 +225,9 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, if (!fdt_addr) pr_efi(sys_table, "Generating empty DTB\n"); - status = handle_cmdline_files(sys_table, image, cmdline_ptr, - "initrd=", dram_base + SZ_512M, + status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=", + efi_get_max_initrd_addr(dram_base, + *image_addr), (unsigned long *)&initrd_addr, (unsigned long *)&initrd_size); if (status != EFI_SUCCESS) @@ -222,9 +235,29 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table, efi_random_get_seed(sys_table); + if (!nokaslr()) { + /* + * Randomize the base of the UEFI runtime services region. + * Preserve the 2 MB alignment of the region by taking a + * shift of 21 bit positions into account when scaling + * the headroom value using a 32-bit random value. + */ + static const u64 headroom = EFI_RT_VIRTUAL_LIMIT - + EFI_RT_VIRTUAL_BASE - + EFI_RT_VIRTUAL_SIZE; + u32 rnd; + + status = efi_get_random_bytes(sys_table, sizeof(rnd), + (u8 *)&rnd); + if (status == EFI_SUCCESS) { + virtmap_base = EFI_RT_VIRTUAL_BASE + + (((headroom >> 21) * rnd) >> (32 - 21)); + } + } + new_fdt_addr = fdt_addr; status = allocate_new_fdt_and_exit_boot(sys_table, handle, - &new_fdt_addr, dram_base + MAX_FDT_OFFSET, + &new_fdt_addr, efi_get_max_fdt_addr(dram_base), initrd_addr, initrd_size, cmdline_ptr, fdt_addr, fdt_size); @@ -251,18 +284,6 @@ fail: return EFI_ERROR; } -/* - * This is the base address at which to start allocating virtual memory ranges - * for UEFI Runtime Services. This is in the low TTBR0 range so that we can use - * any allocation we choose, and eliminate the risk of a conflict after kexec. - * The value chosen is the largest non-zero power of 2 suitable for this purpose - * both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can - * be mapped efficiently. - * Since 32-bit ARM could potentially execute with a 1G/3G user/kernel split, - * map everything below 1 GB. - */ -#define EFI_RT_VIRTUAL_BASE SZ_512M - static int cmp_mem_desc(const void *l, const void *r) { const efi_memory_desc_t *left = l, *right = r; @@ -312,7 +333,7 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size, unsigned long desc_size, efi_memory_desc_t *runtime_map, int *count) { - u64 efi_virt_base = EFI_RT_VIRTUAL_BASE; + u64 efi_virt_base = virtmap_base; efi_memory_desc_t *in, *prev = NULL, *out = runtime_map; int l; diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c index e1f0b28e1dcb..becbda445913 100644 --- a/drivers/firmware/efi/libstub/arm32-stub.c +++ b/drivers/firmware/efi/libstub/arm32-stub.c @@ -9,6 +9,8 @@ #include <linux/efi.h> #include <asm/efi.h> +#include "efistub.h" + efi_status_t check_platform_features(efi_system_table_t *sys_table_arg) { int block; @@ -63,6 +65,132 @@ void free_screen_info(efi_system_table_t *sys_table_arg, struct screen_info *si) efi_call_early(free_pool, si); } +static efi_status_t reserve_kernel_base(efi_system_table_t *sys_table_arg, + unsigned long dram_base, + unsigned long *reserve_addr, + unsigned long *reserve_size) +{ + efi_physical_addr_t alloc_addr; + efi_memory_desc_t *memory_map; + unsigned long nr_pages, map_size, desc_size, buff_size; + efi_status_t status; + unsigned long l; + + struct efi_boot_memmap map = { + .map = &memory_map, + .map_size = &map_size, + .desc_size = &desc_size, + .desc_ver = NULL, + .key_ptr = NULL, + .buff_size = &buff_size, + }; + + /* + * Reserve memory for the uncompressed kernel image. This is + * all that prevents any future allocations from conflicting + * with the kernel. Since we can't tell from the compressed + * image how much DRAM the kernel actually uses (due to BSS + * size uncertainty) we allocate the maximum possible size. + * Do this very early, as prints can cause memory allocations + * that may conflict with this. + */ + alloc_addr = dram_base + MAX_UNCOMP_KERNEL_SIZE; + nr_pages = MAX_UNCOMP_KERNEL_SIZE / EFI_PAGE_SIZE; + status = efi_call_early(allocate_pages, EFI_ALLOCATE_MAX_ADDRESS, + EFI_BOOT_SERVICES_DATA, nr_pages, &alloc_addr); + if (status == EFI_SUCCESS) { + if (alloc_addr == dram_base) { + *reserve_addr = alloc_addr; + *reserve_size = MAX_UNCOMP_KERNEL_SIZE; + return EFI_SUCCESS; + } + /* + * If we end up here, the allocation succeeded but starts below + * dram_base. This can only occur if the real base of DRAM is + * not a multiple of 128 MB, in which case dram_base will have + * been rounded up. Since this implies that a part of the region + * was already occupied, we need to fall through to the code + * below to ensure that the existing allocations don't conflict. + * For this reason, we use EFI_BOOT_SERVICES_DATA above and not + * EFI_LOADER_DATA, which we wouldn't able to distinguish from + * allocations that we want to disallow. + */ + } + + /* + * If the allocation above failed, we may still be able to proceed: + * if the only allocations in the region are of types that will be + * released to the OS after ExitBootServices(), the decompressor can + * safely overwrite them. + */ + status = efi_get_memory_map(sys_table_arg, &map); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, + "reserve_kernel_base(): Unable to retrieve memory map.\n"); + return status; + } + + for (l = 0; l < map_size; l += desc_size) { + efi_memory_desc_t *desc; + u64 start, end; + + desc = (void *)memory_map + l; + start = desc->phys_addr; + end = start + desc->num_pages * EFI_PAGE_SIZE; + + /* Skip if entry does not intersect with region */ + if (start >= dram_base + MAX_UNCOMP_KERNEL_SIZE || + end <= dram_base) + continue; + + switch (desc->type) { + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + /* Ignore types that are released to the OS anyway */ + continue; + + case EFI_CONVENTIONAL_MEMORY: + /* + * Reserve the intersection between this entry and the + * region. + */ + start = max(start, (u64)dram_base); + end = min(end, (u64)dram_base + MAX_UNCOMP_KERNEL_SIZE); + + status = efi_call_early(allocate_pages, + EFI_ALLOCATE_ADDRESS, + EFI_LOADER_DATA, + (end - start) / EFI_PAGE_SIZE, + &start); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table_arg, + "reserve_kernel_base(): alloc failed.\n"); + goto out; + } + break; + + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + /* + * These regions may be released and reallocated for + * another purpose (including EFI_RUNTIME_SERVICE_DATA) + * at any time during the execution of the OS loader, + * so we cannot consider them as safe. + */ + default: + /* + * Treat any other allocation in the region as unsafe */ + status = EFI_OUT_OF_RESOURCES; + goto out; + } + } + + status = EFI_SUCCESS; +out: + efi_call_early(free_pool, memory_map); + return status; +} + efi_status_t handle_kernel_image(efi_system_table_t *sys_table, unsigned long *image_addr, unsigned long *image_size, @@ -71,10 +199,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table, unsigned long dram_base, efi_loaded_image_t *image) { - unsigned long nr_pages; efi_status_t status; - /* Use alloc_addr to tranlsate between types */ - efi_physical_addr_t alloc_addr; /* * Verify that the DRAM base address is compatible with the ARM @@ -85,27 +210,12 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table, */ dram_base = round_up(dram_base, SZ_128M); - /* - * Reserve memory for the uncompressed kernel image. This is - * all that prevents any future allocations from conflicting - * with the kernel. Since we can't tell from the compressed - * image how much DRAM the kernel actually uses (due to BSS - * size uncertainty) we allocate the maximum possible size. - * Do this very early, as prints can cause memory allocations - * that may conflict with this. - */ - alloc_addr = dram_base; - *reserve_size = MAX_UNCOMP_KERNEL_SIZE; - nr_pages = round_up(*reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; - status = sys_table->boottime->allocate_pages(EFI_ALLOCATE_ADDRESS, - EFI_LOADER_DATA, - nr_pages, &alloc_addr); + status = reserve_kernel_base(sys_table, dram_base, reserve_addr, + reserve_size); if (status != EFI_SUCCESS) { - *reserve_size = 0; pr_efi_err(sys_table, "Unable to allocate memory for uncompressed kernel.\n"); return status; } - *reserve_addr = alloc_addr; /* * Relocate the zImage, so that it appears in the lowest 128 MB diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c index eae693eb3e91..b4c2589d7c91 100644 --- a/drivers/firmware/efi/libstub/arm64-stub.c +++ b/drivers/firmware/efi/libstub/arm64-stub.c @@ -16,8 +16,6 @@ #include "efistub.h" -extern bool __nokaslr; - efi_status_t check_platform_features(efi_system_table_t *sys_table_arg) { u64 tg; @@ -52,7 +50,7 @@ efi_status_t handle_kernel_image(efi_system_table_t *sys_table_arg, u64 phys_seed = 0; if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) { - if (!__nokaslr) { + if (!nokaslr()) { status = efi_get_random_bytes(sys_table_arg, sizeof(phys_seed), (u8 *)&phys_seed); diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index 919822b7773d..b0184360efc6 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -32,6 +32,18 @@ static unsigned long __chunk_size = EFI_READ_CHUNK_SIZE; +static int __section(.data) __nokaslr; +static int __section(.data) __quiet; + +int __pure nokaslr(void) +{ + return __nokaslr; +} +int __pure is_quiet(void) +{ + return __quiet; +} + #define EFI_MMAP_NR_SLACK_SLOTS 8 struct file_info { @@ -409,17 +421,17 @@ static efi_status_t efi_file_close(void *handle) * environments, first in the early boot environment of the EFI boot * stub, and subsequently during the kernel boot. */ -efi_status_t efi_parse_options(char *cmdline) +efi_status_t efi_parse_options(char const *cmdline) { char *str; - /* - * Currently, the only efi= option we look for is 'nochunk', which - * is intended to work around known issues on certain x86 UEFI - * versions. So ignore for now on other architectures. - */ - if (!IS_ENABLED(CONFIG_X86)) - return EFI_SUCCESS; + str = strstr(cmdline, "nokaslr"); + if (str == cmdline || (str && str > cmdline && *(str - 1) == ' ')) + __nokaslr = 1; + + str = strstr(cmdline, "quiet"); + if (str == cmdline || (str && str > cmdline && *(str - 1) == ' ')) + __quiet = 1; /* * If no EFI parameters were specified on the cmdline we've got @@ -436,14 +448,14 @@ efi_status_t efi_parse_options(char *cmdline) * Remember, because efi= is also used by the kernel we need to * skip over arguments we don't understand. */ - while (*str) { + while (*str && *str != ' ') { if (!strncmp(str, "nochunk", 7)) { str += strlen("nochunk"); __chunk_size = -1UL; } /* Group words together, delimited by "," */ - while (*str && *str != ',') + while (*str && *str != ' ' && *str != ',') str++; if (*str == ',') diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index 71c4d0e3c4ed..83f268c05007 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -24,6 +24,15 @@ #define EFI_ALLOC_ALIGN EFI_PAGE_SIZE #endif +extern int __pure nokaslr(void); +extern int __pure is_quiet(void); + +#define pr_efi(sys_table, msg) do { \ + if (!is_quiet()) efi_printk(sys_table, "EFI stub: "msg); \ +} while (0) + +#define pr_efi_err(sys_table, msg) efi_printk(sys_table, "EFI stub: ERROR: "msg) + void efi_char16_printk(efi_system_table_t *, efi_char16_t *); efi_status_t efi_open_volume(efi_system_table_t *sys_table_arg, void *__image, diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index 82973b86efe4..8830fa601e45 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -230,6 +230,10 @@ static efi_status_t exit_boot_func(efi_system_table_t *sys_table_arg, return update_fdt_memmap(p->new_fdt_addr, map); } +#ifndef MAX_FDT_SIZE +#define MAX_FDT_SIZE SZ_2M +#endif + /* * Allocate memory for a new FDT, then add EFI, commandline, and * initrd related fields to the FDT. This routine increases the @@ -257,7 +261,6 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, u32 desc_ver; unsigned long mmap_key; efi_memory_desc_t *memory_map, *runtime_map; - unsigned long new_fdt_size; efi_status_t status; int runtime_entry_count = 0; struct efi_boot_memmap map; @@ -286,41 +289,29 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, "Exiting boot services and installing virtual address map...\n"); map.map = &memory_map; + status = efi_high_alloc(sys_table, MAX_FDT_SIZE, EFI_FDT_ALIGN, + new_fdt_addr, max_addr); + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, + "Unable to allocate memory for new device tree.\n"); + goto fail; + } + /* - * Estimate size of new FDT, and allocate memory for it. We - * will allocate a bigger buffer if this ends up being too - * small, so a rough guess is OK here. + * Now that we have done our final memory allocation (and free) + * we can get the memory map key needed for exit_boot_services(). */ - new_fdt_size = fdt_size + EFI_PAGE_SIZE; - while (1) { - status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN, - new_fdt_addr, max_addr); - if (status != EFI_SUCCESS) { - pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n"); - goto fail; - } - - status = update_fdt(sys_table, - (void *)fdt_addr, fdt_size, - (void *)*new_fdt_addr, new_fdt_size, - cmdline_ptr, initrd_addr, initrd_size); + status = efi_get_memory_map(sys_table, &map); + if (status != EFI_SUCCESS) + goto fail_free_new_fdt; - /* Succeeding the first time is the expected case. */ - if (status == EFI_SUCCESS) - break; + status = update_fdt(sys_table, (void *)fdt_addr, fdt_size, + (void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr, + initrd_addr, initrd_size); - if (status == EFI_BUFFER_TOO_SMALL) { - /* - * We need to allocate more space for the new - * device tree, so free existing buffer that is - * too small. - */ - efi_free(sys_table, new_fdt_size, *new_fdt_addr); - new_fdt_size += EFI_PAGE_SIZE; - } else { - pr_efi_err(sys_table, "Unable to construct new device tree.\n"); - goto fail_free_new_fdt; - } + if (status != EFI_SUCCESS) { + pr_efi_err(sys_table, "Unable to construct new device tree.\n"); + goto fail_free_new_fdt; } priv.runtime_map = runtime_map; @@ -364,7 +355,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table, pr_efi_err(sys_table, "Exit boot services failed.\n"); fail_free_new_fdt: - efi_free(sys_table, new_fdt_size, *new_fdt_addr); + efi_free(sys_table, MAX_FDT_SIZE, *new_fdt_addr); fail: sys_table->boottime->free_pool(runtime_map); diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c index 932742e4cf23..24c461dea7af 100644 --- a/drivers/firmware/efi/libstub/gop.c +++ b/drivers/firmware/efi/libstub/gop.c @@ -149,7 +149,8 @@ setup_gop32(efi_system_table_t *sys_table_arg, struct screen_info *si, status = __gop_query32(sys_table_arg, gop32, &info, &size, ¤t_fb_base); - if (status == EFI_SUCCESS && (!first_gop || conout_found)) { + if (status == EFI_SUCCESS && (!first_gop || conout_found) && + info->pixel_format != PIXEL_BLT_ONLY) { /* * Systems that use the UEFI Console Splitter may * provide multiple GOP devices, not all of which are @@ -266,7 +267,8 @@ setup_gop64(efi_system_table_t *sys_table_arg, struct screen_info *si, status = __gop_query64(sys_table_arg, gop64, &info, &size, ¤t_fb_base); - if (status == EFI_SUCCESS && (!first_gop || conout_found)) { + if (status == EFI_SUCCESS && (!first_gop || conout_found) && + info->pixel_format != PIXEL_BLT_ONLY) { /* * Systems that use the UEFI Console Splitter may * provide multiple GOP devices, not all of which are diff --git a/drivers/firmware/efi/libstub/secureboot.c b/drivers/firmware/efi/libstub/secureboot.c index 5da36e56b36a..8c34d50a4d80 100644 --- a/drivers/firmware/efi/libstub/secureboot.c +++ b/drivers/firmware/efi/libstub/secureboot.c @@ -12,6 +12,8 @@ #include <linux/efi.h> #include <asm/efi.h> +#include "efistub.h" + /* BIOS variables */ static const efi_guid_t efi_variable_guid = EFI_GLOBAL_VARIABLE_GUID; static const efi_char16_t const efi_SecureBoot_name[] = { diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index 29c8cdda82a1..f16b381a569c 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -1,18 +1,16 @@ -config GOOGLE_FIRMWARE +menuconfig GOOGLE_FIRMWARE bool "Google Firmware Drivers" - depends on X86 default n help These firmware drivers are used by Google's servers. They are only useful if you are working directly on one of their proprietary servers. If in doubt, say "N". -menu "Google Firmware Drivers" - depends on GOOGLE_FIRMWARE +if GOOGLE_FIRMWARE config GOOGLE_SMI tristate "SMI interface for Google platforms" - depends on ACPI && DMI && EFI + depends on X86 && ACPI && DMI && EFI select EFI_VARS help Say Y here if you want to enable SMI callbacks for Google @@ -20,12 +18,57 @@ config GOOGLE_SMI clearing the EFI event log and reading and writing NVRAM variables. +config GOOGLE_COREBOOT_TABLE + tristate + depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF + +config GOOGLE_COREBOOT_TABLE_ACPI + tristate "Coreboot Table Access - ACPI" + depends on ACPI + select GOOGLE_COREBOOT_TABLE + help + This option enables the coreboot_table module, which provides other + firmware modules to access to the coreboot table. The coreboot table + pointer is accessed through the ACPI "GOOGCB00" object. + If unsure say N. + +config GOOGLE_COREBOOT_TABLE_OF + tristate "Coreboot Table Access - Device Tree" + depends on OF + select GOOGLE_COREBOOT_TABLE + help + This option enable the coreboot_table module, which provide other + firmware modules to access coreboot table. The coreboot table pointer + is accessed through the device tree node /firmware/coreboot. + If unsure say N. + config GOOGLE_MEMCONSOLE - tristate "Firmware Memory Console" - depends on DMI + tristate + depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT + +config GOOGLE_MEMCONSOLE_X86_LEGACY + tristate "Firmware Memory Console - X86 Legacy support" + depends on X86 && ACPI && DMI + select GOOGLE_MEMCONSOLE help This option enables the kernel to search for a firmware log in the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log. -endmenu +config GOOGLE_MEMCONSOLE_COREBOOT + tristate "Firmware Memory Console" + depends on GOOGLE_COREBOOT_TABLE + select GOOGLE_MEMCONSOLE + help + This option enables the kernel to search for a firmware log in + the coreboot table. If found, this log is exported to userland + in the file /sys/firmware/log. + +config GOOGLE_VPD + tristate "Vital Product Data" + depends on GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to expose the content of Google VPD + under /sys/firmware/vpd. + +endif # GOOGLE_FIRMWARE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index 54a294e3cb61..bc4de02202ad 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -1,3 +1,11 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o -obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o +obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o + +vpd-sysfs-y := vpd.o vpd_decode.o +obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c new file mode 100644 index 000000000000..fb98db2d20e2 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-acpi.c @@ -0,0 +1,88 @@ +/* + * coreboot_table-acpi.c + * + * Using ACPI to locate Coreboot table and provide coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "coreboot_table.h" + +static int coreboot_table_acpi_probe(struct platform_device *pdev) +{ + phys_addr_t phyaddr; + resource_size_t len; + struct coreboot_table_header __iomem *header = NULL; + struct resource *res; + void __iomem *ptr = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + len = resource_size(res); + if (!res->start || !len) + return -EINVAL; + + phyaddr = res->start; + header = ioremap_cache(phyaddr, sizeof(*header)); + if (header == NULL) + return -ENOMEM; + + ptr = ioremap_cache(phyaddr, + header->header_bytes + header->table_bytes); + iounmap(header); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_acpi_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct acpi_device_id cros_coreboot_acpi_match[] = { + { "GOOGCB00", 0 }, + { "BOOT0000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); + +static struct platform_driver coreboot_table_acpi_driver = { + .probe = coreboot_table_acpi_probe, + .remove = coreboot_table_acpi_remove, + .driver = { + .name = "coreboot_table_acpi", + .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match), + }, +}; + +static int __init coreboot_table_acpi_init(void) +{ + return platform_driver_register(&coreboot_table_acpi_driver); +} + +module_init(coreboot_table_acpi_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c new file mode 100644 index 000000000000..727acdc83e83 --- /dev/null +++ b/drivers/firmware/google/coreboot_table-of.c @@ -0,0 +1,82 @@ +/* + * coreboot_table-of.c + * + * Coreboot table access through open firmware. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include "coreboot_table.h" + +static int coreboot_table_of_probe(struct platform_device *pdev) +{ + struct device_node *fw_dn = pdev->dev.of_node; + void __iomem *ptr; + + ptr = of_iomap(fw_dn, 0); + of_node_put(fw_dn); + if (!ptr) + return -ENOMEM; + + return coreboot_table_init(ptr); +} + +static int coreboot_table_of_remove(struct platform_device *pdev) +{ + return coreboot_table_exit(); +} + +static const struct of_device_id coreboot_of_match[] = { + { .compatible = "coreboot" }, + {}, +}; + +static struct platform_driver coreboot_table_of_driver = { + .probe = coreboot_table_of_probe, + .remove = coreboot_table_of_remove, + .driver = { + .name = "coreboot_table_of", + .of_match_table = coreboot_of_match, + }, +}; + +static int __init platform_coreboot_table_of_init(void) +{ + struct platform_device *pdev; + struct device_node *of_node; + + /* Limit device creation to the presence of /firmware/coreboot node */ + of_node = of_find_node_by_path("/firmware/coreboot"); + if (!of_node) + return -ENODEV; + + if (!of_match_node(coreboot_of_match, of_node)) + return -ENODEV; + + pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL); + if (!pdev) + return -ENODEV; + + return platform_driver_register(&coreboot_table_of_driver); +} + +module_init(platform_coreboot_table_of_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c new file mode 100644 index 000000000000..0019d3ec18dd --- /dev/null +++ b/drivers/firmware/google/coreboot_table.c @@ -0,0 +1,94 @@ +/* + * coreboot_table.c + * + * Module providing coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "coreboot_table.h" + +struct coreboot_table_entry { + u32 tag; + u32 size; +}; + +static struct coreboot_table_header __iomem *ptr_header; + +/* + * This function parses the coreboot table for an entry that contains the base + * address of the given entry tag. The coreboot table consists of a header + * directly followed by a number of small, variable-sized entries, which each + * contain an identifying tag and their length as the first two fields. + */ +int coreboot_table_find(int tag, void *data, size_t data_size) +{ + struct coreboot_table_header header; + struct coreboot_table_entry entry; + void *ptr_entry; + int i; + + if (!ptr_header) + return -EPROBE_DEFER; + + memcpy_fromio(&header, ptr_header, sizeof(header)); + + if (strncmp(header.signature, "LBIO", sizeof(header.signature))) { + pr_warn("coreboot_table: coreboot table missing or corrupt!\n"); + return -ENODEV; + } + + ptr_entry = (void *)ptr_header + header.header_bytes; + + for (i = 0; i < header.table_entries; i++) { + memcpy_fromio(&entry, ptr_entry, sizeof(entry)); + if (entry.tag == tag) { + if (data_size < entry.size) + return -EINVAL; + + memcpy_fromio(data, ptr_entry, entry.size); + + return 0; + } + + ptr_entry += entry.size; + } + + return -ENOENT; +} +EXPORT_SYMBOL(coreboot_table_find); + +int coreboot_table_init(void __iomem *ptr) +{ + ptr_header = ptr; + + return 0; +} +EXPORT_SYMBOL(coreboot_table_init); + +int coreboot_table_exit(void) +{ + if (ptr_header) + iounmap(ptr_header); + + return 0; +} +EXPORT_SYMBOL(coreboot_table_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h new file mode 100644 index 000000000000..6eff1ae0c5d3 --- /dev/null +++ b/drivers/firmware/google/coreboot_table.h @@ -0,0 +1,50 @@ +/* + * coreboot_table.h + * + * Internal header for coreboot table access. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __COREBOOT_TABLE_H +#define __COREBOOT_TABLE_H + +#include <linux/io.h> + +/* List of coreboot entry structures that is used */ +struct lb_cbmem_ref { + uint32_t tag; + uint32_t size; + + uint64_t cbmem_addr; +}; + +/* Coreboot table header structure */ +struct coreboot_table_header { + char signature[4]; + u32 header_bytes; + u32 header_checksum; + u32 table_bytes; + u32 table_checksum; + u32 table_entries; +}; + +/* Retrieve coreboot table entry with tag *tag* and copy it to data */ +int coreboot_table_find(int tag, void *data, size_t data_size); + +/* Initialize coreboot table module given a pointer to iomem */ +int coreboot_table_init(void __iomem *ptr); + +/* Cleanup coreboot table module */ +int coreboot_table_exit(void); + +#endif /* __COREBOOT_TABLE_H */ diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c new file mode 100644 index 000000000000..02711114dece --- /dev/null +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -0,0 +1,109 @@ +/* + * memconsole-coreboot.c + * + * Memory based BIOS console accessed through coreboot table. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "memconsole.h" +#include "coreboot_table.h" + +#define CB_TAG_CBMEM_CONSOLE 0x17 + +/* CBMEM firmware console log descriptor. */ +struct cbmem_cons { + u32 buffer_size; + u32 buffer_cursor; + u8 buffer_body[0]; +} __packed; + +static struct cbmem_cons __iomem *cbmem_console; + +static int memconsole_coreboot_init(phys_addr_t physaddr) +{ + struct cbmem_cons __iomem *tmp_cbmc; + + tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB); + + if (!tmp_cbmc) + return -ENOMEM; + + cbmem_console = memremap(physaddr, + tmp_cbmc->buffer_size + sizeof(*cbmem_console), + MEMREMAP_WB); + memunmap(tmp_cbmc); + + if (!cbmem_console) + return -ENOMEM; + + memconsole_setup(cbmem_console->buffer_body, + min(cbmem_console->buffer_cursor, cbmem_console->buffer_size)); + + return 0; +} + +static int memconsole_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry)); + if (ret) + return ret; + + ret = memconsole_coreboot_init(entry.cbmem_addr); + if (ret) + return ret; + + return memconsole_sysfs_init(); +} + +static int memconsole_remove(struct platform_device *pdev) +{ + memconsole_exit(); + + if (cbmem_console) + memunmap(cbmem_console); + + return 0; +} + +static struct platform_driver memconsole_driver = { + .probe = memconsole_probe, + .remove = memconsole_remove, + .driver = { + .name = "memconsole", + }, +}; + +static int __init platform_memconsole_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("memconsole", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + platform_driver_register(&memconsole_driver); + + return 0; +} + +module_init(platform_memconsole_init); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c new file mode 100644 index 000000000000..1f279ee883b9 --- /dev/null +++ b/drivers/firmware/google/memconsole-x86-legacy.c @@ -0,0 +1,153 @@ +/* + * memconsole-x86-legacy.c + * + * EBDA specific parts of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <linux/mm.h> +#include <asm/bios_ebda.h> +#include <linux/acpi.h> + +#include "memconsole.h" + +#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE +#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) + +struct biosmemcon_ebda { + u32 signature; + union { + struct { + u8 enabled; + u32 buffer_addr; + u16 start; + u16 end; + u16 num_chars; + u8 wrapped; + } __packed v1; + struct { + u32 buffer_addr; + /* Misdocumented as number of pages! */ + u16 num_bytes; + u16 start; + u16 end; + } __packed v2; + }; +} __packed; + +static void found_v1_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n", + hdr->v1.buffer_addr, hdr->v1.start, + hdr->v1.end, hdr->v1.num_chars); + + memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars); +} + +static void found_v2_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n", + hdr->v2.buffer_addr, hdr->v2.start, + hdr->v2.end, hdr->v2.num_bytes); + + memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start), + hdr->v2.end - hdr->v2.start); +} + +/* + * Search through the EBDA for the BIOS Memory Console, and + * set the global variables to point to it. Return true if found. + */ +static bool memconsole_ebda_init(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + pr_info("memconsole: BIOS EBDA non-existent.\n"); + return false; + } + + /* EBDA length is byte 0 of EBDA (in KB) */ + length = *(u8 *)phys_to_virt(address); + length <<= 10; /* convert to bytes */ + + /* + * Search through EBDA for BIOS memory console structure + * note: signature is not necessarily dword-aligned + */ + for (cur = 0; cur < length; cur++) { + struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); + + /* memconsole v1 */ + if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { + found_v1_header(hdr); + return true; + } + + /* memconsole v2 */ + if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { + found_v2_header(hdr); + return true; + } + } + + pr_info("memconsole: BIOS console EBDA structure not found!\n"); + return false; +} + +static struct dmi_system_id memconsole_dmi_table[] __initdata = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); + +static bool __init memconsole_find(void) +{ + if (!dmi_check_system(memconsole_dmi_table)) + return false; + + return memconsole_ebda_init(); +} + +static int __init memconsole_x86_init(void) +{ + if (!memconsole_find()) + return -ENODEV; + + return memconsole_sysfs_init(); +} + +static void __exit memconsole_x86_exit(void) +{ + memconsole_exit(); +} + +module_init(memconsole_x86_init); +module_exit(memconsole_x86_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c index 2f569aaed4c7..94e200ddb4fa 100644 --- a/drivers/firmware/google/memconsole.c +++ b/drivers/firmware/google/memconsole.c @@ -1,66 +1,36 @@ /* * memconsole.c * - * Infrastructure for importing the BIOS memory based console - * into the kernel log ringbuffer. + * Architecture-independent parts of the memory based BIOS console. * - * Copyright 2010 Google Inc. All rights reserved. + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. */ -#include <linux/ctype.h> #include <linux/init.h> -#include <linux/kernel.h> -#include <linux/string.h> #include <linux/sysfs.h> #include <linux/kobject.h> #include <linux/module.h> -#include <linux/dmi.h> -#include <linux/io.h> -#include <asm/bios_ebda.h> -#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE -#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) +#include "memconsole.h" -struct biosmemcon_ebda { - u32 signature; - union { - struct { - u8 enabled; - u32 buffer_addr; - u16 start; - u16 end; - u16 num_chars; - u8 wrapped; - } __packed v1; - struct { - u32 buffer_addr; - /* Misdocumented as number of pages! */ - u16 num_bytes; - u16 start; - u16 end; - } __packed v2; - }; -} __packed; - -static u32 memconsole_baseaddr; +static char *memconsole_baseaddr; static size_t memconsole_length; static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { - char *memconsole; - ssize_t ret; - - memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length); - if (!memconsole) { - pr_err("memconsole: ioremap_cache failed\n"); - return -ENOMEM; - } - ret = memory_read_from_buffer(buf, count, &pos, memconsole, - memconsole_length); - iounmap(memconsole); - return ret; + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); } static struct bin_attribute memconsole_bin_attr = { @@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = { .read = memconsole_read, }; - -static void __init found_v1_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v1 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num = %d\n", - hdr->v1.buffer_addr, hdr->v1.start, - hdr->v1.end, hdr->v1.num_chars); - - memconsole_length = hdr->v1.num_chars; - memconsole_baseaddr = hdr->v1.buffer_addr; -} - -static void __init found_v2_header(struct biosmemcon_ebda *hdr) -{ - pr_info("BIOS console v2 EBDA structure found at %p\n", hdr); - pr_info("BIOS console buffer at 0x%.8x, " - "start = %d, end = %d, num_bytes = %d\n", - hdr->v2.buffer_addr, hdr->v2.start, - hdr->v2.end, hdr->v2.num_bytes); - - memconsole_length = hdr->v2.end - hdr->v2.start; - memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start; -} - -/* - * Search through the EBDA for the BIOS Memory Console, and - * set the global variables to point to it. Return true if found. - */ -static bool __init found_memconsole(void) +void memconsole_setup(void *baseaddr, size_t length) { - unsigned int address; - size_t length, cur; - - address = get_bios_ebda(); - if (!address) { - pr_info("BIOS EBDA non-existent.\n"); - return false; - } - - /* EBDA length is byte 0 of EBDA (in KB) */ - length = *(u8 *)phys_to_virt(address); - length <<= 10; /* convert to bytes */ - - /* - * Search through EBDA for BIOS memory console structure - * note: signature is not necessarily dword-aligned - */ - for (cur = 0; cur < length; cur++) { - struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); - - /* memconsole v1 */ - if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { - found_v1_header(hdr); - return true; - } - - /* memconsole v2 */ - if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { - found_v2_header(hdr); - return true; - } - } - - pr_info("BIOS console EBDA structure not found!\n"); - return false; + memconsole_baseaddr = baseaddr; + memconsole_length = length; } +EXPORT_SYMBOL(memconsole_setup); -static struct dmi_system_id memconsole_dmi_table[] __initdata = { - { - .ident = "Google Board", - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), - }, - }, - {} -}; -MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); - -static int __init memconsole_init(void) +int memconsole_sysfs_init(void) { - if (!dmi_check_system(memconsole_dmi_table)) - return -ENODEV; - - if (!found_memconsole()) - return -ENODEV; - memconsole_bin_attr.size = memconsole_length; return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); } +EXPORT_SYMBOL(memconsole_sysfs_init); -static void __exit memconsole_exit(void) +void memconsole_exit(void) { sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr); } - -module_init(memconsole_init); -module_exit(memconsole_exit); +EXPORT_SYMBOL(memconsole_exit); MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h new file mode 100644 index 000000000000..190fc03a51ae --- /dev/null +++ b/drivers/firmware/google/memconsole.h @@ -0,0 +1,43 @@ +/* + * memconsole.h + * + * Internal headers of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H +#define __FIRMWARE_GOOGLE_MEMCONSOLE_H + +/* + * memconsole_setup + * + * Initialize the memory console from raw (virtual) base + * address and length. + */ +void memconsole_setup(void *baseaddr, size_t length); + +/* + * memconsole_sysfs_init + * + * Update memory console length and create binary file + * for firmware object. + */ +int memconsole_sysfs_init(void); + +/* memconsole_exit + * + * Unmap the console buffer. + */ +void memconsole_exit(void); + +#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */ diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c new file mode 100644 index 000000000000..3ce813110d5e --- /dev/null +++ b/drivers/firmware/google/vpd.c @@ -0,0 +1,332 @@ +/* + * vpd.c + * + * Driver for exporting VPD content to sysfs. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include "coreboot_table.h" +#include "vpd_decode.h" + +#define CB_TAG_VPD 0x2c +#define VPD_CBMEM_MAGIC 0x43524f53 + +static struct kobject *vpd_kobj; + +struct vpd_cbmem { + u32 magic; + u32 version; + u32 ro_size; + u32 rw_size; + u8 blob[0]; +}; + +struct vpd_section { + bool enabled; + const char *name; + char *raw_name; /* the string name_raw */ + struct kobject *kobj; /* vpd/name directory */ + char *baseaddr; + struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */ + struct list_head attribs; /* key/value in vpd_attrib_info list */ +}; + +struct vpd_attrib_info { + char *key; + const char *value; + struct bin_attribute bin_attr; + struct list_head list; +}; + +static struct vpd_section ro_vpd; +static struct vpd_section rw_vpd; + +static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_attrib_info *info = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, info->value, + info->bin_attr.size); +} + +/* + * vpd_section_check_key_name() + * + * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but + * old firmware versions may have entries like "S/N" which are problematic when + * exporting them as sysfs attributes. These keys present in old firmwares are + * ignored. + * + * Returns VPD_OK for a valid key name, VPD_FAIL otherwise. + * + * @key: The key name to check + * @key_len: key name length + */ +static int vpd_section_check_key_name(const u8 *key, s32 key_len) +{ + int c; + + while (key_len-- > 0) { + c = *key++; + + if (!isalnum(c) && c != '_') + return VPD_FAIL; + } + + return VPD_OK; +} + +static int vpd_section_attrib_add(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg) +{ + int ret; + struct vpd_section *sec = arg; + struct vpd_attrib_info *info; + + /* + * Return VPD_OK immediately to decode next entry if the current key + * name contains invalid characters. + */ + if (vpd_section_check_key_name(key, key_len) != VPD_OK) + return VPD_OK; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + info->key = kzalloc(key_len + 1, GFP_KERNEL); + if (!info->key) + return -ENOMEM; + + memcpy(info->key, key, key_len); + + sysfs_bin_attr_init(&info->bin_attr); + info->bin_attr.attr.name = info->key; + info->bin_attr.attr.mode = 0444; + info->bin_attr.size = value_len; + info->bin_attr.read = vpd_attrib_read; + info->bin_attr.private = info; + + info->value = value; + + INIT_LIST_HEAD(&info->list); + list_add_tail(&info->list, &sec->attribs); + + ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr); + if (ret) { + kfree(info->key); + return ret; + } + + return 0; +} + +static void vpd_section_attrib_destroy(struct vpd_section *sec) +{ + struct vpd_attrib_info *info; + struct vpd_attrib_info *temp; + + list_for_each_entry_safe(info, temp, &sec->attribs, list) { + kfree(info->key); + sysfs_remove_bin_file(sec->kobj, &info->bin_attr); + kfree(info); + } +} + +static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp, + struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_section *sec = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, sec->baseaddr, + sec->bin_attr.size); +} + +static int vpd_section_create_attribs(struct vpd_section *sec) +{ + s32 consumed; + int ret; + + consumed = 0; + do { + ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr, + &consumed, vpd_section_attrib_add, sec); + } while (ret == VPD_OK); + + return 0; +} + +static int vpd_section_init(const char *name, struct vpd_section *sec, + phys_addr_t physaddr, size_t size) +{ + int ret; + int raw_len; + + sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); + if (!sec->baseaddr) + return -ENOMEM; + + sec->name = name; + + /* We want to export the raw partion with name ${name}_raw */ + raw_len = strlen(name) + 5; + sec->raw_name = kzalloc(raw_len, GFP_KERNEL); + strncpy(sec->raw_name, name, raw_len); + strncat(sec->raw_name, "_raw", raw_len); + + sysfs_bin_attr_init(&sec->bin_attr); + sec->bin_attr.attr.name = sec->raw_name; + sec->bin_attr.attr.mode = 0444; + sec->bin_attr.size = size; + sec->bin_attr.read = vpd_section_read; + sec->bin_attr.private = sec; + + ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); + if (ret) + goto free_sec; + + sec->kobj = kobject_create_and_add(name, vpd_kobj); + if (!sec->kobj) { + ret = -EINVAL; + goto sysfs_remove; + } + + INIT_LIST_HEAD(&sec->attribs); + vpd_section_create_attribs(sec); + + sec->enabled = true; + + return 0; + +sysfs_remove: + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + +free_sec: + kfree(sec->raw_name); + iounmap(sec->baseaddr); + + return ret; +} + +static int vpd_section_destroy(struct vpd_section *sec) +{ + if (sec->enabled) { + vpd_section_attrib_destroy(sec); + kobject_del(sec->kobj); + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + kfree(sec->raw_name); + iounmap(sec->baseaddr); + } + + return 0; +} + +static int vpd_sections_init(phys_addr_t physaddr) +{ + struct vpd_cbmem __iomem *temp; + struct vpd_cbmem header; + int ret = 0; + + temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB); + if (!temp) + return -ENOMEM; + + memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem)); + iounmap(temp); + + if (header.magic != VPD_CBMEM_MAGIC) + return -ENODEV; + + if (header.ro_size) { + ret = vpd_section_init("ro", &ro_vpd, + physaddr + sizeof(struct vpd_cbmem), + header.ro_size); + if (ret) + return ret; + } + + if (header.rw_size) { + ret = vpd_section_init("rw", &rw_vpd, + physaddr + sizeof(struct vpd_cbmem) + + header.ro_size, header.rw_size); + if (ret) + return ret; + } + + return 0; +} + +static int vpd_probe(struct platform_device *pdev) +{ + int ret; + struct lb_cbmem_ref entry; + + ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry)); + if (ret) + return ret; + + return vpd_sections_init(entry.cbmem_addr); +} + +static struct platform_driver vpd_driver = { + .probe = vpd_probe, + .driver = { + .name = "vpd", + }, +}; + +static int __init vpd_platform_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("vpd", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); + if (!vpd_kobj) + return -ENOMEM; + + memset(&ro_vpd, 0, sizeof(ro_vpd)); + memset(&rw_vpd, 0, sizeof(rw_vpd)); + + platform_driver_register(&vpd_driver); + + return 0; +} + +static void __exit vpd_platform_exit(void) +{ + vpd_section_destroy(&ro_vpd); + vpd_section_destroy(&rw_vpd); + kobject_del(vpd_kobj); +} + +module_init(vpd_platform_init); +module_exit(vpd_platform_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c new file mode 100644 index 000000000000..943acaa8aa76 --- /dev/null +++ b/drivers/firmware/google/vpd_decode.c @@ -0,0 +1,99 @@ +/* + * vpd_decode.c + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/export.h> + +#include "vpd_decode.h" + +static int vpd_decode_len(const s32 max_len, const u8 *in, + s32 *length, s32 *decoded_len) +{ + u8 more; + int i = 0; + + if (!length || !decoded_len) + return VPD_FAIL; + + *length = 0; + do { + if (i >= max_len) + return VPD_FAIL; + + more = in[i] & 0x80; + *length <<= 7; + *length |= in[i] & 0x7f; + ++i; + } while (more); + + *decoded_len = i; + + return VPD_OK; +} + +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg) +{ + int type; + int res; + s32 key_len; + s32 value_len; + s32 decoded_len; + const u8 *key; + const u8 *value; + + /* type */ + if (*consumed >= max_len) + return VPD_FAIL; + + type = input_buf[*consumed]; + + switch (type) { + case VPD_TYPE_INFO: + case VPD_TYPE_STRING: + (*consumed)++; + + /* key */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &key_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len >= max_len) + return VPD_FAIL; + + *consumed += decoded_len; + key = &input_buf[*consumed]; + *consumed += key_len; + + /* value */ + res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed], + &value_len, &decoded_len); + if (res != VPD_OK || *consumed + decoded_len > max_len) + return VPD_FAIL; + + *consumed += decoded_len; + value = &input_buf[*consumed]; + *consumed += value_len; + + if (type == VPD_TYPE_STRING) + return callback(key, key_len, value, value_len, + callback_arg); + break; + + default: + return VPD_FAIL; + } + + return VPD_OK; +} diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h new file mode 100644 index 000000000000..be3d62c5ca2f --- /dev/null +++ b/drivers/firmware/google/vpd_decode.h @@ -0,0 +1,58 @@ +/* + * vpd_decode.h + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __VPD_DECODE_H +#define __VPD_DECODE_H + +#include <linux/types.h> + +enum { + VPD_OK = 0, + VPD_FAIL, +}; + +enum { + VPD_TYPE_TERMINATOR = 0, + VPD_TYPE_STRING, + VPD_TYPE_INFO = 0xfe, + VPD_TYPE_IMPLICIT_TERMINATOR = 0xff, +}; + +/* Callback for vpd_decode_string to invoke. */ +typedef int vpd_decode_callback(const u8 *key, s32 key_len, + const u8 *value, s32 value_len, + void *arg); + +/* + * vpd_decode_string + * + * Given the encoded string, this function invokes callback with extracted + * (key, value). The *consumed will be plused the number of bytes consumed in + * this function. + * + * The input_buf points to the first byte of the input buffer. + * + * The *consumed starts from 0, which is actually the next byte to be decoded. + * It can be non-zero to be used in multiple calls. + * + * If one entry is successfully decoded, sends it to callback and returns the + * result. + */ +int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed, + vpd_decode_callback callback, void *callback_arg); + +#endif /* __VPD_DECODE_H */ |