4d710d689608fba78b8f6c60be538a8c327c4949
[people/mdeck/gpxe.git] / src / arch / i386 / firmware / pcbios / smbios.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <string.h>
21 #include <stdio.h>
22 #include <errno.h>
23 #include <gpxe/uaccess.h>
24 #include <gpxe/uuid.h>
25 #include <realmode.h>
26 #include <pnpbios.h>
27 #include <smbios.h>
28
29 /** @file
30  *
31  * System Management BIOS
32  *
33  */
34
35 /** Signature for SMBIOS entry point */
36 #define SMBIOS_SIGNATURE \
37         ( ( '_' << 0 ) + ( 'S' << 8 ) + ( 'M' << 16 ) + ( '_' << 24 ) )
38
39 /**
40  * SMBIOS entry point
41  *
42  * This is the single table which describes the list of SMBIOS
43  * structures.  It is located by scanning through the BIOS segment.
44  */
45 struct smbios_entry {
46         /** Signature
47          *
48          * Must be equal to SMBIOS_SIGNATURE
49          */
50         uint32_t signature;
51         /** Checksum */
52         uint8_t checksum;
53         /** Length */
54         uint8_t length;
55         /** Major version */
56         uint8_t major;
57         /** Minor version */
58         uint8_t minor;
59         /** Maximum structure size */
60         uint16_t max;
61         /** Entry point revision */
62         uint8_t revision;
63         /** Formatted area */
64         uint8_t formatted[5];
65         /** DMI Signature */
66         uint8_t dmi_signature[5];
67         /** DMI checksum */
68         uint8_t dmi_checksum;
69         /** Structure table length */
70         uint16_t smbios_length;
71         /** Structure table address */
72         physaddr_t smbios_address;
73         /** Number of SMBIOS structures */
74         uint16_t smbios_count;
75         /** BCD revision */
76         uint8_t bcd_revision;
77 } __attribute__ (( packed ));
78
79 /**
80  * SMBIOS entry point descriptor
81  *
82  * This contains the information from the SMBIOS entry point that we
83  * care about.
84  */
85 struct smbios {
86         /** Start of SMBIOS structures */
87         userptr_t address;
88         /** Length of SMBIOS structures */ 
89         size_t length;
90         /** Number of SMBIOS structures */
91         unsigned int count;
92 };
93
94 /**
95  * SMBIOS strings descriptor
96  *
97  * This is returned as part of the search for an SMBIOS structure, and
98  * contains the information needed for extracting the strings within
99  * the "unformatted" portion of the structure.
100  */
101 struct smbios_strings {
102         /** Start of strings data */
103         userptr_t data;
104         /** Length of strings data */
105         size_t length;
106 };
107
108 /**
109  * Find SMBIOS
110  *
111  * @ret smbios          SMBIOS entry point descriptor, or NULL if not found
112  */
113 static struct smbios * find_smbios ( void ) {
114         static struct smbios smbios = {
115                 .address = UNULL,
116         };
117         union {
118                 struct smbios_entry entry;
119                 uint8_t bytes[256]; /* 256 is maximum length possible */
120         } u;
121         unsigned int offset;
122         size_t len;
123         unsigned int i;
124         uint8_t sum;
125
126         /* Return cached result if available */
127         if ( smbios.address != UNULL )
128                 return &smbios;
129
130         /* Try to find SMBIOS */
131         for ( offset = 0 ; offset < 0x10000 ; offset += 0x10 ) {
132
133                 /* Read start of header and verify signature */
134                 copy_from_real ( &u.entry, BIOS_SEG, offset,
135                                  sizeof ( u.entry ));
136                 if ( u.entry.signature != SMBIOS_SIGNATURE )
137                         continue;
138
139                 /* Read whole header and verify checksum */
140                 len = u.entry.length;
141                 copy_from_real ( &u.bytes, BIOS_SEG, offset, len );
142                 for ( i = 0 , sum = 0 ; i < len ; i++ ) {
143                         sum += u.bytes[i];
144                 }
145                 if ( sum != 0 ) {
146                         DBG ( "SMBIOS at %04x:%04x has bad checksum %02x\n",
147                               BIOS_SEG, offset, sum );
148                         continue;
149                 }
150
151                 /* Fill result structure */
152                 DBG ( "Found SMBIOS entry point at %04x:%04x\n",
153                       BIOS_SEG, offset );
154                 smbios.address = phys_to_user ( u.entry.smbios_address );
155                 smbios.length = u.entry.smbios_length;
156                 smbios.count = u.entry.smbios_count;
157                 return &smbios;
158         }
159
160         DBG ( "No SMBIOS found\n" );
161         return NULL;
162 }
163
164 /**
165  * Find SMBIOS strings terminator
166  *
167  * @v smbios            SMBIOS entry point descriptor
168  * @v offset            Offset to start of strings
169  * @ret offset          Offset to strings terminator, or 0 if not found
170  */
171 static size_t find_strings_terminator ( struct smbios *smbios,
172                                         size_t offset ) {
173         size_t max_offset = ( smbios->length - 2 );
174         uint16_t nulnul;
175
176         for ( ; offset <= max_offset ; offset++ ) {
177                 copy_from_user ( &nulnul, smbios->address, offset, 2 );
178                 if ( nulnul == 0 )
179                         return ( offset + 1 );
180         }
181         return 0;
182 }
183
184 /**
185  * Find specific structure type within SMBIOS
186  *
187  * @v type              Structure type to search for
188  * @v structure         Buffer to fill in with structure
189  * @v length            Length of buffer
190  * @v strings           Strings descriptor to fill in, or NULL
191  * @ret rc              Return status code
192  */
193 int find_smbios_structure ( unsigned int type, void *structure,
194                             size_t length, struct smbios_strings *strings ) {
195         struct smbios *smbios;
196         struct smbios_header header;
197         struct smbios_strings temp_strings;
198         unsigned int count = 0;
199         size_t offset = 0;
200         size_t strings_offset;
201         size_t terminator_offset;
202
203         /* Locate SMBIOS entry point */
204         if ( ! ( smbios = find_smbios() ) )
205                 return -ENOENT;
206
207         /* Ensure that we have a usable strings descriptor buffer */
208         if ( ! strings )
209                 strings = &temp_strings;
210
211         /* Scan through list of structures */
212         while ( ( ( offset + sizeof ( header ) ) < smbios->length ) &&
213                 ( count < smbios->count ) ) {
214
215                 /* Read next SMBIOS structure header */
216                 copy_from_user ( &header, smbios->address, offset,
217                                  sizeof ( header ) );
218
219                 /* Determine start and extent of strings block */
220                 strings_offset = ( offset + header.length );
221                 if ( strings_offset > smbios->length ) {
222                         DBG ( "SMBIOS structure at offset %zx with length "
223                               "%x extends beyond SMBIOS\n", offset,
224                               header.length );
225                         return -ENOENT;
226                 }
227                 terminator_offset =
228                         find_strings_terminator ( smbios, strings_offset );
229                 if ( ! terminator_offset ) {
230                         DBG ( "SMBIOS structure at offset %zx has "
231                               "unterminated strings section\n", offset );
232                         return -ENOENT;
233                 }
234                 strings->data = userptr_add ( smbios->address,
235                                               strings_offset );
236                 strings->length = ( terminator_offset - strings_offset );
237
238                 DBG ( "SMBIOS structure at offset %zx has type %d, "
239                       "length %x, strings length %zx\n",
240                       offset, header.type, header.length, strings->length );
241
242                 /* If this is the structure we want, return */
243                 if ( header.type == type ) {
244                         if ( length > header.length )
245                                 length = header.length;
246                         copy_from_user ( structure, smbios->address,
247                                          offset, length );
248                         return 0;
249                 }
250
251                 /* Move to next SMBIOS structure */
252                 offset = ( terminator_offset + 1 );
253                 count++;
254         }
255
256         DBG ( "SMBIOS structure type %d not found\n", type );
257         return -ENOENT;
258 }
259
260 /**
261  * Find indexed string within SMBIOS structure
262  *
263  * @v strings           SMBIOS strings descriptor
264  * @v index             String index
265  * @v buffer            Buffer for string
266  * @v length            Length of string buffer
267  * @ret rc              Return status code
268  */
269 int find_smbios_string ( struct smbios_strings *strings, unsigned int index,
270                          char *buffer, size_t length ) {
271         size_t offset = 0;
272         size_t string_len;
273
274         /* Zero buffer.  This ensures that a valid NUL terminator is
275          * always present (unless length==0).
276          */
277         memset ( buffer, 0, length );
278            
279         /* String numbers start at 1 (0 is used to indicate "no string") */
280         if ( ! index )
281                 return 0;
282
283         while ( offset < strings->length ) {
284                 /* Get string length.  This is known safe, since the
285                  * smbios_strings struct is constructed so as to
286                  * always end on a string boundary.
287                  */
288                 string_len = strlen_user ( strings->data, offset );
289                 if ( --index == 0 ) {
290                         /* Copy string, truncating as necessary. */
291                         if ( string_len >= length )
292                                 string_len = ( length - 1 );
293                         copy_from_user ( buffer, strings->data,
294                                          offset, string_len );
295                         return 0;
296                 }
297                 offset += ( string_len + 1 );
298         }
299
300         DBG ( "SMBIOS string index %d not found\n", index );
301         return -ENOENT;
302 }
303
304 /**
305  * Get UUID from SMBIOS
306  *
307  * @v uuid              UUID to fill in
308  * @ret rc              Return status code
309  */
310 int smbios_get_uuid ( union uuid *uuid ) {
311         struct smbios_system_information sysinfo;
312         int rc;
313
314         if ( ( rc = find_smbios_structure ( SMBIOS_TYPE_SYSTEM_INFORMATION,
315                                             &sysinfo, sizeof ( sysinfo ),
316                                             NULL ) ) != 0 )
317                 return rc;
318
319         memcpy ( uuid, sysinfo.uuid, sizeof ( *uuid ) );
320         DBG ( "SMBIOS found UUID %s\n", uuid_ntoa ( uuid ) );
321
322         return 0;
323 }