1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3 * Copyright (c) 2023, Google Limited
4 */
5
6 #include <compiler.h>
7 #include <console.h>
8 #include <drivers/cbmem_console.h>
9 #include <drivers/serial.h>
10 #include <io.h>
11 #include <keep.h>
12 #include <kernel/dt.h>
13 #include <libfdt.h>
14 #include <mm/core_mmu.h>
15 #include <types_ext.h>
16 #include <util.h>
17
18 #define CURSOR_MASK (BIT(28) - 1)
19 #define OVERFLOW BIT(31)
20
21 struct cbmem_console {
22 uint32_t size;
23 uint32_t cursor;
24 uint8_t body[0];
25 };
26
27 struct cbmem_console_data {
28 paddr_t base;
29 struct cbmem_console *console;
30 struct serial_chip chip;
31 uint32_t size;
32 };
33
34 /*
35 * Structures describing coreboot's in-memory descriptor tables. See
36 * https://github.com/coreboot/coreboot/blob/ea2a38be323173075db3b13729a4006ea1fef72d/src/commonlib/include/commonlib/coreboot_tables.h
37 * for canonical implementation.
38 */
39
40 struct cb_header {
41 uint8_t signature[4];
42 uint32_t header_bytes;
43 uint32_t header_checksum;
44 uint32_t table_bytes;
45 uint32_t table_checksum;
46 uint32_t table_entries;
47 };
48
49 #define CB_TAG_CBMEM_CONSOLE 0x17
50
51 struct cb_entry {
52 uint32_t tag;
53 uint32_t size;
54 uint64_t value;
55 };
56
57 static struct cbmem_console_data cbmem_console;
58
cbmem_console_flush(struct serial_chip * chip __unused)59 static void cbmem_console_flush(struct serial_chip *chip __unused)
60 {
61 }
62
cbmem_console_getchar(struct serial_chip * chip __unused)63 static int cbmem_console_getchar(struct serial_chip *chip __unused)
64 {
65 return 0;
66 }
67
cbmem_console_have_rx_data(struct serial_chip * chip __unused)68 static bool cbmem_console_have_rx_data(struct serial_chip *chip __unused)
69 {
70 return false;
71 }
72
cbmem_console_putc(struct serial_chip * chip,int ch)73 static void cbmem_console_putc(struct serial_chip *chip, int ch)
74 {
75 struct cbmem_console_data *pd =
76 container_of(chip, struct cbmem_console_data, chip);
77 struct cbmem_console *c = pd->console;
78
79 if (!pd->size)
80 return;
81
82 if ((c->cursor & CURSOR_MASK) + 1 >= pd->size) {
83 c->cursor &= ~CURSOR_MASK;
84 c->cursor |= OVERFLOW;
85 c->body[0] = (uint8_t)(ch & 0xFF);
86 } else {
87 c->body[c->cursor & CURSOR_MASK] = (uint8_t)(ch & 0xFF);
88 c->cursor++;
89 }
90 }
91
92 static const struct serial_ops cbmem_console_ops = {
93 .flush = cbmem_console_flush,
94 .getchar = cbmem_console_getchar,
95 .have_rx_data = cbmem_console_have_rx_data,
96 .putc = cbmem_console_putc,
97 };
98 DECLARE_KEEP_PAGER(cbmem_console_ops);
99
get_cbmem_console_from_coreboot_table(paddr_t table_addr,size_t table_size)100 static paddr_t get_cbmem_console_from_coreboot_table(paddr_t table_addr,
101 size_t table_size)
102 {
103 struct cb_header *header = NULL;
104 void *ptr = NULL;
105 uint32_t i = 0;
106 struct cb_entry *entry = NULL;
107 paddr_t cbmem_console_base = 0;
108 void *base = NULL;
109
110 base = core_mmu_add_mapping(MEM_AREA_RAM_NSEC, table_addr, table_size);
111 if (!base)
112 return 0;
113
114 header = (struct cb_header *)base;
115 if (memcmp(header->signature, "LBIO", 4))
116 goto done;
117
118 if (header->header_bytes + header->table_bytes > table_size)
119 goto done;
120
121 ptr = (uint8_t *)base + header->header_bytes;
122 for (i = 0; i < header->table_entries; ++i) {
123 entry = (struct cb_entry *)ptr;
124 if ((uint8_t *)ptr >= (uint8_t *)base + table_size -
125 sizeof(struct cb_entry)) {
126 goto done;
127 }
128
129 switch (get_le32(&entry->tag)) {
130 case CB_TAG_CBMEM_CONSOLE:
131 cbmem_console_base = get_le64(&entry->value);
132 goto done;
133 default:
134 /* We skip all but one tag type. */
135 break;
136 }
137
138 ptr = (uint8_t *)ptr + get_le32(&entry->size);
139 }
140
141 done:
142 core_mmu_remove_mapping(MEM_AREA_RAM_NSEC, base, table_size);
143 return cbmem_console_base;
144 }
145
cbmem_console_init_from_dt(void * fdt)146 bool cbmem_console_init_from_dt(void *fdt)
147 {
148 int offset = 0;
149 paddr_t cb_addr = 0;
150 size_t cb_size = 0;
151 paddr_t cbmem_console_base = 0;
152
153 if (!fdt)
154 return false;
155
156 offset = fdt_path_offset(fdt, "/firmware/coreboot");
157 if (offset < 0)
158 return false;
159
160 if (fdt_reg_info(fdt, offset, &cb_addr, &cb_size))
161 return false;
162
163 cbmem_console_base = get_cbmem_console_from_coreboot_table(cb_addr,
164 cb_size);
165 if (!cbmem_console_base)
166 return false;
167
168 cbmem_console.base = cbmem_console_base;
169 cbmem_console.console = (struct cbmem_console *)
170 core_mmu_add_mapping(MEM_AREA_RAM_NSEC, cbmem_console_base,
171 sizeof(struct cbmem_console));
172 if (!cbmem_console.console)
173 return false;
174
175 /*
176 * Copy the size now to prevent non-secure world from spoofing
177 * it later.
178 */
179 cbmem_console.size = cbmem_console.console->size;
180 cbmem_console.chip.ops = &cbmem_console_ops;
181
182 register_serial_console(&cbmem_console.chip);
183 return true;
184 }
185