Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 * memconsole-coreboot.c
  3 *
  4 * Memory based BIOS console accessed through coreboot table.
  5 *
  6 * Copyright 2017 Google Inc.
  7 *
  8 * This program is free software; you can redistribute it and/or modify
  9 * it under the terms of the GNU General Public License v2.0 as published by
 10 * the Free Software Foundation.
 11 *
 12 * This program is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15 * GNU General Public License for more details.
 16 */
 17
 18#include <linux/kernel.h>
 19#include <linux/module.h>
 20#include <linux/platform_device.h>
 21
 22#include "memconsole.h"
 23#include "coreboot_table.h"
 24
 25#define CB_TAG_CBMEM_CONSOLE	0x17
 26
 27/* CBMEM firmware console log descriptor. */
 28struct cbmem_cons {
 29	u32 size_dont_access_after_boot;
 30	u32 cursor;
 31	u8  body[0];
 32} __packed;
 33
 34#define CURSOR_MASK ((1 << 28) - 1)
 35#define OVERFLOW (1 << 31)
 36
 37static struct cbmem_cons __iomem *cbmem_console;
 38static u32 cbmem_console_size;
 39
 40/*
 41 * The cbmem_console structure is read again on every access because it may
 42 * change at any time if runtime firmware logs new messages. This may rarely
 43 * lead to race conditions where the firmware overwrites the beginning of the
 44 * ring buffer with more lines after we have already read |cursor|. It should be
 45 * rare and harmless enough that we don't spend extra effort working around it.
 46 */
 47static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count)
 48{
 49	u32 cursor = cbmem_console->cursor & CURSOR_MASK;
 50	u32 flags = cbmem_console->cursor & ~CURSOR_MASK;
 51	u32 size = cbmem_console_size;
 52	struct seg {	/* describes ring buffer segments in logical order */
 53		u32 phys;	/* physical offset from start of mem buffer */
 54		u32 len;	/* length of segment */
 55	} seg[2] = { {0}, {0} };
 56	size_t done = 0;
 57	int i;
 58
 59	if (flags & OVERFLOW) {
 60		if (cursor > size)	/* Shouldn't really happen, but... */
 61			cursor = 0;
 62		seg[0] = (struct seg){.phys = cursor, .len = size - cursor};
 63		seg[1] = (struct seg){.phys = 0, .len = cursor};
 64	} else {
 65		seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)};
 66	}
 67
 68	for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) {
 69		done += memory_read_from_buffer(buf + done, count - done, &pos,
 70			cbmem_console->body + seg[i].phys, seg[i].len);
 71		pos -= seg[i].len;
 72	}
 73	return done;
 74}
 75
 76static int memconsole_coreboot_init(phys_addr_t physaddr)
 77{
 78	struct cbmem_cons __iomem *tmp_cbmc;
 79
 80	tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB);
 81
 82	if (!tmp_cbmc)
 83		return -ENOMEM;
 84
 85	/* Read size only once to prevent overrun attack through /dev/mem. */
 86	cbmem_console_size = tmp_cbmc->size_dont_access_after_boot;
 87	cbmem_console = memremap(physaddr,
 88				 cbmem_console_size + sizeof(*cbmem_console),
 89				 MEMREMAP_WB);
 90	memunmap(tmp_cbmc);
 91
 92	if (!cbmem_console)
 93		return -ENOMEM;
 94
 95	memconsole_setup(memconsole_coreboot_read);
 96	return 0;
 97}
 98
 99static int memconsole_probe(struct platform_device *pdev)
100{
101	int ret;
102	struct lb_cbmem_ref entry;
103
104	ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry));
105	if (ret)
106		return ret;
107
108	ret = memconsole_coreboot_init(entry.cbmem_addr);
109	if (ret)
110		return ret;
111
112	return memconsole_sysfs_init();
113}
114
115static int memconsole_remove(struct platform_device *pdev)
116{
117	memconsole_exit();
118
119	if (cbmem_console)
120		memunmap(cbmem_console);
121
122	return 0;
123}
124
125static struct platform_driver memconsole_driver = {
126	.probe = memconsole_probe,
127	.remove = memconsole_remove,
128	.driver = {
129		.name = "memconsole",
130	},
131};
132
133static int __init platform_memconsole_init(void)
134{
135	struct platform_device *pdev;
136
137	pdev = platform_device_register_simple("memconsole", -1, NULL, 0);
138	if (IS_ERR(pdev))
139		return PTR_ERR(pdev);
140
141	platform_driver_register(&memconsole_driver);
142
143	return 0;
144}
145
146module_init(platform_memconsole_init);
147
148MODULE_AUTHOR("Google, Inc.");
149MODULE_LICENSE("GPL");