1/*	$OpenBSD: smbios.c,v 1.1 2022/12/07 23:04:26 patrick 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
21#include <machine/smbiosvar.h>
22
23#include <lib/libkern/libkern.h>
24#include <stand/boot/cmd.h>
25
26#include "libsa.h"
27
28#undef DPRINTF
29#if defined(SMBIOSDEBUG)
30#define DPRINTF(x...)	do { printf(x); } while(0)
31#else
32#define DPRINTF(x...)
33#endif
34
35struct smbios_entry smbios_entry;
36
37const char *smbios_uninfo[] = {
38	"System",
39	"Not ",
40	"To be",
41	"SYS-"
42};
43
44char smbios_bios_date[64];
45char smbios_board_vendor[64];
46char smbios_board_prod[64];
47char smbios_board_serial[64];
48
49void smbios_info(void);
50char *fixstring(char *);
51
52char *hw_vendor, *hw_prod, *hw_ver, *hw_serial;
53
54void
55smbios_init(void *smbios)
56{
57	struct smbios_struct_bios *sb;
58	struct smbtable bios;
59	char scratch[64];
60	char *sminfop;
61	uint64_t addr;
62
63	if (smbios == NULL)
64		return;
65
66	if (strncmp(smbios, "_SM_", 4) == 0) {
67		struct smbhdr *hdr = smbios;
68		uint8_t *p, checksum = 0;
69		int i;
70
71		if (hdr->len != sizeof(*hdr))
72			return;
73		for (i = 0, p = (uint8_t *)hdr; i < hdr->len; i++)
74			checksum += p[i];
75		if (checksum != 0)
76			return;
77
78		DPRINTF("SMBIOS %d.%d", hdr->majrev, hdr->minrev);
79
80		smbios_entry.len = hdr->size;
81		smbios_entry.mjr = hdr->majrev;
82		smbios_entry.min = hdr->minrev;
83		smbios_entry.count = hdr->count;
84
85		addr = hdr->addr;
86	} else if (strncmp(smbios, "_SM3_", 5) == 0) {
87		struct smb3hdr *hdr = smbios;
88		uint8_t *p, checksum = 0;
89		int i;
90
91		if (hdr->len != sizeof(*hdr) || hdr->epr != 0x01)
92			return;
93		for (i = 0, p = (uint8_t *)hdr; i < hdr->len; i++)
94			checksum += p[i];
95		if (checksum != 0)
96			return;
97
98		DPRINTF("SMBIOS %d.%d.%d", hdr->majrev, hdr->minrev,
99		    hdr->docrev);
100
101		smbios_entry.len = hdr->size;
102		smbios_entry.mjr = hdr->majrev;
103		smbios_entry.min = hdr->minrev;
104		smbios_entry.count = -1;
105
106		addr = hdr->addr;
107	} else {
108		DPRINTF("Unsupported SMBIOS entry point\n");
109		return;
110	}
111
112	smbios_entry.addr = (uint8_t *)addr;
113
114	bios.cookie = 0;
115	if (smbios_find_table(SMBIOS_TYPE_BIOS, &bios)) {
116		sb = bios.tblhdr;
117		DPRINTF("SMBIOS:");
118		if ((smbios_get_string(&bios, sb->vendor,
119		    scratch, sizeof(scratch))) != NULL)
120			DPRINTF(" vendor %s",
121			    fixstring(scratch));
122		if ((smbios_get_string(&bios, sb->version,
123		    scratch, sizeof(scratch))) != NULL)
124			DPRINTF(" version \"%s\"",
125			    fixstring(scratch));
126		if ((smbios_get_string(&bios, sb->release,
127		    scratch, sizeof(scratch))) != NULL) {
128			sminfop = fixstring(scratch);
129			if (sminfop != NULL) {
130				strlcpy(smbios_bios_date,
131				    sminfop,
132				    sizeof(smbios_bios_date));
133				DPRINTF(" date %s", sminfop);
134			}
135		}
136
137		smbios_info();
138		DPRINTF("\n");
139	}
140
141	return;
142}
143
144/*
145 * smbios_find_table() takes a caller supplied smbios struct type and
146 * a pointer to a handle (struct smbtable) returning one if the structure
147 * is successfully located and zero otherwise. Callers should take care
148 * to initialize the cookie field of the smbtable structure to zero before
149 * the first invocation of this function.
150 * Multiple tables of the same type can be located by repeatedly calling
151 * smbios_find_table with the same arguments.
152 */
153int
154smbios_find_table(uint8_t type, struct smbtable *st)
155{
156	uint8_t *va, *end;
157	struct smbtblhdr *hdr;
158	int ret = 0, tcount = 1;
159
160	va = smbios_entry.addr;
161	end = va + smbios_entry.len;
162
163	/*
164	 * The cookie field of the smtable structure is used to locate
165	 * multiple instances of a table of an arbitrary type. Following the
166	 * successful location of a table, the type is encoded as bits 0:7 of
167	 * the cookie value, the offset in terms of the number of structures
168	 * preceding that referenced by the handle is encoded in bits 15:31.
169	 */
170	if ((st->cookie & 0xfff) == type && st->cookie >> 16) {
171		if ((uint8_t *)st->hdr >= va && (uint8_t *)st->hdr < end) {
172			hdr = st->hdr;
173			if (hdr->type == type) {
174				va = (uint8_t *)hdr + hdr->size;
175				for (; va + 1 < end; va++)
176					if (*va == 0 && *(va + 1) == 0)
177						break;
178				va += 2;
179				tcount = st->cookie >> 16;
180			}
181		}
182	}
183	for (; va + sizeof(struct smbtblhdr) < end &&
184	    tcount <= smbios_entry.count; tcount++) {
185		hdr = (struct smbtblhdr *)va;
186		if (hdr->type == type) {
187			ret = 1;
188			st->hdr = hdr;
189			st->tblhdr = va + sizeof(struct smbtblhdr);
190			st->cookie = (tcount + 1) << 16 | type;
191			break;
192		}
193		if (hdr->type == SMBIOS_TYPE_EOT)
194			break;
195		va += hdr->size;
196		for (; va + 1 < end; va++)
197			if (*va == 0 && *(va + 1) == 0)
198				break;
199		va += 2;
200	}
201	return ret;
202}
203
204char *
205smbios_get_string(struct smbtable *st, uint8_t indx, char *dest, size_t len)
206{
207	uint8_t *va, *end;
208	char *ret = NULL;
209	int i;
210
211	va = (uint8_t *)st->hdr + st->hdr->size;
212	end = smbios_entry.addr + smbios_entry.len;
213	for (i = 1; va < end && i < indx && *va; i++)
214		while (*va++)
215			;
216	if (i == indx) {
217		if (va + len < end) {
218			ret = dest;
219			memcpy(ret, va, len);
220			ret[len - 1] = '\0';
221		}
222	}
223
224	return ret;
225}
226
227char *
228fixstring(char *s)
229{
230	char *p, *e;
231#if 0
232	int i;
233
234	for (i = 0; i < nitems(smbios_uninfo); i++)
235		if ((strncasecmp(s, smbios_uninfo[i],
236		    strlen(smbios_uninfo[i]))) == 0)
237			return NULL;
238#endif
239	/*
240	 * Remove leading and trailing whitespace
241	 */
242	for (p = s; *p == ' '; p++)
243		;
244	/*
245	 * Special case entire string is whitespace
246	 */
247	if (p == s + strlen(s))
248		return NULL;
249	for (e = s + strlen(s) - 1; e > s && *e == ' '; e--)
250		;
251	if (p > s || e < s + strlen(s) - 1) {
252		memmove(s, p, e - p + 1);
253		s[e - p + 1] = '\0';
254	}
255
256	return s;
257}
258
259void
260smbios_info(void)
261{
262	char *sminfop, sminfo[64];
263	struct smbtable stbl, btbl;
264	struct smbios_sys *sys;
265	struct smbios_board *board;
266	int infolen, havebb;
267	char *p;
268
269	if (smbios_entry.mjr < 2)
270		return;
271	/*
272	 * According to the spec the system table among others is required,
273	 * if it is not we do not bother with this smbios implementation.
274	 */
275	stbl.cookie = btbl.cookie = 0;
276	if (!smbios_find_table(SMBIOS_TYPE_SYSTEM, &stbl))
277		return;
278	havebb = smbios_find_table(SMBIOS_TYPE_BASEBOARD, &btbl);
279
280	sys = (struct smbios_sys *)stbl.tblhdr;
281	if (havebb) {
282		board = (struct smbios_board *)btbl.tblhdr;
283
284		sminfop = NULL;
285		if ((p = smbios_get_string(&btbl, board->vendor,
286		    sminfo, sizeof(sminfo))) != NULL)
287			sminfop = fixstring(p);
288		if (sminfop)
289			strlcpy(smbios_board_vendor, sminfop,
290			    sizeof(smbios_board_vendor));
291
292		sminfop = NULL;
293		if ((p = smbios_get_string(&btbl, board->product,
294		    sminfo, sizeof(sminfo))) != NULL)
295			sminfop = fixstring(p);
296		if (sminfop)
297			strlcpy(smbios_board_prod, sminfop,
298			    sizeof(smbios_board_prod));
299
300		sminfop = NULL;
301		if ((p = smbios_get_string(&btbl, board->serial,
302		    sminfo, sizeof(sminfo))) != NULL)
303			sminfop = fixstring(p);
304		if (sminfop)
305			strlcpy(smbios_board_serial, sminfop,
306			    sizeof(smbios_board_serial));
307	}
308	/*
309	 * Some smbios implementations have no system vendor or
310	 * product strings, some have very uninformative data which is
311	 * harder to work around and we must rely upon various
312	 * heuristics to detect this. In both cases we attempt to fall
313	 * back on the base board information in the perhaps naive
314	 * belief that motherboard vendors will supply this
315	 * information.
316	 */
317	sminfop = NULL;
318	if ((p = smbios_get_string(&stbl, sys->vendor, sminfo,
319	    sizeof(sminfo))) != NULL)
320		sminfop = fixstring(p);
321	if (sminfop == NULL) {
322		if (havebb) {
323			if ((p = smbios_get_string(&btbl, board->vendor,
324			    sminfo, sizeof(sminfo))) != NULL)
325				sminfop = fixstring(p);
326		}
327	}
328	if (sminfop) {
329		infolen = strlen(sminfop) + 1;
330		hw_vendor = alloc(infolen);
331		if (hw_vendor)
332			strlcpy(hw_vendor, sminfop, infolen);
333		sminfop = NULL;
334	}
335	if ((p = smbios_get_string(&stbl, sys->product, sminfo,
336	    sizeof(sminfo))) != NULL)
337		sminfop = fixstring(p);
338	if (sminfop == NULL) {
339		if (havebb) {
340			if ((p = smbios_get_string(&btbl, board->product,
341			    sminfo, sizeof(sminfo))) != NULL)
342				sminfop = fixstring(p);
343		}
344	}
345	if (sminfop) {
346		infolen = strlen(sminfop) + 1;
347		hw_prod = alloc(infolen);
348		if (hw_prod)
349			strlcpy(hw_prod, sminfop, infolen);
350		sminfop = NULL;
351	}
352	if (hw_vendor != NULL && hw_prod != NULL)
353		DPRINTF("\nSMBIOS: %s %s", hw_vendor, hw_prod);
354	if ((p = smbios_get_string(&stbl, sys->version, sminfo,
355	    sizeof(sminfo))) != NULL)
356		sminfop = fixstring(p);
357	if (sminfop) {
358		infolen = strlen(sminfop) + 1;
359		hw_ver = alloc(infolen);
360		if (hw_ver)
361			strlcpy(hw_ver, sminfop, infolen);
362		sminfop = NULL;
363	}
364	if ((p = smbios_get_string(&stbl, sys->serial, sminfo,
365	    sizeof(sminfo))) != NULL)
366		sminfop = fixstring(p);
367	if (sminfop) {
368		infolen = strlen(sminfop) + 1;
369		hw_serial = alloc(infolen);
370		if (hw_serial)
371			strlcpy(hw_serial, sminfop, infolen);
372	}
373}
374