summaryrefslogtreecommitdiffstats
path: root/src/arch/i386/firmware/pcbios/smbios.c
blob: bafcafc25e6e4aeec390f290d4aba10093ce98ac (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <realmode.h>
#include <pnpbios.h>
#include <smbios.h>

/** @file
 *
 * System Management BIOS
 *
 */

/** Signature for an SMBIOS structure */
#define SMBIOS_SIGNATURE \
        ( ( '_' << 0 ) + ( 'S' << 8 ) + ( 'M' << 16 ) + ( '_' << 24 ) )

/** SMBIOS entry point */
struct smbios_entry {
	/** Signature
	 *
	 * Must be equal to SMBIOS_SIGNATURE
	 */
	uint32_t signature;
	/** Checksum */
	uint8_t checksum;
	/** Length */
	uint8_t length;
	/** Major version */
	uint8_t major;
	/** Minor version */
	uint8_t minor;
	/** Maximum structure size */
	uint16_t max;
	/** Entry point revision */
	uint8_t revision;
	/** Formatted area */
	uint8_t formatted[5];
	/** DMI Signature */
	uint8_t dmi_signature[5];
	/** DMI checksum */
	uint8_t dmi_checksum;
	/** Structure table length */
	uint16_t smbios_length;
	/** Structure table address */
	physaddr_t smbios_address;
	/** Number of SMBIOS structures */
	uint16_t smbios_count;
	/** BCD revision */
	uint8_t bcd_revision;
} __attribute__ (( packed ));

/** An SMBIOS structure */
struct smbios {
	/** Type */
	uint8_t type;
	/** Length */
	uint8_t length;
	/** Handle */
	uint16_t handle;
} __attribute__ (( packed ));

struct smbios_system_information {
	struct smbios header;
	uint8_t manufacturer;
	uint8_t product;
	uint8_t version;
	uint8_t serial;
} __attribute__ (( packed ));

/**
 * Find SMBIOS
 *
 * @v emtry		SMBIOS entry point to fill in
 * @ret rc		Return status code
 */
static int find_smbios_entry ( struct smbios_entry *entry ) {
	union {
		struct smbios_entry entry;
		uint8_t bytes[256]; /* 256 is maximum length possible */
	} u;
	unsigned int offset;
	size_t len;
	unsigned int i;
	uint8_t sum = 0;

	/* Try to find SMBIOS */
	for ( offset = 0 ; offset < 0x10000 ; offset += 0x10 ) {

		/* Read start of header and verify signature */
		copy_from_real ( &u.entry, BIOS_SEG, offset,
				 sizeof ( u.entry ));
		if ( u.entry.signature != SMBIOS_SIGNATURE )
			continue;

		/* Read whole header and verify checksum */
		len = u.entry.length;
		copy_from_real ( &u.bytes, BIOS_SEG, offset, len );
		for ( i = 0 ; i < len ; i++ ) {
			sum += u.bytes[i];
		}
		if ( sum != 0 ) {
			DBG ( "SMBIOS at %04x:%04x has bad checksum %02x\n",
			      BIOS_SEG, offset, sum );
			continue;
		}

		/* Fill result structure */
		DBG ( "Found SMBIOS entry point at %04x:%04x\n",
		      BIOS_SEG, offset );
		memcpy ( entry, &u.entry, sizeof ( *entry ) );
		return 0;
	}

	DBG ( "No SMBIOS found\n" );
	return -ENOENT;
}

/**
 * Find specific structure type within SMBIOS
 *
 * @v entry		SMBIOS entry point
 * @v type		Structure type
 * @v data		SMBIOS structure buffer to fill in
 * @ret rc		Return status code
 *
 * The buffer must be at least @c entry->max bytes in size.
 */
static int find_smbios ( struct smbios_entry *entry, unsigned int type,
			 void *data ) {
	struct smbios *smbios = data;
	userptr_t smbios_address = phys_to_user ( entry->smbios_address );
	unsigned int count = 0;
	size_t offset = 0;
	size_t frag_len;
	void *end;

	while ( ( offset < entry->smbios_length ) &&
		( count < entry->smbios_count ) ) {
		/* Read next SMBIOS structure */
		frag_len = ( entry->smbios_length - offset );
		if ( frag_len > entry->max )
			frag_len = entry->max;
		copy_from_user ( data, smbios_address, offset, frag_len );

		/* Sanity protection; ensure the last two bytes of the
		 * buffer are 0x00,0x00, just so that a terminator
		 * exists somewhere.  Also ensure that this lies
		 * outside the formatted area.
		 */
		*( ( uint16_t * ) ( data + entry->max - 2 ) ) = 0;
		if ( smbios->length > ( entry->max - 2 ) ) {
			DBG ( "Invalid SMBIOS structure length %zd\n",
			      smbios->length );
			return -ENOENT;
		}

		DBG ( "Found SMBIOS structure type %d at offset %zx\n",
		      smbios->type, offset );

		/* If this is the structure we want, return */
		if ( smbios->type == type )
			return 0;

		/* Find end of record.  This will always exist, thanks
		 * to our sanity check above.
		 */
		for ( end = ( data + smbios->length ) ;
		      end < ( data + entry->max ) ; end++ ) {
			if ( *( ( uint16_t * ) end ) == 0 ) {
				end += 2;
				break;
			}
		}

		offset += ( end - data );
		count++;
	}

	DBG ( "SMBIOS structure type %d not found\n", type );
	return -ENOENT;
}

/**
 * Find indexed string within SMBIOS structure
 *
 * @v data		SMBIOS structure
 * @v index		String index
 * @ret string		String, or NULL
 */
static const char * find_smbios_string ( void *data, unsigned int index ) {
	struct smbios *smbios = data;
	const char *string;
	size_t len;

	if ( ! index )
		return NULL;

	string = ( data + smbios->length );
	while ( --index ) {
		/* Move to next string */
		len = strlen ( string );
		if ( len == 0 ) {
			/* Reached premature end of string table */
			DBG ( "SMBIOS string index %d not found\n", index );
			return NULL;
		}
		string += ( len + 1 );
	}
	return string;
}

/**
 * Find SMBIOS serial number
 *
 * @v data		Buffer to fill
 * @v len		Length of buffer
 */
int find_smbios_serial ( void *data, size_t len ) {
	struct smbios_entry entry;
	const char *string;
	int rc;

	if ( ( rc = find_smbios_entry ( &entry ) ) != 0 )
		return rc;

	char buffer[entry.max];
	if ( ( rc = find_smbios ( &entry, 1, buffer ) ) != 0 )
		return rc;

	struct smbios_system_information *sysinfo = ( void * ) buffer;
	string = find_smbios_string ( buffer, sysinfo->serial );
	if ( ! string )
		return -ENOENT;

	DBG ( "Found serial number \"%s\"\n", string );
	snprintf ( data, len, "%s", string );
	return 0;
}