| 1 | |
| 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/io.h>
|
| 12 | #include <linux/kernel.h>
|
| 13 | #include <linux/device-id/coreboot.h>
|
| 14 | #include <linux/module.h>
|
| 15 |
|
| 16 | #include "memconsole.h"
|
| 17 | #include "coreboot_table.h"
|
| 18 |
|
| 19 | #define CB_TAG_CBMEM_CONSOLE 0x17
|
| 20 |
|
| 21 |
|
| 22 | struct cbmem_cons {
|
| 23 | u32 size_dont_access_after_boot;
|
| 24 | u32 cursor;
|
| 25 | u8 body[];
|
| 26 | } __packed;
|
| 27 |
|
| 28 | #define CURSOR_MASK ((1 << 28) - 1)
|
| 29 | #define OVERFLOW (1 << 31)
|
| 30 |
|
| 31 | static struct cbmem_cons *cbmem_console;
|
| 32 | static u32 cbmem_console_size;
|
| 33 |
|
| 34 | |
| 35 | * The cbmem_console structure is read again on every access because it may
|
| 36 | * change at any time if runtime firmware logs new messages. This may rarely
|
| 37 | * lead to race conditions where the firmware overwrites the beginning of the
|
| 38 | * ring buffer with more lines after we have already read |cursor|. It should be
|
| 39 | * rare and harmless enough that we don't spend extra effort working around it.
|
| 40 |
|
| 41 | static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count)
|
| 42 | {
|
| 43 | u32 cursor = cbmem_console->cursor & CURSOR_MASK;
|
| 44 | u32 flags = cbmem_console->cursor & ~CURSOR_MASK;
|
| 45 | u32 size = cbmem_console_size;
|
| 46 | struct seg {
|
| 47 | u32 phys;
|
| 48 | u32 len;
|
| 49 | } seg[2] = { {0}, {0} };
|
| 50 | size_t done = 0;
|
| 51 | int i;
|
| 52 |
|
| 53 | if (flags & OVERFLOW) {
|
| 54 | if (cursor > size)
|
| 55 | cursor = 0;
|
| 56 | seg[0] = (struct seg){.phys = cursor, .len = size - cursor};
|
| 57 | seg[1] = (struct seg){.phys = 0, .len = cursor};
|
| 58 | } else {
|
| 59 | seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)};
|
| 60 | }
|
| 61 |
|
| 62 | for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) {
|
| 63 | done += memory_read_from_buffer(buf + done, count - done, &pos,
|
| 64 | cbmem_console->body + seg[i].phys, seg[i].len);
|
| 65 | pos -= seg[i].len;
|
| 66 | }
|
| 67 | return done;
|
| 68 | }
|
| 69 |
|
| 70 | static int memconsole_probe(struct coreboot_device *dev)
|
| 71 | {
|
| 72 | struct cbmem_cons *tmp_cbmc;
|
| 73 |
|
| 74 | tmp_cbmc = memremap(dev->cbmem_ref.cbmem_addr,
|
| 75 | sizeof(*tmp_cbmc), MEMREMAP_WB);
|
| 76 |
|
| 77 | if (!tmp_cbmc)
|
| 78 | return -ENOMEM;
|
| 79 |
|
| 80 |
|
| 81 | cbmem_console_size = tmp_cbmc->size_dont_access_after_boot;
|
| 82 | cbmem_console = devm_memremap(&dev->dev, dev->cbmem_ref.cbmem_addr,
|
| 83 | cbmem_console_size + sizeof(*cbmem_console),
|
| 84 | MEMREMAP_WB);
|
| 85 | memunmap(tmp_cbmc);
|
| 86 |
|
| 87 | if (IS_ERR(cbmem_console))
|
| 88 | return PTR_ERR(cbmem_console);
|
| 89 |
|
| 90 | memconsole_setup(memconsole_coreboot_read);
|
| 91 |
|
| 92 | return memconsole_sysfs_init();
|
| 93 | }
|
| 94 |
|
| 95 | static void memconsole_remove(struct coreboot_device *dev)
|
| 96 | {
|
| 97 | memconsole_exit();
|
| 98 | }
|
| 99 |
|
| 100 | static const struct coreboot_device_id memconsole_ids[] = {
|
| 101 | { .tag = CB_TAG_CBMEM_CONSOLE },
|
| 102 | { }
|
| 103 | };
|
| 104 | MODULE_DEVICE_TABLE(coreboot, memconsole_ids);
|
| 105 |
|
| 106 | static struct coreboot_driver memconsole_driver = {
|
| 107 | .probe = memconsole_probe,
|
| 108 | .remove = memconsole_remove,
|
| 109 | .drv = {
|
| 110 | .name = "memconsole",
|
| 111 | },
|
| 112 | .id_table = memconsole_ids,
|
| 113 | };
|
| 114 | module_coreboot_driver(memconsole_driver);
|
| 115 |
|
| 116 | MODULE_AUTHOR("Google, Inc.");
|
| 117 | MODULE_DESCRIPTION("Memory based BIOS console accessed through coreboot table");
|
| 118 | MODULE_LICENSE("GPL");
|