1 /*
2 * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 /*
8 * /dev/coreboot_console — read-only access to the CBMEM firmware console
9 *
10 * The CBMEM console is a ring buffer written by coreboot during boot.
11 * Bit 31 of the cursor field indicates overflow (the buffer has wrapped).
12 * When overflow is set, data starts at cursor and wraps around.
13 * When not set, data starts at offset 0 and ends at cursor.
14 */
15
16 #include <sys/systm.h>
17 #include <sys/bus.h>
18 #include <sys/conf.h>
19 #include <sys/kernel.h>
20 #include <sys/module.h>
21 #include <sys/uio.h>
22
23 #include <vm/vm.h>
24 #include <vm/vm_param.h>
25 #include <vm/pmap.h>
26
27 #include <dev/coreboot/coreboot.h>
28
29 static d_read_t coreboot_console_read;
30
31 static struct cdevsw coreboot_console_cdevsw = {
32 .d_version = D_VERSION,
33 .d_read = coreboot_console_read,
34 .d_name = "coreboot_console",
35 };
36
37 /*
38 * Read the console ring buffer.
39 *
40 * The ring buffer has a cursor that may have wrapped. If the OVERFLOW
41 * bit (bit 31) is set, the ring has wrapped and data goes from
42 * body[cursor..size-1] then body[0..cursor-1]. Otherwise it's just
43 * body[0..cursor-1].
44 *
45 * We linearize the buffer into a contiguous view for the uiomove.
46 */
47 static int
coreboot_console_read(struct cdev * dev,struct uio * uio,int ioflag)48 coreboot_console_read(struct cdev *dev, struct uio *uio, int ioflag)
49 {
50 struct coreboot_softc *sc;
51 struct cbmem_console *cons;
52 uint32_t cursor, flags, buf_size;
53 uint32_t data_start, data_len;
54 off_t offset;
55 size_t todo;
56 int error;
57
58 sc = dev->si_drv1;
59 if (sc->console_vaddr == NULL)
60 return (ENXIO);
61
62 cons = sc->console_vaddr;
63 cursor = cons->cursor & CBMEM_CONSOLE_CURSOR_MASK;
64 flags = cons->cursor & ~CBMEM_CONSOLE_CURSOR_MASK;
65 buf_size = sc->console_data_size;
66
67 if (cursor > buf_size)
68 cursor = 0;
69
70 if (flags & CBMEM_CONSOLE_OVERFLOW) {
71 /*
72 * Ring has wrapped. Logical order:
73 * segment 1: body[cursor .. buf_size-1] (oldest data)
74 * segment 2: body[0 .. cursor-1] (newest data)
75 * Total length = buf_size
76 */
77 data_len = buf_size;
78 offset = uio->uio_offset;
79
80 if (offset >= data_len)
81 return (0);
82
83 while (uio->uio_resid > 0 && offset < data_len) {
84 uint32_t seg_start, seg_len;
85 uint32_t seg1_len = buf_size - cursor;
86
87 if (offset < seg1_len) {
88 seg_start = cursor + offset;
89 seg_len = seg1_len - offset;
90 } else {
91 seg_start = offset - seg1_len;
92 seg_len = cursor - seg_start;
93 }
94
95 todo = MIN(uio->uio_resid, seg_len);
96 error = uiomove(cons->body + seg_start, todo, uio);
97 if (error != 0)
98 return (error);
99 offset += todo;
100 }
101 } else {
102 /*
103 * No overflow — data is linear: body[0 .. cursor-1]
104 */
105 data_start = 0;
106 data_len = cursor;
107 offset = uio->uio_offset;
108
109 if (offset >= data_len)
110 return (0);
111
112 todo = MIN(uio->uio_resid, data_len - offset);
113 error = uiomove(cons->body + data_start + offset, todo, uio);
114 if (error != 0)
115 return (error);
116 }
117
118 return (0);
119 }
120
121 int
coreboot_console_create(struct coreboot_softc * sc)122 coreboot_console_create(struct coreboot_softc *sc)
123 {
124 struct cbmem_console *tmp;
125 vm_size_t map_size;
126 uint32_t cons_size;
127
128 if (!sc->has_console || sc->console_paddr == 0)
129 return (ENXIO);
130
131 tmp = (struct cbmem_console *)pmap_mapbios(sc->console_paddr,
132 sizeof(struct cbmem_console));
133 if (tmp == NULL) {
134 device_printf(sc->dev,
135 "unable to map CBMEM console header\n");
136 return (ENOMEM);
137 }
138
139 cons_size = tmp->size;
140 pmap_unmapbios(tmp, sizeof(struct cbmem_console));
141 if (cons_size == 0 || cons_size > CB_MAX_CONSOLE_BYTES) {
142 device_printf(sc->dev, "invalid CBMEM console size: %u\n",
143 cons_size);
144 return (EINVAL);
145 }
146 map_size = sizeof(struct cbmem_console) + cons_size;
147
148 sc->console_vaddr = (struct cbmem_console *)pmap_mapbios(
149 sc->console_paddr, map_size);
150 if (sc->console_vaddr == NULL) {
151 device_printf(sc->dev,
152 "unable to map CBMEM console (%zu bytes)\n",
153 (size_t)map_size);
154 return (ENOMEM);
155 }
156 sc->console_size = map_size;
157 sc->console_data_size = cons_size;
158
159 struct make_dev_args args;
160 int error;
161
162 make_dev_args_init(&args);
163 args.mda_devsw = &coreboot_console_cdevsw;
164 args.mda_uid = UID_ROOT;
165 args.mda_gid = GID_WHEEL;
166 args.mda_mode = 0440;
167 args.mda_si_drv1 = sc;
168 error = make_dev_s(&args, &sc->console_cdev, "coreboot_console");
169 if (error != 0) {
170 pmap_unmapbios(sc->console_vaddr, sc->console_size);
171 sc->console_vaddr = NULL;
172 sc->console_size = 0;
173 sc->console_data_size = 0;
174 return (error);
175 }
176
177 device_printf(sc->dev,
178 "CBMEM console: %u bytes, cursor at %u%s\n",
179 sc->console_vaddr->size,
180 sc->console_vaddr->cursor & CBMEM_CONSOLE_CURSOR_MASK,
181 (sc->console_vaddr->cursor & CBMEM_CONSOLE_OVERFLOW) ?
182 " (wrapped)" : "");
183
184 return (0);
185 }
186
187 void
coreboot_console_destroy(struct coreboot_softc * sc)188 coreboot_console_destroy(struct coreboot_softc *sc)
189 {
190
191 coreboot_cdev_destroy(&sc->console_cdev);
192 }
193