diff options
Diffstat (limited to 'drivers/firmware')
-rw-r--r-- | drivers/firmware/efi/Kconfig | 9 | ||||
-rw-r--r-- | drivers/firmware/efi/arm-runtime.c | 16 | ||||
-rw-r--r-- | drivers/firmware/efi/capsule-loader.c | 117 | ||||
-rw-r--r-- | drivers/firmware/efi/capsule.c | 11 | ||||
-rw-r--r-- | drivers/firmware/efi/efi.c | 3 | ||||
-rw-r--r-- | drivers/firmware/efi/test/efi_test.c | 11 | ||||
-rw-r--r-- | drivers/firmware/google/memconsole-coreboot.c | 54 | ||||
-rw-r--r-- | drivers/firmware/google/memconsole-x86-legacy.c | 18 | ||||
-rw-r--r-- | drivers/firmware/google/memconsole.c | 14 | ||||
-rw-r--r-- | drivers/firmware/google/memconsole.h | 7 | ||||
-rw-r--r-- | drivers/firmware/google/vpd.c | 39 |
11 files changed, 172 insertions, 127 deletions
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index 2e78b0b96d74..394db40ed374 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -112,6 +112,15 @@ config EFI_CAPSULE_LOADER Most users should say N. +config EFI_CAPSULE_QUIRK_QUARK_CSH + boolean "Add support for Quark capsules with non-standard headers" + depends on X86 && !64BIT + select EFI_CAPSULE_LOADER + default y + help + Add support for processing Quark X1000 EFI capsules, whose header + layout deviates from the layout mandated by the UEFI specification. + config EFI_TEST tristate "EFI Runtime Service Tests Support" depends on EFI diff --git a/drivers/firmware/efi/arm-runtime.c b/drivers/firmware/efi/arm-runtime.c index 974c5a31a005..1cc41c3d6315 100644 --- a/drivers/firmware/efi/arm-runtime.c +++ b/drivers/firmware/efi/arm-runtime.c @@ -11,6 +11,7 @@ * */ +#include <linux/dmi.h> #include <linux/efi.h> #include <linux/io.h> #include <linux/memblock.h> @@ -166,3 +167,18 @@ void efi_virtmap_unload(void) efi_set_pgd(current->active_mm); preempt_enable(); } + + +static int __init arm_dmi_init(void) +{ + /* + * On arm64/ARM, DMI depends on UEFI, and dmi_scan_machine() needs to + * be called early because dmi_id_init(), which is an arch_initcall + * itself, depends on dmi_scan_machine() having been called already. + */ + dmi_scan_machine(); + if (dmi_available) + dmi_set_dump_stack_arch_desc(); + return 0; +} +core_initcall(arm_dmi_init); diff --git a/drivers/firmware/efi/capsule-loader.c b/drivers/firmware/efi/capsule-loader.c index 9ae6c116c474..ec8ac5c4dd84 100644 --- a/drivers/firmware/efi/capsule-loader.c +++ b/drivers/firmware/efi/capsule-loader.c @@ -20,15 +20,9 @@ #define NO_FURTHER_WRITE_ACTION -1 -struct capsule_info { - bool header_obtained; - int reset_type; - long index; - size_t count; - size_t total_size; - struct page **pages; - size_t page_bytes_remain; -}; +#ifndef phys_to_page +#define phys_to_page(x) pfn_to_page((x) >> PAGE_SHIFT) +#endif /** * efi_free_all_buff_pages - free all previous allocated buffer pages @@ -41,65 +35,70 @@ struct capsule_info { static void efi_free_all_buff_pages(struct capsule_info *cap_info) { while (cap_info->index > 0) - __free_page(cap_info->pages[--cap_info->index]); + __free_page(phys_to_page(cap_info->pages[--cap_info->index])); cap_info->index = NO_FURTHER_WRITE_ACTION; } -/** - * efi_capsule_setup_info - obtain the efi capsule header in the binary and - * setup capsule_info structure - * @cap_info: pointer to current instance of capsule_info structure - * @kbuff: a mapped first page buffer pointer - * @hdr_bytes: the total received number of bytes for efi header - **/ -static ssize_t efi_capsule_setup_info(struct capsule_info *cap_info, - void *kbuff, size_t hdr_bytes) +int __efi_capsule_setup_info(struct capsule_info *cap_info) { - efi_capsule_header_t *cap_hdr; size_t pages_needed; int ret; void *temp_page; - /* Only process data block that is larger than efi header size */ - if (hdr_bytes < sizeof(efi_capsule_header_t)) - return 0; - - /* Reset back to the correct offset of header */ - cap_hdr = kbuff - cap_info->count; - pages_needed = ALIGN(cap_hdr->imagesize, PAGE_SIZE) >> PAGE_SHIFT; + pages_needed = ALIGN(cap_info->total_size, PAGE_SIZE) / PAGE_SIZE; if (pages_needed == 0) { - pr_err("%s: pages count invalid\n", __func__); + pr_err("invalid capsule size"); return -EINVAL; } /* Check if the capsule binary supported */ - ret = efi_capsule_supported(cap_hdr->guid, cap_hdr->flags, - cap_hdr->imagesize, + ret = efi_capsule_supported(cap_info->header.guid, + cap_info->header.flags, + cap_info->header.imagesize, &cap_info->reset_type); if (ret) { - pr_err("%s: efi_capsule_supported() failed\n", - __func__); + pr_err("capsule not supported\n"); return ret; } - cap_info->total_size = cap_hdr->imagesize; temp_page = krealloc(cap_info->pages, pages_needed * sizeof(void *), GFP_KERNEL | __GFP_ZERO); - if (!temp_page) { - pr_debug("%s: krealloc() failed\n", __func__); + if (!temp_page) return -ENOMEM; - } cap_info->pages = temp_page; - cap_info->header_obtained = true; return 0; } /** + * efi_capsule_setup_info - obtain the efi capsule header in the binary and + * setup capsule_info structure + * @cap_info: pointer to current instance of capsule_info structure + * @kbuff: a mapped first page buffer pointer + * @hdr_bytes: the total received number of bytes for efi header + * + * Platforms with non-standard capsule update mechanisms can override + * this __weak function so they can perform any required capsule + * image munging. See quark_quirk_function() for an example. + **/ +int __weak efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff, + size_t hdr_bytes) +{ + /* Only process data block that is larger than efi header size */ + if (hdr_bytes < sizeof(efi_capsule_header_t)) + return 0; + + memcpy(&cap_info->header, kbuff, sizeof(cap_info->header)); + cap_info->total_size = cap_info->header.imagesize; + + return __efi_capsule_setup_info(cap_info); +} + +/** * efi_capsule_submit_update - invoke the efi_capsule_update API once binary * upload done * @cap_info: pointer to current instance of capsule_info structure @@ -107,26 +106,17 @@ static ssize_t efi_capsule_setup_info(struct capsule_info *cap_info, static ssize_t efi_capsule_submit_update(struct capsule_info *cap_info) { int ret; - void *cap_hdr_temp; - - cap_hdr_temp = vmap(cap_info->pages, cap_info->index, - VM_MAP, PAGE_KERNEL); - if (!cap_hdr_temp) { - pr_debug("%s: vmap() failed\n", __func__); - return -EFAULT; - } - ret = efi_capsule_update(cap_hdr_temp, cap_info->pages); - vunmap(cap_hdr_temp); + ret = efi_capsule_update(&cap_info->header, cap_info->pages); if (ret) { - pr_err("%s: efi_capsule_update() failed\n", __func__); + pr_err("capsule update failed\n"); return ret; } /* Indicate capsule binary uploading is done */ cap_info->index = NO_FURTHER_WRITE_ACTION; - pr_info("%s: Successfully upload capsule file with reboot type '%s'\n", - __func__, !cap_info->reset_type ? "RESET_COLD" : + pr_info("Successfully upload capsule file with reboot type '%s'\n", + !cap_info->reset_type ? "RESET_COLD" : cap_info->reset_type == 1 ? "RESET_WARM" : "RESET_SHUTDOWN"); return 0; @@ -171,37 +161,30 @@ static ssize_t efi_capsule_write(struct file *file, const char __user *buff, if (!cap_info->page_bytes_remain) { page = alloc_page(GFP_KERNEL); if (!page) { - pr_debug("%s: alloc_page() failed\n", __func__); ret = -ENOMEM; goto failed; } - cap_info->pages[cap_info->index++] = page; + cap_info->pages[cap_info->index++] = page_to_phys(page); cap_info->page_bytes_remain = PAGE_SIZE; + } else { + page = phys_to_page(cap_info->pages[cap_info->index - 1]); } - page = cap_info->pages[cap_info->index - 1]; - kbuff = kmap(page); - if (!kbuff) { - pr_debug("%s: kmap() failed\n", __func__); - ret = -EFAULT; - goto failed; - } kbuff += PAGE_SIZE - cap_info->page_bytes_remain; /* Copy capsule binary data from user space to kernel space buffer */ write_byte = min_t(size_t, count, cap_info->page_bytes_remain); if (copy_from_user(kbuff, buff, write_byte)) { - pr_debug("%s: copy_from_user() failed\n", __func__); ret = -EFAULT; goto fail_unmap; } cap_info->page_bytes_remain -= write_byte; /* Setup capsule binary info structure */ - if (!cap_info->header_obtained) { - ret = efi_capsule_setup_info(cap_info, kbuff, + if (cap_info->header.headersize == 0) { + ret = efi_capsule_setup_info(cap_info, kbuff - cap_info->count, cap_info->count + write_byte); if (ret) goto fail_unmap; @@ -211,11 +194,10 @@ static ssize_t efi_capsule_write(struct file *file, const char __user *buff, kunmap(page); /* Submit the full binary to efi_capsule_update() API */ - if (cap_info->header_obtained && + if (cap_info->header.headersize > 0 && cap_info->count >= cap_info->total_size) { if (cap_info->count > cap_info->total_size) { - pr_err("%s: upload size exceeded header defined size\n", - __func__); + pr_err("capsule upload size exceeded header defined size\n"); ret = -EINVAL; goto failed; } @@ -249,7 +231,7 @@ static int efi_capsule_flush(struct file *file, fl_owner_t id) struct capsule_info *cap_info = file->private_data; if (cap_info->index > 0) { - pr_err("%s: capsule upload not complete\n", __func__); + pr_err("capsule upload not complete\n"); efi_free_all_buff_pages(cap_info); ret = -ECANCELED; } @@ -328,8 +310,7 @@ static int __init efi_capsule_loader_init(void) ret = misc_register(&efi_capsule_misc); if (ret) - pr_err("%s: Failed to register misc char file note\n", - __func__); + pr_err("Unable to register capsule loader device\n"); return ret; } diff --git a/drivers/firmware/efi/capsule.c b/drivers/firmware/efi/capsule.c index 6eedff45e6d7..901b9306bf94 100644 --- a/drivers/firmware/efi/capsule.c +++ b/drivers/firmware/efi/capsule.c @@ -214,7 +214,7 @@ efi_capsule_update_locked(efi_capsule_header_t *capsule, * * Return 0 on success, a converted EFI status code on failure. */ -int efi_capsule_update(efi_capsule_header_t *capsule, struct page **pages) +int efi_capsule_update(efi_capsule_header_t *capsule, phys_addr_t *pages) { u32 imagesize = capsule->imagesize; efi_guid_t guid = capsule->guid; @@ -247,16 +247,13 @@ int efi_capsule_update(efi_capsule_header_t *capsule, struct page **pages) efi_capsule_block_desc_t *sglist; sglist = kmap(sg_pages[i]); - if (!sglist) { - rv = -ENOMEM; - goto out; - } for (j = 0; j < SGLIST_PER_PAGE && count > 0; j++) { - u64 sz = min_t(u64, imagesize, PAGE_SIZE); + u64 sz = min_t(u64, imagesize, + PAGE_SIZE - (u64)*pages % PAGE_SIZE); sglist[j].length = sz; - sglist[j].data = page_to_phys(*pages++); + sglist[j].data = *pages++; imagesize -= sz; count--; diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index b372aad3b449..045d6d311bde 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -528,7 +528,8 @@ int __init efi_config_parse_tables(void *config_tables, int count, int sz, } } - efi_memattr_init(); + if (efi_enabled(EFI_MEMMAP)) + efi_memattr_init(); /* Parse the EFI Properties table if it exists */ if (efi.properties_table != EFI_INVALID_TABLE_ADDR) { diff --git a/drivers/firmware/efi/test/efi_test.c b/drivers/firmware/efi/test/efi_test.c index 8cd578f62059..08129b7b80ab 100644 --- a/drivers/firmware/efi/test/efi_test.c +++ b/drivers/firmware/efi/test/efi_test.c @@ -71,18 +71,13 @@ copy_ucs2_from_user_len(efi_char16_t **dst, efi_char16_t __user *src, if (!access_ok(VERIFY_READ, src, 1)) return -EFAULT; - buf = kmalloc(len, GFP_KERNEL); - if (!buf) { + buf = memdup_user(src, len); + if (IS_ERR(buf)) { *dst = NULL; - return -ENOMEM; + return PTR_ERR(buf); } *dst = buf; - if (copy_from_user(*dst, src, len)) { - kfree(buf); - return -EFAULT; - } - return 0; } diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c index 02711114dece..52738887735c 100644 --- a/drivers/firmware/google/memconsole-coreboot.c +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -26,12 +26,52 @@ /* CBMEM firmware console log descriptor. */ struct cbmem_cons { - u32 buffer_size; - u32 buffer_cursor; - u8 buffer_body[0]; + u32 size_dont_access_after_boot; + u32 cursor; + u8 body[0]; } __packed; +#define CURSOR_MASK ((1 << 28) - 1) +#define OVERFLOW (1 << 31) + static struct cbmem_cons __iomem *cbmem_console; +static u32 cbmem_console_size; + +/* + * The cbmem_console structure is read again on every access because it may + * change at any time if runtime firmware logs new messages. This may rarely + * lead to race conditions where the firmware overwrites the beginning of the + * ring buffer with more lines after we have already read |cursor|. It should be + * rare and harmless enough that we don't spend extra effort working around it. + */ +static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count) +{ + u32 cursor = cbmem_console->cursor & CURSOR_MASK; + u32 flags = cbmem_console->cursor & ~CURSOR_MASK; + u32 size = cbmem_console_size; + struct seg { /* describes ring buffer segments in logical order */ + u32 phys; /* physical offset from start of mem buffer */ + u32 len; /* length of segment */ + } seg[2] = { {0}, {0} }; + size_t done = 0; + int i; + + if (flags & OVERFLOW) { + if (cursor > size) /* Shouldn't really happen, but... */ + cursor = 0; + seg[0] = (struct seg){.phys = cursor, .len = size - cursor}; + seg[1] = (struct seg){.phys = 0, .len = cursor}; + } else { + seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)}; + } + + for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) { + done += memory_read_from_buffer(buf + done, count - done, &pos, + cbmem_console->body + seg[i].phys, seg[i].len); + pos -= seg[i].len; + } + return done; +} static int memconsole_coreboot_init(phys_addr_t physaddr) { @@ -42,17 +82,17 @@ static int memconsole_coreboot_init(phys_addr_t physaddr) if (!tmp_cbmc) return -ENOMEM; + /* Read size only once to prevent overrun attack through /dev/mem. */ + cbmem_console_size = tmp_cbmc->size_dont_access_after_boot; cbmem_console = memremap(physaddr, - tmp_cbmc->buffer_size + sizeof(*cbmem_console), + cbmem_console_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)); - + memconsole_setup(memconsole_coreboot_read); return 0; } diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c index 1f279ee883b9..8c1bf6dbdaa6 100644 --- a/drivers/firmware/google/memconsole-x86-legacy.c +++ b/drivers/firmware/google/memconsole-x86-legacy.c @@ -48,6 +48,15 @@ struct biosmemcon_ebda { }; } __packed; +static char *memconsole_baseaddr; +static size_t memconsole_length; + +static ssize_t memconsole_read(char *buf, loff_t pos, size_t count) +{ + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); +} + static void found_v1_header(struct biosmemcon_ebda *hdr) { pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n", @@ -56,7 +65,9 @@ static void found_v1_header(struct biosmemcon_ebda *hdr) 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); + memconsole_baseaddr = phys_to_virt(hdr->v1.buffer_addr); + memconsole_length = hdr->v1.num_chars; + memconsole_setup(memconsole_read); } static void found_v2_header(struct biosmemcon_ebda *hdr) @@ -67,8 +78,9 @@ static void found_v2_header(struct biosmemcon_ebda *hdr) 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); + memconsole_baseaddr = phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start); + memconsole_length = hdr->v2.end - hdr->v2.start; + memconsole_setup(memconsole_read); } /* diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c index 94e200ddb4fa..166f07c68c02 100644 --- a/drivers/firmware/google/memconsole.c +++ b/drivers/firmware/google/memconsole.c @@ -22,15 +22,15 @@ #include "memconsole.h" -static char *memconsole_baseaddr; -static size_t memconsole_length; +static ssize_t (*memconsole_read_func)(char *, loff_t, size_t); static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { - return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, - memconsole_length); + if (WARN_ON_ONCE(!memconsole_read_func)) + return -EIO; + return memconsole_read_func(buf, pos, count); } static struct bin_attribute memconsole_bin_attr = { @@ -38,16 +38,14 @@ static struct bin_attribute memconsole_bin_attr = { .read = memconsole_read, }; -void memconsole_setup(void *baseaddr, size_t length) +void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t)) { - memconsole_baseaddr = baseaddr; - memconsole_length = length; + memconsole_read_func = read_func; } EXPORT_SYMBOL(memconsole_setup); int memconsole_sysfs_init(void) { - memconsole_bin_attr.size = memconsole_length; return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); } EXPORT_SYMBOL(memconsole_sysfs_init); diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h index 190fc03a51ae..ff1592dc7d1a 100644 --- a/drivers/firmware/google/memconsole.h +++ b/drivers/firmware/google/memconsole.h @@ -18,13 +18,14 @@ #ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H #define __FIRMWARE_GOOGLE_MEMCONSOLE_H +#include <linux/types.h> + /* * memconsole_setup * - * Initialize the memory console from raw (virtual) base - * address and length. + * Initialize the memory console, passing the function to handle read accesses. */ -void memconsole_setup(void *baseaddr, size_t length); +void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t)); /* * memconsole_sysfs_init diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index 31058d400bda..78945729388e 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -118,14 +118,13 @@ static int vpd_section_attrib_add(const u8 *key, s32 key_len, info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - info->key = kzalloc(key_len + 1, GFP_KERNEL); + + info->key = kstrndup(key, key_len, GFP_KERNEL); if (!info->key) { ret = -ENOMEM; goto free_info; } - 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; @@ -191,8 +190,7 @@ static int vpd_section_create_attribs(struct vpd_section *sec) static int vpd_section_init(const char *name, struct vpd_section *sec, phys_addr_t physaddr, size_t size) { - int ret; - int raw_len; + int err; sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); if (!sec->baseaddr) @@ -201,10 +199,11 @@ static int vpd_section_init(const char *name, struct vpd_section *sec, 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); + sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name); + if (!sec->raw_name) { + err = -ENOMEM; + goto err_iounmap; + } sysfs_bin_attr_init(&sec->bin_attr); sec->bin_attr.attr.name = sec->raw_name; @@ -213,14 +212,14 @@ static int vpd_section_init(const char *name, struct vpd_section *sec, 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; + err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); + if (err) + goto err_free_raw_name; sec->kobj = kobject_create_and_add(name, vpd_kobj); if (!sec->kobj) { - ret = -EINVAL; - goto sysfs_remove; + err = -EINVAL; + goto err_sysfs_remove; } INIT_LIST_HEAD(&sec->attribs); @@ -230,14 +229,13 @@ static int vpd_section_init(const char *name, struct vpd_section *sec, return 0; -sysfs_remove: +err_sysfs_remove: sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); - -free_sec: +err_free_raw_name: kfree(sec->raw_name); +err_iounmap: iounmap(sec->baseaddr); - - return ret; + return err; } static int vpd_section_destroy(struct vpd_section *sec) @@ -319,9 +317,6 @@ static int __init vpd_platform_init(void) 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; |