blob: 3a1a98b17bcb2d13d30fe91d9dc55d007eaa60b0 [file] [log] [blame]
/*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <ipxe/uaccess.h>
#include <ipxe/smbios.h>
/** @file
*
* System Management BIOS
*
*/
/** SMBIOS entry point descriptor */
static struct smbios smbios = {
.address = NULL,
};
/**
* Calculate SMBIOS entry point structure checksum
*
* @v start Start address of region
* @v len Length of entry point structure
* @ret sum Byte checksum
*/
static uint8_t smbios_checksum ( const void *start, size_t len ) {
const uint8_t *byte = start;
uint8_t sum = 0;
/* Compute checksum */
while ( len-- )
sum += *(byte++);
return sum;
}
/**
* Scan for SMBIOS 32-bit entry point structure
*
* @v start Start address of region to scan
* @v len Length of region to scan
* @ret entry SMBIOS entry point structure, or NULL if not found
*/
const struct smbios_entry * find_smbios_entry ( const void *start,
size_t len ) {
static size_t offset = 0; /* Avoid repeated attempts to locate SMBIOS */
const struct smbios_entry *entry;
uint8_t sum;
/* Try to find SMBIOS */
for ( ; ( offset + sizeof ( *entry ) ) <= len ; offset += 0x10 ) {
/* Verify signature */
entry = ( start + offset );
if ( entry->signature != SMBIOS_SIGNATURE )
continue;
/* Verify length */
if ( ( entry->len < sizeof ( *entry ) ) ||
( ( offset + entry->len ) > len ) ) {
DBGC ( &smbios, "SMBIOS at %#08lx has bad length "
"%#02x\n", virt_to_phys ( entry ), entry->len );
continue;
}
/* Verify checksum */
if ( ( sum = smbios_checksum ( entry, entry->len ) ) != 0 ) {
DBGC ( &smbios, "SMBIOS at %#08lx has bad checksum "
"%#02x\n", virt_to_phys ( entry ), sum );
continue;
}
/* Fill result structure */
DBGC ( &smbios, "Found SMBIOS v%d.%d entry point at %#08lx\n",
entry->major, entry->minor, virt_to_phys ( entry ) );
return entry;
}
DBGC ( &smbios, "No SMBIOS found\n" );
return NULL;
}
/**
* Scan for SMBIOS 64-bit entry point structure
*
* @v start Start address of region to scan
* @v len Length of region to scan
* @ret entry SMBIOS entry point structure, or NULL if not found
*/
const struct smbios3_entry * find_smbios3_entry ( const void *start,
size_t len ) {
static size_t offset = 0; /* Avoid repeated attempts to locate SMBIOS */
const struct smbios3_entry *entry;
uint8_t sum;
/* Try to find SMBIOS */
for ( ; ( offset + sizeof ( *entry ) ) <= len ; offset += 0x10 ) {
/* Verify signature */
entry = ( start + offset );
if ( entry->signature != SMBIOS3_SIGNATURE )
continue;
/* Verify length */
if ( ( entry->len < sizeof ( *entry ) ) ||
( ( offset + entry->len ) > len ) ) {
DBGC ( &smbios, "SMBIOS at %#08lx has bad length "
"%#02x\n", virt_to_phys ( entry ), entry->len );
continue;
}
/* Verify checksum */
if ( ( sum = smbios_checksum ( entry, entry->len ) ) != 0 ) {
DBGC ( &smbios, "SMBIOS3 at %#08lx has bad checksum "
"%#02x\n", virt_to_phys ( entry ), sum );
continue;
}
/* Fill result structure */
DBGC ( &smbios, "Found SMBIOS3 v%d.%d entry point at %#08lx\n",
entry->major, entry->minor, virt_to_phys ( entry ) );
return entry;
}
DBGC ( &smbios, "No SMBIOS3 found\n" );
return NULL;
}
/**
* Find SMBIOS strings terminator
*
* @v offset Offset to start of strings
* @ret offset Offset to strings terminator, or 0 if not found
*/
static size_t find_strings_terminator ( size_t offset ) {
const uint16_t *nulnul __attribute__ (( aligned ( 1 ) ));
/* Sanity checks */
assert ( smbios.address != NULL );
/* Check for presence of terminating empty string */
for ( ; ( offset + sizeof ( *nulnul ) ) <= smbios.len ; offset++ ) {
nulnul = ( smbios.address + offset );
if ( *nulnul == 0 )
return ( offset + 1 );
}
return 0;
}
/**
* Find specific structure type within SMBIOS
*
* @v type Structure type to search for
* @v instance Instance of this type of structure
* @ret structure SMBIOS structure header, or NULL if not found
*/
const struct smbios_header * smbios_structure ( unsigned int type,
unsigned int instance ) {
const struct smbios_header *structure;
unsigned int count = 0;
size_t offset = 0;
size_t strings_offset;
size_t terminator_offset;
size_t strings_len;
int rc;
/* Find SMBIOS */
if ( ( smbios.address == NULL ) &&
( ( rc = find_smbios ( &smbios ) ) != 0 ) )
return NULL;
assert ( smbios.address != NULL );
/* Scan through list of structures */
while ( ( ( offset + sizeof ( *structure ) ) < smbios.len ) &&
( ( smbios.count == 0 ) || ( count < smbios.count ) ) ) {
/* Access next SMBIOS structure header */
structure = ( smbios.address + offset );
/* Determine start and extent of strings block */
strings_offset = ( offset + structure->len );
if ( strings_offset > smbios.len ) {
DBGC ( &smbios, "SMBIOS structure at offset %#zx "
"with length %#x extends beyond SMBIOS\n",
offset, structure->len );
return NULL;
}
terminator_offset = find_strings_terminator ( strings_offset );
if ( ! terminator_offset ) {
DBGC ( &smbios, "SMBIOS structure at offset %#zx has "
"unterminated strings section\n", offset );
return NULL;
}
strings_len = ( terminator_offset - strings_offset);
DBGC ( &smbios, "SMBIOS structure at offset %#zx has type %d, "
"length %#x, strings length %#zx\n", offset,
structure->type, structure->len, strings_len );
/* Stop if we have reached an end-of-table marker */
if ( ( smbios.count == 0 ) &&
( structure->type == SMBIOS_TYPE_END ) )
break;
/* If this is the structure we want, return */
if ( ( structure->type == type ) &&
( instance-- == 0 ) ) {
return structure;
}
/* Move to next SMBIOS structure */
offset = ( terminator_offset + 1 );
count++;
}
DBGC ( &smbios, "SMBIOS structure type %d not found\n", type );
return NULL;
}
/**
* Get indexed string within SMBIOS structure
*
* @v structure SMBIOS structure header
* @v index String index
* @ret string SMBIOS string, or NULL if not fond
*/
const char * smbios_string ( const struct smbios_header *structure,
unsigned int index ) {
const char *string;
unsigned int i;
size_t len;
/* Sanity check */
assert ( smbios.address != NULL );
/* Step through strings */
string = ( ( ( const void * ) structure ) + structure->len );
for ( i = index ; i-- ; ) {
/* Get string length. This is known safe, since we
* check for the empty-string terminator in
* smbios_structure().
*/
len = strlen ( string );
if ( ! len )
break;
if ( i == 0 )
return string;
string += ( len + 1 /* NUL */ );
}
DBGC ( &smbios, "SMBIOS string index %d not found\n", index );
return NULL;
}
/**
* Get SMBIOS version
*
* @ret version Version, or negative error
*/
int smbios_version ( void ) {
int rc;
/* Find SMBIOS */
if ( ( smbios.address == NULL ) &&
( ( rc = find_smbios ( &smbios ) ) != 0 ) )
return rc;
assert ( smbios.address != NULL );
return smbios.version;
}
/**
* Clear SMBIOS entry point descriptor
*
*/
void smbios_clear ( void ) {
/* Clear address */
smbios.address = NULL;
}