| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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; | |
| } |