diff options
| author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 | 
|---|---|---|
| committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 | 
| commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
| tree | 65ca85f13617aee1dce474596800950f266a456c /roms/ipxe/src/interface/smbios | |
| download | qemu-master.tar.gz qemu-master.tar.bz2 qemu-master.zip  | |
Diffstat (limited to 'roms/ipxe/src/interface/smbios')
| -rw-r--r-- | roms/ipxe/src/interface/smbios/smbios.c | 248 | ||||
| -rw-r--r-- | roms/ipxe/src/interface/smbios/smbios_settings.c | 255 | 
2 files changed, 503 insertions, 0 deletions
diff --git a/roms/ipxe/src/interface/smbios/smbios.c b/roms/ipxe/src/interface/smbios/smbios.c new file mode 100644 index 00000000..85694342 --- /dev/null +++ b/roms/ipxe/src/interface/smbios/smbios.c @@ -0,0 +1,248 @@ +/* + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#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 = UNULL, +}; + +/** + * Scan for SMBIOS entry point structure + * + * @v start		Start address of region to scan + * @v len		Length of region to scan + * @v entry		SMBIOS entry point structure to fill in + * @ret rc		Return status code + */ +int find_smbios_entry ( userptr_t start, size_t len, +			struct smbios_entry *entry ) { +	uint8_t buf[256]; /* 256 is maximum length possible */ +	static size_t offset = 0; /* Avoid repeated attempts to locate SMBIOS */ +	size_t entry_len; +	unsigned int i; +	uint8_t sum; + +	/* Try to find SMBIOS */ +	for ( ; offset < len ; offset += 0x10 ) { + +		/* Read start of header and verify signature */ +		copy_from_user ( entry, start, offset, sizeof ( *entry ) ); +		if ( entry->signature != SMBIOS_SIGNATURE ) +			continue; + +		/* Read whole header and verify checksum */ +		entry_len = entry->len; +		assert ( entry_len <= sizeof ( buf ) ); +		copy_from_user ( buf, start, offset, entry_len ); +		for ( i = 0, sum = 0 ; i < entry_len ; i++ ) { +			sum += buf[i]; +		} +		if ( sum != 0 ) { +			DBG ( "SMBIOS at %08lx has bad checksum %02x\n", +			      user_to_phys ( start, offset ), sum ); +			continue; +		} + +		/* Fill result structure */ +		DBG ( "Found SMBIOS v%d.%d entry point at %08lx\n", +		      entry->major, entry->minor, +		      user_to_phys ( start, offset ) ); +		return 0; +	} + +	DBG ( "No SMBIOS found\n" ); +	return -ENODEV; +} + +/** + * 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 instance		Instance of this type of structure + * @v structure		SMBIOS structure descriptor to fill in + * @ret rc		Return status code + */ +int find_smbios_structure ( unsigned int type, unsigned int instance, +			    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 ) && +		     ( instance-- == 0 ) ) { +			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; +} + +/** + * Get SMBIOS version + * + * @ret version		Version, or negative error + */ +int smbios_version ( void ) { +	int rc; + +	/* Find SMBIOS */ +	if ( ( smbios.address == UNULL ) && +	     ( ( rc = find_smbios ( &smbios ) ) != 0 ) ) +		return rc; +	assert ( smbios.address != UNULL ); + +	return smbios.version; +} diff --git a/roms/ipxe/src/interface/smbios/smbios_settings.c b/roms/ipxe/src/interface/smbios/smbios_settings.c new file mode 100644 index 00000000..83e4320e --- /dev/null +++ b/roms/ipxe/src/interface/smbios/smbios_settings.c @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <ipxe/settings.h> +#include <ipxe/init.h> +#include <ipxe/uuid.h> +#include <ipxe/smbios.h> + +/** SMBIOS settings scope */ +static const struct settings_scope smbios_settings_scope; + +/** + * Construct SMBIOS raw-data tag + * + * @v _type		SMBIOS structure type number + * @v _structure	SMBIOS structure data type + * @v _field		Field within SMBIOS structure data type + * @ret tag		SMBIOS setting tag + */ +#define SMBIOS_RAW_TAG( _type, _structure, _field )		\ +	( ( (_type) << 16 ) |					\ +	  ( offsetof ( _structure, _field ) << 8 ) |		\ +	  ( sizeof ( ( ( _structure * ) 0 )->_field ) ) ) + +/** + * Construct SMBIOS string tag + * + * @v _type		SMBIOS structure type number + * @v _structure	SMBIOS structure data type + * @v _field		Field within SMBIOS structure data type + * @ret tag		SMBIOS setting tag + */ +#define SMBIOS_STRING_TAG( _type, _structure, _field )		\ +	( ( (_type) << 16 ) |					\ +	  ( offsetof ( _structure, _field ) << 8 ) ) + +/** + * Check applicability of SMBIOS setting + * + * @v settings		Settings block + * @v setting		Setting + * @ret applies		Setting applies within this settings block + */ +static int smbios_applies ( struct settings *settings __unused, +			    const struct setting *setting ) { + +	return ( setting->scope == &smbios_settings_scope ); +} + +/** + * Fetch value of SMBIOS setting + * + * @v settings		Settings block, or NULL to search all blocks + * @v setting		Setting to fetch + * @v data		Buffer to fill with setting data + * @v len		Length of buffer + * @ret len		Length of setting data, or negative error + */ +static int smbios_fetch ( struct settings *settings __unused, +			  struct setting *setting, +			  void *data, size_t len ) { +	struct smbios_structure structure; +	unsigned int tag_instance; +	unsigned int tag_type; +	unsigned int tag_offset; +	unsigned int tag_len; +	int rc; + +	/* Split tag into instance, type, offset and length */ +	tag_instance = ( ( setting->tag >> 24 ) & 0xff ); +	tag_type = ( ( setting->tag >> 16 ) & 0xff ); +	tag_offset = ( ( setting->tag >> 8 ) & 0xff ); +	tag_len = ( setting->tag & 0xff ); + +	/* Find SMBIOS structure */ +	if ( ( rc = find_smbios_structure ( tag_type, tag_instance, +					    &structure ) ) != 0 ) +		return rc; + +	{ +		uint8_t buf[structure.header.len]; +		const void *raw; +		union uuid uuid; +		unsigned int index; + +		/* Read SMBIOS structure */ +		if ( ( rc = read_smbios_structure ( &structure, buf, +						    sizeof ( buf ) ) ) != 0 ) +			return rc; + +		/* A <length> of zero indicates that the byte at +		 * <offset> contains a string index.  An <offset> of +		 * zero indicates that the <length> contains a literal +		 * string index. +		 */ +		if ( ( tag_len == 0 ) || ( tag_offset == 0 ) ) { +			index = ( ( tag_offset == 0 ) ? +				  tag_len : buf[tag_offset] ); +			if ( ( rc = read_smbios_string ( &structure, index, +							 data, len ) ) < 0 ) { +				return rc; +			} +			if ( ! setting->type ) +				setting->type = &setting_type_string; +			return rc; +		} + +		/* Mangle UUIDs if necessary.  iPXE treats UUIDs as +		 * being in network byte order (big-endian).  SMBIOS +		 * specification version 2.6 states that UUIDs are +		 * stored with little-endian values in the first three +		 * fields; earlier versions did not specify an +		 * endianness.  dmidecode assumes that the byte order +		 * is little-endian if and only if the SMBIOS version +		 * is 2.6 or higher; we match this behaviour. +		 */ +		raw = &buf[tag_offset]; +		if ( ( setting->type == &setting_type_uuid ) && +		     ( tag_len == sizeof ( uuid ) ) && +		     ( smbios_version() >= SMBIOS_VERSION ( 2, 6 ) ) ) { +			DBG ( "SMBIOS detected mangled UUID\n" ); +			memcpy ( &uuid, &buf[tag_offset], sizeof ( uuid ) ); +			uuid_mangle ( &uuid ); +			raw = &uuid; +		} + +		/* Return data */ +		if ( len > tag_len ) +			len = tag_len; +		memcpy ( data, raw, len ); +		if ( ! setting->type ) +			setting->type = &setting_type_hex; +		return tag_len; +	} +} + +/** SMBIOS settings operations */ +static struct settings_operations smbios_settings_operations = { +	.applies = smbios_applies, +	.fetch = smbios_fetch, +}; + +/** SMBIOS settings */ +static struct settings smbios_settings = { +	.refcnt = NULL, +	.siblings = LIST_HEAD_INIT ( smbios_settings.siblings ), +	.children = LIST_HEAD_INIT ( smbios_settings.children ), +	.op = &smbios_settings_operations, +	.default_scope = &smbios_settings_scope, +}; + +/** Initialise SMBIOS settings */ +static void smbios_init ( void ) { +	int rc; + +	if ( ( rc = register_settings ( &smbios_settings, NULL, +					"smbios" ) ) != 0 ) { +		DBG ( "SMBIOS could not register settings: %s\n", +		      strerror ( rc ) ); +		return; +	} +} + +/** SMBIOS settings initialiser */ +struct init_fn smbios_init_fn __init_fn ( INIT_NORMAL ) = { +	.initialise = smbios_init, +}; + +/** UUID setting obtained via SMBIOS */ +const struct setting uuid_setting __setting ( SETTING_HOST, uuid ) = { +	.name = "uuid", +	.description = "UUID", +	.tag = SMBIOS_RAW_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				struct smbios_system_information, uuid ), +	.type = &setting_type_uuid, +	.scope = &smbios_settings_scope, +}; + +/** Manufacturer name setting */ +const struct setting manufacturer_setting __setting ( SETTING_HOST_EXTRA, +						      manufacturer ) = { +	.name = "manufacturer", +	.description = "Manufacturer", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   manufacturer ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Product name setting */ +const struct setting product_setting __setting ( SETTING_HOST_EXTRA, product )={ +	.name = "product", +	.description = "Product name", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   product ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Serial number setting */ +const struct setting serial_setting __setting ( SETTING_HOST_EXTRA, serial ) = { +	.name = "serial", +	.description = "Serial number", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   serial ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Asset tag setting */ +const struct setting asset_setting __setting ( SETTING_HOST_EXTRA, asset ) = { +	.name = "asset", +	.description = "Asset tag", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_ENCLOSURE_INFORMATION, +				   struct smbios_enclosure_information, +				   asset_tag ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Board serial number setting (may differ from chassis serial number) */ +const struct setting board_serial_setting __setting ( SETTING_HOST_EXTRA, +						      board_serial ) = { +	.name = "board-serial", +	.description = "Base board serial", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_BASE_BOARD_INFORMATION, +				   struct smbios_base_board_information, +				   serial ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +};  | 
