grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v3] Add a module for retrieving SMBIOS information


From: David Michael
Subject: [PATCH v3] Add a module for retrieving SMBIOS information
Date: Sun, 22 Mar 2015 22:01:49 -0400

The following are two use cases from Rajat Jain <address@hidden>:

1) We have a board that boots Linux and this board itself can be plugged into 
one of different chassis types. We need to pass different parameters to the 
kernel based on the "CHASSIS_TYPE" information that is passed by the bios in 
the DMI / SMBIOS tables.

2) We may have a USB stick that can go into multiple boards, and the exact 
kernel to be loaded depends on the machine information (PRODUCT_NAME etc) 
passed via the DMI.
---

Changes since v2:

* Switched to language like "string set" and "SMBIOS structure" to use
  terminology consistent with the specification.

To address points suggested by Andrei Borzenkov:

* Dropped ChangeLog text from the commit message.

* Changed to long options in the documentation.

* Renamed --variable to --set.

* Exit with an error when given out-of-range option values instead of
  resetting the option.

* Functions were added to retrieve data types that should avoid
  alignment and endianness issues.

* Force string set searches to terminate at end of table.

* Each data type now has a separate size/bounds test.

* Error messages have better explanations.

 docs/grub.texi              |  65 +++++++
 grub-core/Makefile.core.def |   7 +
 grub-core/commands/smbios.c | 445 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 517 insertions(+)
 create mode 100644 grub-core/commands/smbios.c

diff --git a/docs/grub.texi b/docs/grub.texi
index 46b9e7f..73f0909 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -3830,6 +3830,7 @@ you forget a command, you can run the command 
@command{help}
 * 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 @@ if timeout was interrupted by @key{ESC}.
 @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
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 8eaae45..eabc5c2 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1011,6 +1011,13 @@ module = {
 };
 
 module = {
+  name = smbios;
+  common = commands/smbios.c;
+  enable = efi;
+  enable = x86;
+};
+
+module = {
   name = suspend;
   ieee1275 = commands/ieee1275/suspend.c;
   enable = i386_ieee1275;
diff --git a/grub-core/commands/smbios.c b/grub-core/commands/smbios.c
new file mode 100644
index 0000000..899a9c3
--- /dev/null
+++ b/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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/extcmd.h>
+#include <grub/env.h>
+#include <grub/i18n.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+#ifdef GRUB_MACHINE_EFI
+#include <grub/efi/efi.h>
+#else
+#include <grub/acpi.h>
+#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);
+}
-- 
2.1.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]