| /* |
| * 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. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ); |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <gpxe/uaccess.h> |
| #include <gpxe/smbios.h> |
| |
| /** @file |
| * |
| * System Management BIOS |
| * |
| */ |
| |
| /** SMBIOS entry point descriptor */ |
| static struct smbios smbios = { |
| .address = UNULL, |
| }; |
| |
| /** |
| * 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 ) { |
| size_t max_offset = ( smbios.len - 2 ); |
| uint16_t nulnul; |
| |
| for ( ; offset <= max_offset ; offset++ ) { |
| copy_from_user ( &nulnul, smbios.address, offset, 2 ); |
| if ( nulnul == 0 ) |
| return ( offset + 1 ); |
| } |
| return 0; |
| } |
| |
| /** |
| * Find specific structure type within SMBIOS |
| * |
| * @v type Structure type to search for |
| * @v structure SMBIOS structure descriptor to fill in |
| * @ret rc Return status code |
| */ |
| int find_smbios_structure ( unsigned int type, |
| struct smbios_structure *structure ) { |
| unsigned int count = 0; |
| size_t offset = 0; |
| size_t strings_offset; |
| size_t terminator_offset; |
| int rc; |
| |
| /* Find SMBIOS */ |
| if ( ( smbios.address == UNULL ) && |
| ( ( rc = find_smbios ( &smbios ) ) != 0 ) ) |
| return rc; |
| assert ( smbios.address != UNULL ); |
| |
| /* Scan through list of structures */ |
| while ( ( ( offset + sizeof ( structure->header ) ) < smbios.len ) |
| && ( count < smbios.count ) ) { |
| |
| /* Read next SMBIOS structure header */ |
| copy_from_user ( &structure->header, smbios.address, offset, |
| sizeof ( structure->header ) ); |
| |
| /* Determine start and extent of strings block */ |
| strings_offset = ( offset + structure->header.len ); |
| if ( strings_offset > smbios.len ) { |
| DBG ( "SMBIOS structure at offset %zx with length " |
| "%x extends beyond SMBIOS\n", offset, |
| structure->header.len ); |
| return -ENOENT; |
| } |
| terminator_offset = find_strings_terminator ( strings_offset ); |
| if ( ! terminator_offset ) { |
| DBG ( "SMBIOS structure at offset %zx has " |
| "unterminated strings section\n", offset ); |
| return -ENOENT; |
| } |
| structure->strings_len = ( terminator_offset - strings_offset); |
| |
| DBG ( "SMBIOS structure at offset %zx has type %d, length %x, " |
| "strings length %zx\n", offset, structure->header.type, |
| structure->header.len, structure->strings_len ); |
| |
| /* If this is the structure we want, return */ |
| if ( structure->header.type == type ) { |
| structure->offset = offset; |
| return 0; |
| } |
| |
| /* Move to next SMBIOS structure */ |
| offset = ( terminator_offset + 1 ); |
| count++; |
| } |
| |
| DBG ( "SMBIOS structure type %d not found\n", type ); |
| return -ENOENT; |
| } |
| |
| /** |
| * Copy SMBIOS structure |
| * |
| * @v structure SMBIOS structure descriptor |
| * @v data Buffer to hold SMBIOS structure |
| * @v len Length of buffer |
| * @ret rc Return status code |
| */ |
| int read_smbios_structure ( struct smbios_structure *structure, |
| void *data, size_t len ) { |
| |
| assert ( smbios.address != UNULL ); |
| |
| if ( len > structure->header.len ) |
| len = structure->header.len; |
| copy_from_user ( data, smbios.address, structure->offset, len ); |
| return 0; |
| } |
| |
| /** |
| * Find indexed string within SMBIOS structure |
| * |
| * @v structure SMBIOS structure descriptor |
| * @v index String index |
| * @v data Buffer for string |
| * @v len Length of string buffer |
| * @ret rc Length of string, or negative error |
| */ |
| int read_smbios_string ( struct smbios_structure *structure, |
| unsigned int index, void *data, size_t len ) { |
| size_t strings_start = ( structure->offset + structure->header.len ); |
| size_t strings_end = ( strings_start + structure->strings_len ); |
| size_t offset; |
| size_t string_len; |
| |
| assert ( smbios.address != UNULL ); |
| |
| /* String numbers start at 1 (0 is used to indicate "no string") */ |
| if ( ! index ) |
| return -ENOENT; |
| |
| for ( offset = strings_start ; offset < strings_end ; |
| offset += ( string_len + 1 ) ) { |
| /* Get string length. This is known safe, since the |
| * smbios_strings struct is constructed so as to |
| * always end on a string boundary. |
| */ |
| string_len = strlen_user ( smbios.address, offset ); |
| if ( --index == 0 ) { |
| /* Copy string, truncating as necessary. */ |
| if ( len > string_len ) |
| len = string_len; |
| copy_from_user ( data, smbios.address, offset, len ); |
| return string_len; |
| } |
| } |
| |
| DBG ( "SMBIOS string index %d not found\n", index ); |
| return -ENOENT; |
| } |