xref: /dragonfly/stand/boot/pc32/libi386/smbios.c (revision 5e488df32cb01056a5b714a522e51c69ab7b4612)
1 /*-
2  * Copyright (c) 2005-2009 Jung-uk Kim <jkim@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *        notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *        notice, this list of conditions and the following disclaimer in the
12  *        documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD: src/sys/boot/i386/libi386/smbios.c,v 1.10 2009/04/07 17:58:15 jkim Exp $
27  */
28 
29 #include <stand.h>
30 #include <bootstrap.h>
31 #include <sys/endian.h>
32 
33 #ifdef EFI
34 /* In EFI, we don't need PTOV(). */
35 #define PTOV(x)               (caddr_t)(uintptr_t)(x)
36 #else
37 #include "btxv86.h"
38 #endif
39 #include "smbios.h"
40 
41 /*
42  * Detect SMBIOS and export information about the SMBIOS into the
43  * environment.
44  *
45  * System Management BIOS Reference Specification, v2.6 Final
46  * http://www.dmtf.org/standards/published_documents/DSP0134_2.6.0.pdf
47  */
48 
49 /*
50  * 2.1.1 SMBIOS Structure Table Entry Point
51  *
52  * "On non-EFI systems, the SMBIOS Entry Point structure, described below, can
53  * be located by application software by searching for the anchor-string on
54  * paragraph (16-byte) boundaries within the physical memory address range
55  * 000F0000h to 000FFFFFh. This entry point encapsulates an intermediate anchor
56  * string that is used by some existing DMI browsers."
57  */
58 #define   SMBIOS_START                  0xf0000
59 #define   SMBIOS_LENGTH                 0x10000
60 #define   SMBIOS_STEP                   0x10
61 #define   SMBIOS_SIG                    "_SM_"
62 #define   SMBIOS_DMI_SIG                "_DMI_"
63 
64 #define   SMBIOS_GET8(base, off)        (*(uint8_t *)((base) + (off)))
65 #define   SMBIOS_GET16(base, off)       (*(uint16_t *)((base) + (off)))
66 #define   SMBIOS_GET32(base, off)       (*(uint32_t *)((base) + (off)))
67 
68 #define   SMBIOS_GETLEN(base) SMBIOS_GET8(base, 0x01)
69 #define   SMBIOS_GETSTR(base) ((base) + SMBIOS_GETLEN(base))
70 
71 static uint32_t     smbios_enabled_memory = 0;
72 static uint32_t     smbios_old_enabled_memory = 0;
73 static uint8_t      smbios_enabled_sockets = 0;
74 static uint8_t      smbios_populated_sockets = 0;
75 
76 static uint8_t
smbios_checksum(const caddr_t addr,const uint8_t len)77 smbios_checksum(const caddr_t addr, const uint8_t len)
78 {
79           uint8_t             sum;
80           int                 i;
81 
82           for (sum = 0, i = 0; i < len; i++)
83                     sum += SMBIOS_GET8(addr, i);
84           return (sum);
85 }
86 
87 static caddr_t
smbios_sigsearch(const caddr_t addr,const uint32_t len)88 smbios_sigsearch(const caddr_t addr, const uint32_t len)
89 {
90           caddr_t             cp;
91 
92           /* Search on 16-byte boundaries. */
93           for (cp = addr; cp < addr + len; cp += SMBIOS_STEP)
94                     if (strncmp(cp, SMBIOS_SIG, 4) == 0 &&
95                         smbios_checksum(cp, SMBIOS_GET8(cp, 0x05)) == 0 &&
96                         strncmp(cp + 0x10, SMBIOS_DMI_SIG, 5) == 0 &&
97                         smbios_checksum(cp + 0x10, 0x0f) == 0)
98                               return (cp);
99           return (NULL);
100 }
101 
102 static void
smbios_setenv(const char * name,caddr_t addr,const int offset)103 smbios_setenv(const char *name, caddr_t addr, const int offset)
104 {
105           caddr_t             cp;
106           int                 i, idx;
107 
108           idx = SMBIOS_GET8(addr, offset);
109           if (idx != 0) {
110                     cp = SMBIOS_GETSTR(addr);
111                     for (i = 1; i < idx; i++)
112                               cp += strlen(cp) + 1;
113                     setenv(name, cp, 1);
114           }
115 }
116 
117 #ifdef SMBIOS_SERIAL_NUMBERS
118 
119 #define   UUID_SIZE           16
120 #define   UUID_TYPE           uint32_t
121 #define   UUID_STEP           sizeof(UUID_TYPE)
122 #define   UUID_ALL_BITS                 (UUID_SIZE / UUID_STEP)
123 #define   UUID_GET(base, off) (*(UUID_TYPE *)((base) + (off)))
124 
125 static void
smbios_setuuid(const char * name,const caddr_t addr,const int ver)126 smbios_setuuid(const char *name, const caddr_t addr, const int ver)
127 {
128           char                uuid[37];
129           int                 i, ones, zeros;
130           UUID_TYPE n;
131           uint32_t  f1;
132           uint16_t  f2, f3;
133 
134           for (i = 0, ones = 0, zeros = 0; i < UUID_SIZE; i += UUID_STEP) {
135                     n = UUID_GET(addr, i) + 1;
136                     if (zeros == 0 && n == 0)
137                               ones++;
138                     else if (ones == 0 && n == 1)
139                               zeros++;
140                     else
141                               break;
142           }
143 
144           if (ones != UUID_ALL_BITS && zeros != UUID_ALL_BITS) {
145                     /*
146                      * 3.3.2.1 System UUID
147                      *
148                      * "Although RFC 4122 recommends network byte order for all
149                      * fields, the PC industry (including the ACPI, UEFI, and
150                      * Microsoft specifications) has consistently used
151                      * little-endian byte encoding for the first three fields:
152                      * time_low, time_mid, time_hi_and_version. The same encoding,
153                      * also known as wire format, should also be used for the
154                      * SMBIOS representation of the UUID."
155                      *
156                      * Note: We use network byte order for backward compatibility
157                      * unless SMBIOS version is 2.6+ or little-endian is forced.
158                      */
159 #ifndef SMBIOS_LITTLE_ENDIAN_UUID
160                     if (ver < 0x0206) {
161                               f1 = ntohl(SMBIOS_GET32(addr, 0));
162                               f2 = ntohs(SMBIOS_GET16(addr, 4));
163                               f3 = ntohs(SMBIOS_GET16(addr, 6));
164                     } else
165 #endif
166                     {
167                               f1 = le32toh(SMBIOS_GET32(addr, 0));
168                               f2 = le16toh(SMBIOS_GET16(addr, 4));
169                               f3 = le16toh(SMBIOS_GET16(addr, 6));
170                     }
171                     sprintf(uuid,
172                         "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
173                         f1, f2, f3, SMBIOS_GET8(addr, 8), SMBIOS_GET8(addr, 9),
174                         SMBIOS_GET8(addr, 10), SMBIOS_GET8(addr, 11),
175                         SMBIOS_GET8(addr, 12), SMBIOS_GET8(addr, 13),
176                         SMBIOS_GET8(addr, 14), SMBIOS_GET8(addr, 15));
177                     setenv(name, uuid, 1);
178           }
179 }
180 
181 #undef UUID_SIZE
182 #undef UUID_TYPE
183 #undef UUID_STEP
184 #undef UUID_ALL_BITS
185 #undef UUID_GET
186 
187 #endif
188 
189 static caddr_t
smbios_parse_table(const caddr_t addr,const int ver)190 smbios_parse_table(const caddr_t addr, const int ver)
191 {
192           caddr_t             cp;
193           int                 proc, size, osize, type;
194 
195           type = SMBIOS_GET8(addr, 0);  /* 3.1.2 Structure Header Format */
196           switch(type) {
197           case 0:             /* 3.3.1 BIOS Information (Type 0) */
198                     smbios_setenv("smbios.bios.vendor", addr, 0x04);
199                     smbios_setenv("smbios.bios.version", addr, 0x05);
200                     smbios_setenv("smbios.bios.reldate", addr, 0x08);
201                     break;
202 
203           case 1:             /* 3.3.2 System Information (Type 1) */
204                     smbios_setenv("smbios.system.maker", addr, 0x04);
205                     smbios_setenv("smbios.system.product", addr, 0x05);
206                     smbios_setenv("smbios.system.version", addr, 0x06);
207 #ifdef SMBIOS_SERIAL_NUMBERS
208                     smbios_setenv("smbios.system.serial", addr, 0x07);
209                     smbios_setuuid("smbios.system.uuid", addr + 0x08, ver);
210 #endif
211                     break;
212 
213           case 2:             /* 3.3.3 Base Board (or Module) Information (Type 2) */
214                     smbios_setenv("smbios.planar.maker", addr, 0x04);
215                     smbios_setenv("smbios.planar.product", addr, 0x05);
216                     smbios_setenv("smbios.planar.version", addr, 0x06);
217 #ifdef SMBIOS_SERIAL_NUMBERS
218                     smbios_setenv("smbios.planar.serial", addr, 0x07);
219 #endif
220                     break;
221 
222           case 3:             /* 3.3.4 System Enclosure or Chassis (Type 3) */
223                     smbios_setenv("smbios.chassis.maker", addr, 0x04);
224                     smbios_setenv("smbios.chassis.version", addr, 0x06);
225 #ifdef SMBIOS_SERIAL_NUMBERS
226                     smbios_setenv("smbios.chassis.serial", addr, 0x07);
227                     smbios_setenv("smbios.chassis.tag", addr, 0x08);
228 #endif
229                     break;
230 
231           case 4:             /* 3.3.5 Processor Information (Type 4) */
232                     /*
233                      * Offset 18h: Processor Status
234                      *
235                      * Bit 7  Reserved, must be 0
236                      * Bit 6  CPU Socket Populated
237                      *                  1 - CPU Socket Populated
238                      *                  0 - CPU Socket Unpopulated
239                      * Bit 5:3          Reserved, must be zero
240                      * Bit 2:0          CPU Status
241                      *                  0h - Unknown
242                      *                  1h - CPU Enabled
243                      *                  2h - CPU Disabled by User via BIOS Setup
244                      *                  3h - CPU Disabled by BIOS (POST Error)
245                      *                  4h - CPU is Idle, waiting to be enabled
246                      *                  5-6h - Reserved
247                      *                  7h - Other
248                      */
249                     proc = SMBIOS_GET8(addr, 0x18);
250                     if ((proc & 0x07) == 1)
251                               smbios_enabled_sockets++;
252                     if ((proc & 0x40) != 0)
253                               smbios_populated_sockets++;
254                     break;
255 
256           case 6:             /* 3.3.7 Memory Module Information (Type 6, Obsolete) */
257                     /*
258                      * Offset 0Ah: Enabled Size
259                      *
260                      * Bit 7  Bank connection
261                      *                  1 - Double-bank connection
262                      *                  0 - Single-bank connection
263                      * Bit 6:0          Size (n), where 2**n is the size in MB
264                      *                  7Dh - Not determinable (Installed Size only)
265                      *                  7Eh - Module is installed, but no memory
266                      *                        has been enabled
267                      *                  7Fh - Not installed
268                      */
269                     osize = SMBIOS_GET8(addr, 0x0a) & 0x7f;
270                     if (osize > 0 && osize < 22)
271                               smbios_old_enabled_memory += 1 << (osize + 10);
272                     break;
273 
274           case 17:  /* 3.3.18 Memory Device (Type 17) */
275                     /*
276                      * Offset 0Ch: Size
277                      *
278                      * Bit 15 Granularity
279                      *                  1 - Value is in kilobytes units
280                      *                  0 - Value is in megabytes units
281                      * Bit 14:0         Size
282                      */
283                     size = SMBIOS_GET16(addr, 0x0c);
284                     if (size != 0 && size != 0xffff)
285                               smbios_enabled_memory += (size & 0x8000) != 0 ?
286                                   (size & 0x7fff) : (size << 10);
287                     break;
288 
289           default:  /* skip other types */
290                     break;
291           }
292 
293           /* Find structure terminator. */
294           cp = SMBIOS_GETSTR(addr);
295           while (SMBIOS_GET16(cp, 0) != 0)
296                     cp++;
297 
298           return (cp + 2);
299 }
300 
301 void
smbios_detect(const caddr_t addr)302 smbios_detect(const caddr_t addr)
303 {
304           char                buf[32];
305           caddr_t             vaddr, dmi, smbios;
306           size_t              count, length;
307           uint32_t  paddr;
308           int                 major, minor, ver;
309           unsigned int        i;
310 
311           /* Search signatures and validate checksums. */
312           smbios = smbios_sigsearch(addr ? addr : PTOV(SMBIOS_START),
313               SMBIOS_LENGTH);
314           if (smbios == NULL)
315                     return;
316 
317           /*
318            * Export the SMBIOS entry point via kenv so that the SMBIOS table can
319            * be found (e.g., by dmidecode(8)) in UEFI-only mode, where the BIOS
320            * is not mapped into the standard address space.
321            */
322           sprintf(buf, "%p", smbios);
323           setenv("hint.smbios.0.mem", buf, 1);
324 
325           length = SMBIOS_GET16(smbios, 0x16);    /* Structure Table Length */
326           paddr = SMBIOS_GET32(smbios, 0x18);     /* Structure Table Address */
327           count = SMBIOS_GET16(smbios, 0x1c);     /* No of SMBIOS Structures */
328           ver = SMBIOS_GET8(smbios, 0x1e);        /* SMBIOS BCD Revision */
329 
330           if (ver != 0) {
331                     major = ver >> 4;
332                     minor = ver & 0x0f;
333                     if (major > 9 || minor > 9)
334                               ver = 0;
335           }
336           if (ver == 0) {
337                     major = SMBIOS_GET8(smbios, 0x06); /* SMBIOS Major Version */
338                     minor = SMBIOS_GET8(smbios, 0x07); /* SMBIOS Minor Version */
339           }
340           ver = (major << 8) | minor;
341 
342           vaddr = PTOV(paddr);
343           for (dmi = vaddr, i = 0; dmi < vaddr + length && i < count; i++)
344                     dmi = smbios_parse_table(dmi, ver);
345 
346           sprintf(buf, "%d.%d", major, minor);
347           setenv("smbios.version", buf, 1);
348           if (smbios_enabled_memory > 0 || smbios_old_enabled_memory > 0) {
349                     sprintf(buf, "%u", smbios_enabled_memory > 0 ?
350                         smbios_enabled_memory : smbios_old_enabled_memory);
351                     setenv("smbios.memory.enabled", buf, 1);
352           }
353           if (smbios_enabled_sockets > 0) {
354                     sprintf(buf, "%u", smbios_enabled_sockets);
355                     setenv("smbios.socket.enabled", buf, 1);
356           }
357           if (smbios_populated_sockets > 0) {
358                     sprintf(buf, "%u", smbios_populated_sockets);
359                     setenv("smbios.socket.populated", buf, 1);
360           }
361 }
362