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