1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * memconsole-coreboot.c 4 * 5 * Memory based BIOS console accessed through coreboot table. 6 * 7 * Copyright 2017 Google Inc. 8 */ 9 10 #include <linux/device.h> 11 #include <linux/kernel.h> 12 #include <linux/module.h> 13 14 #include "memconsole.h" 15 #include "coreboot_table.h" 16 17 #define CB_TAG_CBMEM_CONSOLE 0x17 18 19 /* CBMEM firmware console log descriptor. */ 20 struct cbmem_cons { 21 u32 size_dont_access_after_boot; 22 u32 cursor; 23 u8 body[0]; 24 } __packed; 25 26 #define CURSOR_MASK ((1 << 28) - 1) 27 #define OVERFLOW (1 << 31) 28 29 static struct cbmem_cons __iomem *cbmem_console; 30 static u32 cbmem_console_size; 31 32 /* 33 * The cbmem_console structure is read again on every access because it may 34 * change at any time if runtime firmware logs new messages. This may rarely 35 * lead to race conditions where the firmware overwrites the beginning of the 36 * ring buffer with more lines after we have already read |cursor|. It should be 37 * rare and harmless enough that we don't spend extra effort working around it. 38 */ 39 static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count) 40 { 41 u32 cursor = cbmem_console->cursor & CURSOR_MASK; 42 u32 flags = cbmem_console->cursor & ~CURSOR_MASK; 43 u32 size = cbmem_console_size; 44 struct seg { /* describes ring buffer segments in logical order */ 45 u32 phys; /* physical offset from start of mem buffer */ 46 u32 len; /* length of segment */ 47 } seg[2] = { {0}, {0} }; 48 size_t done = 0; 49 int i; 50 51 if (flags & OVERFLOW) { 52 if (cursor > size) /* Shouldn't really happen, but... */ 53 cursor = 0; 54 seg[0] = (struct seg){.phys = cursor, .len = size - cursor}; 55 seg[1] = (struct seg){.phys = 0, .len = cursor}; 56 } else { 57 seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)}; 58 } 59 60 for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) { 61 done += memory_read_from_buffer(buf + done, count - done, &pos, 62 cbmem_console->body + seg[i].phys, seg[i].len); 63 pos -= seg[i].len; 64 } 65 return done; 66 } 67 68 static int memconsole_probe(struct coreboot_device *dev) 69 { 70 struct cbmem_cons __iomem *tmp_cbmc; 71 72 tmp_cbmc = memremap(dev->cbmem_ref.cbmem_addr, 73 sizeof(*tmp_cbmc), MEMREMAP_WB); 74 75 if (!tmp_cbmc) 76 return -ENOMEM; 77 78 /* Read size only once to prevent overrun attack through /dev/mem. */ 79 cbmem_console_size = tmp_cbmc->size_dont_access_after_boot; 80 cbmem_console = memremap(dev->cbmem_ref.cbmem_addr, 81 cbmem_console_size + sizeof(*cbmem_console), 82 MEMREMAP_WB); 83 memunmap(tmp_cbmc); 84 85 if (!cbmem_console) 86 return -ENOMEM; 87 88 memconsole_setup(memconsole_coreboot_read); 89 90 return memconsole_sysfs_init(); 91 } 92 93 static int memconsole_remove(struct coreboot_device *dev) 94 { 95 memconsole_exit(); 96 97 if (cbmem_console) 98 memunmap(cbmem_console); 99 100 return 0; 101 } 102 103 static struct coreboot_driver memconsole_driver = { 104 .probe = memconsole_probe, 105 .remove = memconsole_remove, 106 .drv = { 107 .name = "memconsole", 108 }, 109 .tag = CB_TAG_CBMEM_CONSOLE, 110 }; 111 112 static void coreboot_memconsole_exit(void) 113 { 114 coreboot_driver_unregister(&memconsole_driver); 115 } 116 117 static int __init coreboot_memconsole_init(void) 118 { 119 return coreboot_driver_register(&memconsole_driver); 120 } 121 122 module_exit(coreboot_memconsole_exit); 123 module_init(coreboot_memconsole_init); 124 125 MODULE_AUTHOR("Google, Inc."); 126 MODULE_LICENSE("GPL"); 127