Skip to contentSkip to conversation

Add a module for retrieving SMBIOS information

PATCH In review David Michael 2 participants 2 messages Aug 16, 2015 – Sep 19, 2015

lore
Share & tools
lore
#0 lore raw

PATCH v4 2/2 Add a module for retrieving SMBIOS information

The following are two use cases from Rajat Jain <[email protected]>:

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 v3:

* Added handlers for each --get option that specify their data type's length and a formatting function to hopefully make additions easier.

* Added a check that returned string values are terminated properly.

* Made the table-crawling loop exit on an end-of-table structure.

* Copyright assignment was handled.

To address points suggested by Vladimir Serbinenko:

* Moved the EPS-location functions to standalone files to be shared by different modules a la ACPI.

* Removed the byte-ordering functions in favor of unaligned functions.

* Removed the hex dump. Actually, removed the whole structure printing function since hex dump was the core of it. (Using a --get option is required now.)

* Made the table-crawling loop abort on invalid structure lengths.

* Switched some return statements to GRUB_ERR_IO.

To address points suggested by Andrei Borzenkov:

* Added support for the SMBIOS v3.0.0 (64-bit) specification. Note that when both v2 and v3 are found, v3 is always used since the specification requires the v3 table to be a superset of the v2 table.

* Added an abstraction layer to hide the v2/v3 EPS differences.

* Removed the environment variable export.

* Added an option to get a UUID.

Issues not addressed:

* This patch doesn't add any command options to fetch fields by readable names. I'm not against having the option (aside from the reasons Andrei mentioned), but I would like to keep the ability to specify structure types and offsets. Limiting access to known names would make it impossible to use the non-standard custom/OEM structures.

* The UUID string representation won't always match dmidecode. UUIDs from v2.6 and newer are required by the spec to use little-endian encoding for the first three fields, but it is unspecified before that. In dmidecode, all UUIDs from v2.5 and older are assumed to be big-endian. This patch just uses little-endian. Do we want to make the same assumption to match dmidecode output on old systems?

 docs/grub.texi                       |  69 +++++++
 grub-core/Makefile.core.def          |  15 ++
 grub-core/commands/efi/smbios.c      |  59 ++++++
 grub-core/commands/i386/pc/smbios.c  |  50 +++++
 grub-core/commands/smbios.c          | 372 +++++++++++++++++++++++++++++++++++
 grub-core/efiemu/i386/pc/cfgtables.c |  44 ++---
 include/grub/smbios.h                |  68 +++++++
 7 files changed, 648 insertions(+), 29 deletions(-)
 create mode 100644 grub-core/commands/efi/smbios.c
 create mode 100644 grub-core/commands/i386/pc/smbios.c
 create mode 100644 grub-core/commands/smbios.c
 create mode 100644 include/grub/smbios.h
docs/grub.texi +69 -0
index b9f41a7..aa9d73d 100644
@@ -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,74 @@ if timeout was interrupted by @key{ESC}.
 @end deffn
 
 
+@node smbios
+@subsection smbios
+
+@deffn Command smbios @
+ [@option{--type} @var{type}] @
+ [@option{--handle} @var{handle}] @
+ [@option{--match} @var{match}] @
+ (@option{--get-byte} | @option{--get-word} | @option{--get-dword} | @
+  @option{--get-qword} | @option{--get-string} | @option{--get-uuid}) @
+ @var{offset} @
+ [@option{--set} @var{variable}]
+Retrieve SMBIOS information.
+
+The @command{smbios} command returns the value of a field in an SMBIOS
+structure.  The following options determine which structure to select.
+
+@itemize @bullet
+@item
+Specifying @option{--type} will select structures with a matching
+@var{type}.  The type can be any integer from 0 to 255.
+@item
+Specifying @option{--handle} will select structures with a matching
+@var{handle}.  The handle can be any integer from 0 to 65535.
+@item
+Specifying @option{--match} will select structure number @var{match} in the
+filtered list of structures; e.g. @code{smbios --type 4 --match 2} will select
+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 a positive
+integer.  If unspecified, the first matching structure will be selected.
+@end itemize
+
+The remaining options determine which field in the selected SMBIOS structure to
+return.  Only one of these options may be specified at a time.
+
+@itemize @bullet
+@item
+When given @option{--get-byte}, return the value of the byte
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-word}, return the value of the word (two bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-dword}, return the value of the dword (four bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-qword}, return the value of the qword (eight bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-string}, return the string with its index found
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-uuid}, return the value of the UUID (sixteen bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@end itemize
+
+The default action is to print the value of the requested field to the console,
+but a variable name can be specified with @option{--set} to store the value
+instead of printing it.
+
+For example, this will store and then display the system manufacturer's name.
+
+@example
+smbios --type 1 --get-string 4 --set system_manufacturer
+echo $system_manufacturer
+@end example
+@end deffn
+
+
 @node source
 @subsection source
 
grub-core/Makefile.core.def +15 -0
index 9764cd2..bb04be6 100644
@@ -1009,6 +1009,21 @@ module = {
 };
 
 module = {
+  name = smbios;
+
+  common = commands/smbios.c;
+  efi = commands/efi/smbios.c;
+  i386_pc = commands/i386/pc/smbios.c;
+  i386_coreboot = commands/i386/pc/smbios.c;
+  i386_multiboot = commands/i386/pc/smbios.c;
+
+  enable = efi;
+  enable = i386_pc;
+  enable = i386_coreboot;
+  enable = i386_multiboot;
+};
+
+module = {
   name = suspend;
   ieee1275 = commands/ieee1275/suspend.c;
   enable = i386_ieee1275;
grub-core/commands/efi/smbios.c new file +59 -0

59 lines not on this page · raw message

grub-core/commands/i386/pc/smbios.c new file +50 -0

50 lines not on this page · raw message

grub-core/commands/smbios.c new file +372 -0

372 lines not on this page · raw message

grub-core/efiemu/i386/pc/cfgtables.c +15 -29

71 lines not on this page · raw message

include/grub/smbios.h new file +68 -0

68 lines not on this page · raw message

Signature · 1 line
2.1.0
#1 lore raw

16.08.2015 05:27, David Michael пишет:

Quoted 4 lines
docs/grub.texi +4 -0
+
+@itemize @bullet
+@item
+When given @option{--get-byte}, return the value of the byte

docs/grub.texi if timeout was interrupted by @key{ESC}. · line 4994

Quoted 10 lines
docs/grub.texi +10 -0
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-word}, return the value of the word (two bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-dword}, return the value of the dword (four bytes)
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-qword}, return the value of the qword (eight bytes)
+at @var{offset} bytes into the selected SMBIOS structure.

It probably should be more specific that value is returned as unsigned integer. Note that test command evaluates integers as simple int so it may not be able to compare qword as integers (I assume int is 32 bit on all supported platforms).

docs/grub.texi if timeout was interrupted by @key{ESC}. · line 5000

Quoted 6 lines

+@item

docs/grub.texi +5 -0
+When given @option{--get-string}, return the string with its index found
+at @var{offset} bytes into the selected SMBIOS structure.
+@item
+When given @option{--get-uuid}, return the value of the UUID (sixteen bytes)
+at @var{offset} bytes into the selected SMBIOS structure.

Also format of UUID needs clarification. We had customer call because BIOS and monitoring software did not agree on byte order in UUID.

...

grub-core/commands/smbios.c line 4

Quoted 10 lines
grub-core/commands/smbios.c new file +4 -0
new file mode 100644
index 0000000..8218631
@@ -0,0 +1,372 @@
+/* smbios.c - retrieve smbios information. */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013,2014,2015  Free Software Foundation, Inc.at

That's new file, how comes it has copyright in the past?

Quoted 313 lines
grub-core/commands/smbios.c new file +313 -0
+ *
+ *  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/env.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/smbios.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+/* Locate the SMBIOS entry point structure depending on the hardware. */
+struct grub_smbios_eps *
+grub_smbios_get_eps (void)
+{
+  static struct grub_smbios_eps *eps = NULL;
+  if (eps != NULL)
+    return eps;
+  eps = grub_machine_smbios_get_eps ();
+  return eps;
+}
+
+/* Locate the SMBIOS3 entry point structure depending on the hardware. */
+struct grub_smbios_eps3 *
+grub_smbios_get_eps3 (void)
+{
+  static struct grub_smbios_eps3 *eps = NULL;
+  if (eps != NULL)
+    return eps;
+  eps = grub_machine_smbios_get_eps3 ();
+  return eps;
+}
+
+/* Abstract useful values found in either the SMBIOS3 or SMBIOS EPS. */
+static struct {
+  grub_addr_t start;
+  grub_addr_t end;
+  grub_uint16_t structures;
+} table_desc = {0, 0, 0};
+
+
+/*
+ * These functions convert values from the various SMBIOS structure field types
+ * into a string formatted to be returned to the user.  They expect that the
+ * structure and offset were already validated.  The given buffer stores the
+ * newly formatted string if needed.  When the requested data is successfully
+ * retrieved and formatted, the pointer to the string is returned; otherwise,
+ * NULL is returned on failure.
+ */
+
+static const char *
+grub_smbios_format_byte (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_snprintf (buffer, size, "%u", structure[offset]);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_word (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint16_t value = grub_get_unaligned16 (structure + offset);
+  grub_snprintf (buffer, size, "%u", value);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_dword (char *buffer, grub_size_t size,
+                          const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint32_t value = grub_get_unaligned32 (structure + offset);
+  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT32_T, value);
+  return (const char *)buffer;
+}
+
+static const char *
+grub_smbios_format_qword (char *buffer, grub_size_t size,
+                          const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  grub_uint64_t value = grub_get_unaligned64 (structure + offset);
+  grub_snprintf (buffer, size, "%" PRIuGRUB_UINT64_T, value);
+  return (const char *)buffer;
+}
+
+/* The matching string pointer is returned directly to avoid extra copying. */
+static const char *
+grub_smbios_get_string (char *buffer __attribute__ ((unused)),
+                        grub_size_t size __attribute__ ((unused)),
+                        const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  const grub_uint8_t *ptr = structure + structure[1];
+  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
+  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)
+      {
+        const char *str = (const char *)ptr;
+        while (*ptr++ != 0)
+          if (ptr >= table_end)
+            return NULL; /* The string isn't terminated. */
+        return str;
+      }
+    else
+      while (*ptr++ != 0 && ptr < table_end);
+
+  /* The string number is greater than the number of strings in the set. */
+  return NULL;
+}
+
+static const char *
+grub_smbios_format_uuid (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset)
+{
+  const grub_uint8_t *f = structure + offset; /* little-endian fields */
+  const grub_uint8_t *g = f + 8; /* byte-by-byte fields */
+  grub_snprintf (buffer, size,
+                 "%02x%02x%02x%02x-%02x%02x-%02x%02x-"
+                 "%02x%02x-%02x%02x%02x%02x%02x%02x",
+                 f[3], f[2], f[1], f[0], f[5], f[4], f[7], f[6],
+                 g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7]);
+  return (const char *)buffer;
+}
+
+
+/* List the field formatting functions and the number of bytes they need. */
+#define MAXIMUM_FORMAT_LENGTH (sizeof ("ffffffff-ffff-ffff-ffff-ffffffffffff"))
+static const struct {
+  const char *(*format) (char *buffer, grub_size_t size,
+                         const grub_uint8_t *structure, grub_uint8_t offset);
+  grub_uint8_t field_length;
+} field_extractors[] = {
+  {grub_smbios_format_byte, 1},
+  {grub_smbios_format_word, 2},
+  {grub_smbios_format_dword, 4},
+  {grub_smbios_format_qword, 8},
+  {grub_smbios_get_string, 1},
+  {grub_smbios_format_uuid, 16}
+};
+
+/* List command options, with structure field getters ordered as above. */
+#define FIRST_GETTER_OPT (3)
+#define SETTER_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors))
+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},
+  {"get-uuid",   'u', 0, N_("Get the UUID's value 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}
+};
+
+
+/*
+ * Return a matching SMBIOS structure.
+ *
+ * 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 ignore)
+ *
+ * The return value is a pointer to the first matching structure.  If no
+ * structures match the given parameters, NULL is returned.
+ */
+static const grub_uint8_t *
+grub_smbios_match_structure (const grub_int16_t type,
+                             const grub_int32_t handle,
+                             const grub_uint16_t match)
+{
+  const grub_uint8_t *ptr = (const grub_uint8_t *)table_desc.start;
+  const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end;
+  grub_uint16_t structures = table_desc.structures;
+  grub_uint16_t structure_count = 0;
+  grub_uint16_t matches = 0;
+
+  while (ptr < table_end
+         && ptr[1] >= 4 /* Valid structures include the 4-byte header. */
+         && (structure_count++ < structures || structures == 0))
+    {
+      grub_uint16_t structure_handle = grub_get_unaligned16 (ptr + 2);
+      grub_uint8_t structure_type = ptr[0];
+
+      if ((handle < 0 || handle == structure_handle)
+          && (type < 0 || type == structure_type)
+          && (match == 0 || match == ++matches))
+        return ptr;
+
+      else
+        {
+          ptr += ptr[1];
+          while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end);
+        }
+
+      if (structure_type == GRUB_SMBIOS_TYPE_END_OF_TABLE)
+        break;
+    }
+
+  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;
+
+  const grub_uint8_t *structure;
+  const char *value;
+  char buffer[MAXIMUM_FORMAT_LENGTH];
+  grub_int32_t option;
+  grub_int8_t field_type = -1;
+  grub_uint8_t i;
+
+  if (table_desc.start == 0)
+    return grub_error (GRUB_ERR_IO,
+                       N_("the SMBIOS entry point structure was not found"));
+
+  /* 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;
+    }
+
+  /* Determine the data type of the structure field to retrieve. */
+  for (i = 0; i < ARRAY_SIZE(field_extractors); i++)
+    if (state[FIRST_GETTER_OPT + i].set)
+      {
+        if (field_type >= 0)
+          return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                             N_("only one --get option is usable at a time"));
+        field_type = i;
+      }
+
+  /* Require a choice of a structure field to return. */
+  if (field_type < 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                       N_("one of the --get options is required"));
+
+  /* Locate a matching SMBIOS structure. */
+  structure = grub_smbios_match_structure (type, handle, match);
+  if (structure == NULL)
+    return grub_error (GRUB_ERR_IO,
+                       N_("no structure matched the given options"));
+
+  /* Ensure the requested byte offset is inside the structure. */
+  option = grub_strtol (state[FIRST_GETTER_OPT + field_type].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"));

grub-core/commands/smbios.c line 327

Quoted 10 lines
grub-core/commands/smbios.c new file +10 -0
+
+  /* Ensure the requested data type at the offset is inside the structure. */
+  offset = (grub_uint8_t)option;
+  if (offset + field_extractors[field_type].field_length > structure[1])
+    return grub_error (GRUB_ERR_OUT_OF_RANGE,
+                       N_("the field ends outside the structure"));
+
+  /* Format the requested structure field into a readable string. */
+  value = field_extractors[field_type].format (buffer, sizeof (buffer),
+                                               structure, offset);

Do you really need to pass buffer as argument? It is static anyway.

Otherwise I'm fine with it (without running through actual compile) but I suspect this will require contributor agreement and that's what only Vladimir can decide.