diff --git a/src/arch/i386/firmware/pcbios/smbios.c b/src/arch/i386/firmware/pcbios/smbios.c new file mode 100644 index 00000000..bafcafc2 --- /dev/null +++ b/src/arch/i386/firmware/pcbios/smbios.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2007 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include + +/** @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; +} diff --git a/src/arch/i386/include/smbios.h b/src/arch/i386/include/smbios.h new file mode 100644 index 00000000..5b35ade1 --- /dev/null +++ b/src/arch/i386/include/smbios.h @@ -0,0 +1,11 @@ +#ifndef _SMBIOS_H +#define _SMBIOS_H + +/** @file + * + * System Management BIOS + */ + +extern int find_smbios_serial ( void *data, size_t len ); + +#endif /* _SMBIOS_H */