xref: /freebsd/sys/dev/coreboot/coreboot_console.c (revision 5f74217c05a99f38a31a6e4950220964595d4ac8)
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