xref: /openbsd/sys/arch/arm64/dev/smbios.c (revision 09467b48)
1 /*	$OpenBSD: smbios.c,v 1.5 2020/05/29 04:42:23 deraadt Exp $	*/
2 /*
3  * Copyright (c) 2006 Gordon Willem Klok <gklok@cogeco.ca>
4  * Copyright (c) 2019 Mark Kettenis <kettenis@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22 #include <sys/systm.h>
23 
24 #include <machine/bus.h>
25 #include <machine/fdt.h>
26 #include <machine/smbiosvar.h>
27 
28 #include <dev/ofw/fdt.h>
29 
30 struct smbios_entry smbios_entry;
31 /*
32  * used by hw_sysctl
33  */
34 extern char *hw_vendor, *hw_prod, *hw_uuid, *hw_serial, *hw_ver;
35 
36 const char *smbios_uninfo[] = {
37 	"System",
38 	"Not ",
39 	"To be",
40 	"SYS-"
41 };
42 
43 char smbios_bios_date[64];
44 char smbios_board_vendor[64];
45 char smbios_board_prod[64];
46 char smbios_board_serial[64];
47 
48 void smbios_info(char *);
49 char *fixstring(char *);
50 
51 struct smbios_softc {
52 	struct device	sc_dev;
53 	bus_space_tag_t	sc_iot;
54 };
55 
56 int	smbios_match(struct device *, void *, void *);
57 void	smbios_attach(struct device *, struct device *, void *);
58 
59 struct cfattach smbios_ca = {
60 	sizeof(struct device), smbios_match, smbios_attach
61 };
62 
63 struct cfdriver smbios_cd = {
64 	NULL, "smbios", DV_DULL
65 };
66 
67 int
68 smbios_match(struct device *parent, void *match, void *aux)
69 {
70 	struct fdt_attach_args *faa = aux;
71 
72 	return (strcmp(faa->fa_name, "smbios") == 0);
73 }
74 
75 void
76 smbios_attach(struct device *parent, struct device *self, void *aux)
77 {
78 	struct smbios_softc *sc = (struct smbios_softc *)self;
79 	struct fdt_attach_args *faa = aux;
80 	struct smbios_struct_bios *sb;
81 	struct smbtable bios;
82 	char scratch[64];
83 	char *sminfop;
84 	bus_addr_t addr;
85 	bus_size_t size;
86 	bus_space_handle_t ioh;
87 	struct smb3hdr *hdr;
88 	uint8_t *p, checksum = 0;
89 	int i;
90 
91 	sc->sc_iot = faa->fa_iot;
92 	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, sizeof(*hdr),
93 	    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE, &ioh)) {
94 		printf(": can't map SMBIOS entry point structure\n");
95 		return;
96 	}
97 
98 	hdr = bus_space_vaddr(sc->sc_iot, ioh);
99 	if (strncmp(hdr->sig, "_SM3_", sizeof(hdr->sig)) != 0)
100 		goto fail;
101 	if (hdr->len != sizeof(*hdr) || hdr->epr != 0x01)
102 		goto fail;
103 	for (i = 0, p = (uint8_t *)hdr; i < hdr->len; i++)
104 		checksum += p[i];
105 	if (checksum != 0)
106 		goto fail;
107 
108 	printf(": SMBIOS %d.%d.%d", hdr->majrev, hdr->minrev, hdr->docrev);
109 
110 	smbios_entry.len = hdr->size;
111 	smbios_entry.mjr = hdr->majrev;
112 	smbios_entry.min = hdr->minrev;
113 	smbios_entry.count = -1;
114 
115 	addr = hdr->addr;
116 	size = hdr->size;
117 
118 	bus_space_unmap(sc->sc_iot, ioh, sizeof(*hdr));
119 
120 	if (bus_space_map(sc->sc_iot, addr, size,
121 	    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE, &ioh)) {
122 		printf(": can't map SMBIOS structure table\n");
123 		return;
124 	}
125 	smbios_entry.addr = bus_space_vaddr(sc->sc_iot, ioh);
126 
127 	bios.cookie = 0;
128 	if (smbios_find_table(SMBIOS_TYPE_BIOS, &bios)) {
129 		sb = bios.tblhdr;
130 		printf("\n%s:", sc->sc_dev.dv_xname);
131 		if ((smbios_get_string(&bios, sb->vendor,
132 		    scratch, sizeof(scratch))) != NULL)
133 			printf(" vendor %s",
134 			    fixstring(scratch));
135 		if ((smbios_get_string(&bios, sb->version,
136 		    scratch, sizeof(scratch))) != NULL)
137 			printf(" version \"%s\"",
138 			    fixstring(scratch));
139 		if ((smbios_get_string(&bios, sb->release,
140 		    scratch, sizeof(scratch))) != NULL) {
141 			sminfop = fixstring(scratch);
142 			if (sminfop != NULL) {
143 				strlcpy(smbios_bios_date,
144 				    sminfop,
145 				    sizeof(smbios_bios_date));
146 				printf(" date %s", sminfop);
147 			}
148 		}
149 
150 		smbios_info(sc->sc_dev.dv_xname);
151 	}
152 
153 	bus_space_unmap(sc->sc_iot, ioh, size);
154 
155 	printf("\n");
156 	return;
157 
158 fail:
159 	bus_space_unmap(sc->sc_iot, ioh, sizeof(*hdr));
160 }
161 
162 /*
163  * smbios_find_table() takes a caller supplied smbios struct type and
164  * a pointer to a handle (struct smbtable) returning one if the structure
165  * is successfully located and zero otherwise. Callers should take care
166  * to initialize the cookie field of the smbtable structure to zero before
167  * the first invocation of this function.
168  * Multiple tables of the same type can be located by repeatedly calling
169  * smbios_find_table with the same arguments.
170  */
171 int
172 smbios_find_table(uint8_t type, struct smbtable *st)
173 {
174 	uint8_t *va, *end;
175 	struct smbtblhdr *hdr;
176 	int ret = 0, tcount = 1;
177 
178 	va = smbios_entry.addr;
179 	end = va + smbios_entry.len;
180 
181 	/*
182 	 * The cookie field of the smtable structure is used to locate
183 	 * multiple instances of a table of an arbitrary type. Following the
184 	 * successful location of a table, the type is encoded as bits 0:7 of
185 	 * the cookie value, the offset in terms of the number of structures
186 	 * preceding that referenced by the handle is encoded in bits 15:31.
187 	 */
188 	if ((st->cookie & 0xfff) == type && st->cookie >> 16) {
189 		if ((uint8_t *)st->hdr >= va && (uint8_t *)st->hdr < end) {
190 			hdr = st->hdr;
191 			if (hdr->type == type) {
192 				va = (uint8_t *)hdr + hdr->size;
193 				for (; va + 1 < end; va++)
194 					if (*va == 0 && *(va + 1) == 0)
195 						break;
196 				va += 2;
197 				tcount = st->cookie >> 16;
198 			}
199 		}
200 	}
201 	for (; va + sizeof(struct smbtblhdr) < end &&
202 	    tcount <= smbios_entry.count; tcount++) {
203 		hdr = (struct smbtblhdr *)va;
204 		if (hdr->type == type) {
205 			ret = 1;
206 			st->hdr = hdr;
207 			st->tblhdr = va + sizeof(struct smbtblhdr);
208 			st->cookie = (tcount + 1) << 16 | type;
209 			break;
210 		}
211 		if (hdr->type == SMBIOS_TYPE_EOT)
212 			break;
213 		va += hdr->size;
214 		for (; va + 1 < end; va++)
215 			if (*va == 0 && *(va + 1) == 0)
216 				break;
217 		va += 2;
218 	}
219 	return ret;
220 }
221 
222 char *
223 smbios_get_string(struct smbtable *st, uint8_t indx, char *dest, size_t len)
224 {
225 	uint8_t *va, *end;
226 	char *ret = NULL;
227 	int i;
228 
229 	va = (uint8_t *)st->hdr + st->hdr->size;
230 	end = smbios_entry.addr + smbios_entry.len;
231 	for (i = 1; va < end && i < indx && *va; i++)
232 		while (*va++)
233 			;
234 	if (i == indx) {
235 		if (va + len < end) {
236 			ret = dest;
237 			memcpy(ret, va, len);
238 			ret[len - 1] = '\0';
239 		}
240 	}
241 
242 	return ret;
243 }
244 
245 char *
246 fixstring(char *s)
247 {
248 	char *p, *e;
249 	int i;
250 
251 	for (i = 0; i < nitems(smbios_uninfo); i++)
252 		if ((strncasecmp(s, smbios_uninfo[i],
253 		    strlen(smbios_uninfo[i]))) == 0)
254 			return NULL;
255 	/*
256 	 * Remove leading and trailing whitespace
257 	 */
258 	for (p = s; *p == ' '; p++)
259 		;
260 	/*
261 	 * Special case entire string is whitespace
262 	 */
263 	if (p == s + strlen(s))
264 		return NULL;
265 	for (e = s + strlen(s) - 1; e > s && *e == ' '; e--)
266 		;
267 	if (p > s || e < s + strlen(s) - 1) {
268 		memmove(s, p, e - p + 1);
269 		s[e - p + 1] = '\0';
270 	}
271 
272 	return s;
273 }
274 
275 void
276 smbios_info(char *str)
277 {
278 	char *sminfop, sminfo[64];
279 	struct smbtable stbl, btbl;
280 	struct smbios_sys *sys;
281 	struct smbios_board *board;
282 	int i, infolen, uuidf, havebb;
283 	char *p;
284 
285 	if (smbios_entry.mjr < 2)
286 		return;
287 	/*
288 	 * According to the spec the system table among others is required,
289 	 * if it is not we do not bother with this smbios implementation.
290 	 */
291 	stbl.cookie = btbl.cookie = 0;
292 	if (!smbios_find_table(SMBIOS_TYPE_SYSTEM, &stbl))
293 		return;
294 	havebb = smbios_find_table(SMBIOS_TYPE_BASEBOARD, &btbl);
295 
296 	sys = (struct smbios_sys *)stbl.tblhdr;
297 	if (havebb) {
298 		board = (struct smbios_board *)btbl.tblhdr;
299 
300 		sminfop = NULL;
301 		if ((p = smbios_get_string(&btbl, board->vendor,
302 		    sminfo, sizeof(sminfo))) != NULL)
303 			sminfop = fixstring(p);
304 		if (sminfop)
305 			strlcpy(smbios_board_vendor, sminfop,
306 			    sizeof(smbios_board_vendor));
307 
308 		sminfop = NULL;
309 		if ((p = smbios_get_string(&btbl, board->product,
310 		    sminfo, sizeof(sminfo))) != NULL)
311 			sminfop = fixstring(p);
312 		if (sminfop)
313 			strlcpy(smbios_board_prod, sminfop,
314 			    sizeof(smbios_board_prod));
315 
316 		sminfop = NULL;
317 		if ((p = smbios_get_string(&btbl, board->serial,
318 		    sminfo, sizeof(sminfo))) != NULL)
319 			sminfop = fixstring(p);
320 		if (sminfop)
321 			strlcpy(smbios_board_serial, sminfop,
322 			    sizeof(smbios_board_serial));
323 	}
324 	/*
325 	 * Some smbios implementations have no system vendor or
326 	 * product strings, some have very uninformative data which is
327 	 * harder to work around and we must rely upon various
328 	 * heuristics to detect this. In both cases we attempt to fall
329 	 * back on the base board information in the perhaps naive
330 	 * belief that motherboard vendors will supply this
331 	 * information.
332 	 */
333 	sminfop = NULL;
334 	if ((p = smbios_get_string(&stbl, sys->vendor, sminfo,
335 	    sizeof(sminfo))) != NULL)
336 		sminfop = fixstring(p);
337 	if (sminfop == NULL) {
338 		if (havebb) {
339 			if ((p = smbios_get_string(&btbl, board->vendor,
340 			    sminfo, sizeof(sminfo))) != NULL)
341 				sminfop = fixstring(p);
342 		}
343 	}
344 	if (sminfop) {
345 		infolen = strlen(sminfop) + 1;
346 		hw_vendor = malloc(infolen, M_DEVBUF, M_NOWAIT);
347 		if (hw_vendor)
348 			strlcpy(hw_vendor, sminfop, infolen);
349 		sminfop = NULL;
350 	}
351 	if ((p = smbios_get_string(&stbl, sys->product, sminfo,
352 	    sizeof(sminfo))) != NULL)
353 		sminfop = fixstring(p);
354 	if (sminfop == NULL) {
355 		if (havebb) {
356 			if ((p = smbios_get_string(&btbl, board->product,
357 			    sminfo, sizeof(sminfo))) != NULL)
358 				sminfop = fixstring(p);
359 		}
360 	}
361 	if (sminfop) {
362 		infolen = strlen(sminfop) + 1;
363 		hw_prod = malloc(infolen, M_DEVBUF, M_NOWAIT);
364 		if (hw_prod)
365 			strlcpy(hw_prod, sminfop, infolen);
366 		sminfop = NULL;
367 	}
368 	if (hw_vendor != NULL && hw_prod != NULL)
369 		printf("\n%s: %s %s", str, hw_vendor, hw_prod);
370 	if ((p = smbios_get_string(&stbl, sys->version, sminfo,
371 	    sizeof(sminfo))) != NULL)
372 		sminfop = fixstring(p);
373 	if (sminfop) {
374 		infolen = strlen(sminfop) + 1;
375 		hw_ver = malloc(infolen, M_DEVBUF, M_NOWAIT);
376 		if (hw_ver)
377 			strlcpy(hw_ver, sminfop, infolen);
378 		sminfop = NULL;
379 	}
380 	if ((p = smbios_get_string(&stbl, sys->serial, sminfo,
381 	    sizeof(sminfo))) != NULL)
382 		sminfop = fixstring(p);
383 	if (sminfop) {
384 		infolen = strlen(sminfop) + 1;
385 		for (i = 0; i < infolen - 1; i++)
386 			enqueue_randomness(sminfop[i]);
387 		hw_serial = malloc(infolen, M_DEVBUF, M_NOWAIT);
388 		if (hw_serial)
389 			strlcpy(hw_serial, sminfop, infolen);
390 	}
391 	if (smbios_entry.mjr > 2 || (smbios_entry.mjr == 2 &&
392 	    smbios_entry.min >= 1)) {
393 		/*
394 		 * If the uuid value is all 0xff the uuid is present but not
395 		 * set, if its all 0 then the uuid isn't present at all.
396 		 */
397 		uuidf = SMBIOS_UUID_NPRESENT|SMBIOS_UUID_NSET;
398 		for (i = 0; i < sizeof(sys->uuid); i++) {
399 			if (sys->uuid[i] != 0xff)
400 				uuidf &= ~SMBIOS_UUID_NSET;
401 			if (sys->uuid[i] != 0)
402 				uuidf &= ~SMBIOS_UUID_NPRESENT;
403 		}
404 
405 		if (uuidf & SMBIOS_UUID_NPRESENT)
406 			hw_uuid = NULL;
407 		else if (uuidf & SMBIOS_UUID_NSET)
408 			hw_uuid = "Not Set";
409 		else {
410 			for (i = 0; i < sizeof(sys->uuid); i++)
411 				enqueue_randomness(sys->uuid[i]);
412 			hw_uuid = malloc(SMBIOS_UUID_REPLEN, M_DEVBUF,
413 			    M_NOWAIT);
414 			if (hw_uuid) {
415 				snprintf(hw_uuid, SMBIOS_UUID_REPLEN,
416 				    SMBIOS_UUID_REP,
417 				    sys->uuid[0], sys->uuid[1], sys->uuid[2],
418 				    sys->uuid[3], sys->uuid[4], sys->uuid[5],
419 				    sys->uuid[6], sys->uuid[7], sys->uuid[8],
420 				    sys->uuid[9], sys->uuid[10], sys->uuid[11],
421 				    sys->uuid[12], sys->uuid[13], sys->uuid[14],
422 				    sys->uuid[15]);
423 			}
424 		}
425 	}
426 }
427