Add a module for retrieving SMBIOS information. --- docs/grub.texi +++ docs/grub.texi @@ -3830,6 +3830,7 @@ * sha256sum:: Compute or check SHA256 hash * sha512sum:: Compute or check SHA512 hash * sleep:: Wait for a specified number of seconds +* smbios:: Retrieve SMBIOS information * source:: Read a configuration file in same context * test:: Check file types and compare values * true:: Do nothing, successfully @@ -4944,6 +4945,70 @@ @end deffn address@hidden smbios address@hidden smbios + address@hidden Command smbios @ + address@hidden @var{type}] @ + address@hidden @var{handle}] @ + address@hidden @var{match}] @ + [(@option{--get-byte} | @option{--get-word} | @option{--get-dword} | @ + @option{--get-qword} | @option{--get-string}) @ + @var{offset} address@hidden @var{variable}]] +Retrieve SMBIOS information. This command is only available on x86 and EFI +systems. + +When run with no options, @command{smbios} will print the SMBIOS structures to +the console as the header values, a hex dump, and a string set. The following +options can filter which structures are printed. + address@hidden @bullet address@hidden +Specifying @option{--type} will only print structures with a matching address@hidden The type can be any value from 0 to 255. address@hidden +Specifying @option{--handle} will only print structures with a matching address@hidden The handle can be any value from 0 to 65535. address@hidden +Specifying @option{--match} will only print entry number @var{match} in the +list of filtered structures; e.g. @code{smbios --type 4 --match 2} will print +the second Process Information (Type 4) structure. The list is always ordered +the same as the hardware's SMBIOS table. The match number must be positive. address@hidden itemize + +The remaining options print the value of a specific field in the first filtered +SMBIOS structure. Only one of these options may be specified. + address@hidden @bullet address@hidden +When given @option{--get-byte}, print the value of the byte +at @var{offset} bytes into the first filtered SMBIOS structure. address@hidden +When given @option{--get-word}, print the value of the word (two bytes) +at @var{offset} bytes into the first filtered SMBIOS structure. address@hidden +When given @option{--get-dword}, print the value of the dword (four bytes) +at @var{offset} bytes into the first filtered SMBIOS structure. address@hidden +When given @option{--get-qword}, print the value of the qword (eight bytes) +at @var{offset} bytes into the first filtered SMBIOS structure. address@hidden +When given @option{--get-string}, print the string with its index found +at @var{offset} bytes into the first filtered SMBIOS structure. address@hidden itemize + +If any of the options in the above list were given, a variable name can be +specified with @option{--set} to store the value instead of printing it. + +For example, this will store and display the system manufacturer string. + address@hidden +smbios --type 1 --get-string 4 --set system_manufacturer +echo $system_manufacturer address@hidden example address@hidden deffn + + @node source @subsection source --- grub-core/Makefile.core.def +++ grub-core/Makefile.core.def @@ -1023,6 +1023,13 @@ }; module = { + name = smbios; + common = commands/smbios.c; + enable = efi; + enable = x86; +}; + +module = { name = suspend; ieee1275 = commands/ieee1275/suspend.c; enable = i386_ieee1275; --- /dev/null +++ grub-core/commands/smbios.c @@ -0,0 +1,445 @@ +/* Expose SMBIOS data to the console and configuration files */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013,2014,2015 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#ifdef GRUB_MACHINE_EFI +#include +#else +#include +#endif + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* Reference: DMTF Standard DSP0134 2.7.1 Table 1 */ + +struct __attribute__ ((packed)) grub_smbios_ieps + { + grub_uint8_t anchor[5]; /* "_DMI_" */ + grub_uint8_t checksum; + grub_uint16_t table_length; + grub_uint32_t table_address; + grub_uint16_t structures; + grub_uint8_t revision; + }; + +struct __attribute__ ((packed)) grub_smbios_eps + { + grub_uint8_t anchor[4]; /* "_SM_" */ + grub_uint8_t checksum; + grub_uint8_t length; + grub_uint8_t version_major; + grub_uint8_t version_minor; + grub_uint16_t maximum_structure_size; + grub_uint8_t revision; + grub_uint8_t formatted[5]; + struct grub_smbios_ieps intermediate; + }; + +#define eps_table_begin(eps) ((grub_addr_t)((eps)->intermediate.table_address)) +#define eps_table_end(eps) \ + ((grub_addr_t)((eps)->intermediate.table_address + \ + (eps)->intermediate.table_length)) + +static const struct grub_smbios_eps *eps = NULL; + +/* Reference: DMTF Standard DSP0134 2.7.1 Section 5.2.1 */ + +/* + * In order for any of this module to function, it needs to find an entry point + * structure. This returns a pointer to a struct that identifies the fields of + * the EPS, or NULL if it cannot be located. + */ +static const struct grub_smbios_eps * +grub_smbios_locate_eps (void) +{ +#ifdef GRUB_MACHINE_EFI + static const grub_efi_guid_t smbios_guid = GRUB_EFI_SMBIOS_TABLE_GUID; + const grub_efi_system_table_t *st = grub_efi_system_table; + const grub_efi_configuration_table_t *t = st->configuration_table; + unsigned int i; + + for (i = 0; i < st->num_table_entries; i++) + if (grub_memcmp (&smbios_guid, &t->vendor_guid, sizeof (smbios_guid)) == 0) + { + grub_dprintf ("smbios", "Found entry point structure at %p\n", + t->vendor_table); + return (const struct grub_smbios_eps *)t->vendor_table; + } + else + t++; +#else + grub_uint8_t *ptr; + + for (ptr = (grub_uint8_t *)0x000F0000; + ptr < (grub_uint8_t *)0x00100000; + ptr += 16) + if (grub_memcmp (ptr, "_SM_", 4) == 0 + && grub_byte_checksum (ptr, ptr[5]) == 0) + { + grub_dprintf ("smbios", "Found entry point structure at %p\n", ptr); + return (const struct grub_smbios_eps *)ptr; + } +#endif + + grub_dprintf ("smbios", "Failed to locate entry point structure\n"); + return NULL; +} + +/* + * Given a pointer to an SMBIOS structure, return the unsigned little-endian + * value of the requested number of bytes. These functions avoid alignment and + * endianness issues. + */ +static inline grub_uint8_t +grub_smbios_get_byte (const grub_uint8_t *structure, grub_uint8_t offset) +{ + return structure[offset]; +} + +static inline grub_uint16_t +grub_smbios_get_word (const grub_uint8_t *structure, grub_uint8_t offset) +{ + return (grub_uint16_t)(grub_smbios_get_byte (structure, offset) | + ((grub_uint16_t)grub_smbios_get_byte (structure, offset + 1) << 8)); +} + +static inline grub_uint32_t +grub_smbios_get_dword (const grub_uint8_t *structure, grub_uint8_t offset) +{ + return (grub_uint32_t)(grub_smbios_get_word (structure, offset) | + ((grub_uint32_t)grub_smbios_get_word (structure, offset + 2) << 16)); +} + +static inline grub_uint64_t +grub_smbios_get_qword (const grub_uint8_t *structure, grub_uint8_t offset) +{ + return (grub_uint64_t)(grub_smbios_get_dword (structure, offset) | + ((grub_uint64_t)grub_smbios_get_dword (structure, offset + 4) << 32)); +} + +/* Reference: DMTF Standard DSP0134 2.7.1 Section 6.1.3 */ + +static const char * +grub_smbios_get_string (const struct grub_smbios_eps *ep, + const grub_uint8_t *structure, + grub_uint8_t offset) +{ + const char *table_end = (const char *)eps_table_end (ep); + const char *ptr = (const char *)(structure + structure[1]); + const grub_uint8_t referenced_string_number = structure[offset]; + grub_uint8_t i; + + /* A string referenced with zero is interpreted as unset. */ + if (referenced_string_number == 0) + return NULL; + + /* Search the string set. */ + for (i = 1; *ptr != 0 && ptr < table_end; i++) + if (i == referenced_string_number) + return ptr; + else + while (*ptr++ != 0 && ptr < table_end); + + /* The string number is greater than the number of strings in the set. */ + return NULL; +} + +/* Reference: DMTF Standard DSP0134 2.7.1 Sections 6.1.2-6.1.3 */ + +/* + * Given a pointer to an SMBIOS structure, print its contents to the console. + * Return the size of the structure printed in bytes. + */ +static grub_uint16_t +grub_smbios_dump_structure (const struct grub_smbios_eps *ep, + const grub_uint8_t *structure) +{ + const grub_uint8_t *ptr = structure; + const grub_uint8_t *table_end = (const grub_uint8_t *)eps_table_end (ep); + grub_uint8_t length = ptr[1]; + + /* Write the SMBIOS structure's mandatory four header bytes. */ + grub_printf ("Structure: Type=%u Length=%u Handle=%u\n", + ptr[0], length, grub_smbios_get_word (ptr, 2)); + + /* Dump of the formatted area (including the header) in hex. */ + grub_printf (" Hex Dump: "); + while (length-- > 0) + grub_printf ("%02x", *ptr++); + grub_printf ("\n"); + + /* Print each string found in the appended string set. */ + if (*ptr == 0) + ptr++; + while (*ptr != 0 && ptr < table_end) + ptr += grub_printf (" String: %s\n", ptr) - sizeof (" String: ") + 1; + ptr++; + + /* Return the total number of bytes covered. */ + return ptr - structure; +} + +/* Reference: DMTF Standard DSP0134 2.7.1 Sections 6.1.2-6.1.3 */ + +/* + * Return or print a matched SMBIOS structure. Multiple entries can be matched + * if they are being printed. + * + * This method can use up to three criteria for selecting a structure: + * - The "type" field (use -1 to ignore) + * - The "handle" field (use -1 to ignore) + * - Which to return if several match (use 0 to print all matches) + * + * The parameter "print" was added for verbose debugging. When non-zero, the + * matched entries are printed to the console instead of returned. + */ +static const grub_uint8_t * +grub_smbios_match_structure (const struct grub_smbios_eps *ep, + const grub_int16_t type, + const grub_int32_t handle, + const grub_uint16_t match, + const grub_uint8_t print) +{ + const grub_uint8_t *ptr = (const grub_uint8_t *)eps_table_begin (ep); + const grub_uint8_t *table_end = (const grub_uint8_t *)eps_table_end (ep); + grub_uint16_t structures = ep->intermediate.structures; + grub_uint16_t structure = 0; + grub_uint16_t matches = 0; + + while (structure++ < structures && ptr < table_end) + { + grub_uint16_t structure_handle = grub_smbios_get_word (ptr, 2); + grub_uint8_t structure_type = ptr[0]; + + /* Test if the current structure matches the given parameters. */ + if ((handle < 0 || handle == structure_handle) + && (type < 0 || type == structure_type) + && (match == 0 || match == ++matches)) + { + if (print) + { + ptr += grub_smbios_dump_structure (ep, ptr); + if (match > 0) + break; + } + else + return ptr; + } + + /* If the structure didn't match, skip it. */ + else + { + ptr += ptr[1]; + while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end); + } + } + + return NULL; +} + +static grub_err_t +grub_cmd_smbios (grub_extcmd_context_t ctxt, + int argc __attribute__ ((unused)), + char **argv __attribute__ ((unused))) +{ + struct grub_arg_list *state = ctxt->state; + + grub_int16_t type = -1; + grub_int32_t handle = -1; + grub_uint16_t match = 0; + grub_uint8_t offset = 0; + + grub_int32_t option; + const grub_uint8_t *structure; + grub_uint8_t accessors; + grub_uint8_t i; + char buffer[24]; /* 64-bit number -> maximum 20 decimal digits */ + const char *value = buffer; + + if (eps == NULL) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + N_("the SMBIOS entry point structure was not found")); + + /* Only one value can be returned at a time; reject multiple selections. */ + accessors = !!state[3].set + !!state[4].set + !!state[5].set + + !!state[6].set + !!state[7].set; + if (accessors > 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("only one of the --get options can be specified")); + + /* Reject the environment variable if no value was selected. */ + if (accessors == 0 && state[8].set) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("using --set requires specifying a --get option")); + + /* Read the given filtering options. */ + if (state[0].set) + { + option = grub_strtol (state[0].arg, NULL, 0); + if (option < 0 || option > 255) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the type must be between 0 and 255")); + type = (grub_int16_t)option; + } + if (state[1].set) + { + option = grub_strtol (state[1].arg, NULL, 0); + if (option < 0 || option > 65535) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the handle must be between 0 and 65535")); + handle = (grub_int32_t)option; + } + if (state[2].set) + { + option = grub_strtol (state[2].arg, NULL, 0); + if (option <= 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the match must be a positive integer")); + match = (grub_uint16_t)option; + } + + /* When not selecting a value, print all matching structures and quit. */ + if (accessors == 0) + { + grub_smbios_match_structure (eps, type, handle, match, 1); + return GRUB_ERR_NONE; + } + + /* Select a single structure from the given filtering options. */ + structure = grub_smbios_match_structure (eps, type, handle, match, 0); + if (structure == NULL) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + N_("no SMBIOS structure matched the given options")); + + /* Retrieve the requested byte offset into the structure. */ + for (i = 3; i <= 7; i++) + if (state[i].set) + { + option = grub_strtol (state[i].arg, NULL, 0); + if (option < 0 || option >= structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the given offset is outside the structure")); + offset = (grub_uint8_t)option; + break; + } + + /* If a string was requested, try to find its pointer. */ + if (state[7].set) + { + if (offset + 1 > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the string index ends outside the structure")); + value = grub_smbios_get_string (eps, structure, offset); + if (value == NULL) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the requested string is not defined")); + } + + /* Create a string from a numeric value suitable for printing. */ + else if (state[3].set) + { + if (offset + 1 > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the byte ends outside the structure")); + grub_snprintf (buffer, sizeof (buffer), "%u", + grub_smbios_get_byte (structure, offset)); + } + else if (state[4].set) + { + if (offset + 2 > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the word ends outside the structure")); + grub_snprintf (buffer, sizeof (buffer), "%u", + grub_smbios_get_word (structure, offset)); + } + else if (state[5].set) + { + if (offset + 4 > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the dword ends outside the structure")); + grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT32_T, + grub_smbios_get_dword (structure, offset)); + } + else if (state[6].set) + { + if (offset + 8 > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the qword ends outside the structure")); + grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT64_T, + grub_smbios_get_qword (structure, offset)); + } + + /* Store or print the requested value. */ + if (state[8].set) + { + grub_env_set (state[8].arg, value); + grub_env_export (state[8].arg); + } + else + grub_printf ("%s\n", value); + + return GRUB_ERR_NONE; +} + +static grub_extcmd_t cmd; + +static const struct grub_arg_option options[] = + { + {"type", 't', 0, N_("Match entries with the given type."), + N_("type"), ARG_TYPE_INT}, + {"handle", 'h', 0, N_("Match entries with the given handle."), + N_("handle"), ARG_TYPE_INT}, + {"match", 'm', 0, N_("Select a structure when several match."), + N_("match"), ARG_TYPE_INT}, + {"get-byte", 'b', 0, N_("Get the byte's value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-word", 'w', 0, N_("Get two bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-dword", 'd', 0, N_("Get four bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-qword", 'q', 0, N_("Get eight bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-string", 's', 0, N_("Get the string specified at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"set", '\0', 0, N_("Store the value in the given variable name."), + N_("variable"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +GRUB_MOD_INIT(smbios) +{ + /* SMBIOS data is supposed to be static, so find it only once during init. */ + eps = grub_smbios_locate_eps (); + + cmd = grub_register_extcmd ("smbios", grub_cmd_smbios, 0, + N_("[-t type] [-h handle] [-m match] " + "[(-b|-w|-d|-q|-s) offset [--set variable]]"), + N_("Retrieve SMBIOS information."), options); +} + +GRUB_MOD_FINI(smbios) +{ + grub_unregister_extcmd (cmd); +}