grub-devel.gnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH/RFC] cpuid.c: Provide CPU model information
@ 2013-01-30 20:36 David Michael
  2013-04-03  9:19 ` Vladimir 'φ-coder/phcoder' Serbinenko
  0 siblings, 1 reply; 3+ messages in thread
From: David Michael @ 2013-01-30 20:36 UTC (permalink / raw)
  To: grub-devel

Hi,

I have some disks that can be booted both physically and virtually,
and it is helpful to have different options depending on the type of
platform being used.  It's fine having different menu entries to
select at boot time, but I wanted to automate this in the
configuration.

I didn't find a solution through searching, so I modified a module to
supply sufficient information.  The CPU model name was unique enough
for my purpose of detecting whether it is a virtual machine (and I can
imagine additional use cases when GRUB2 is on a portable drive).  The
following patch can be used with configuration like this:

    insmod cpuid
    if [ "$cpuid_model_name" = "QEMU Virtual CPU ...

Would something like this be useful to have upstream?  Alternatively,
if there is a better way to get at various platform identifiers with
config scripts, can someone point me in the right direction?

Thanks.

David

=== modified file 'grub-core/commands/i386/cpuid.c'
--- grub-core/commands/i386/cpuid.c    2012-03-03 12:09:14 +0000
+++ grub-core/commands/i386/cpuid.c    2013-01-30 19:08:48 +0000
@@ -68,6 +68,8 @@
   unsigned int eax, ebx, ecx, edx;
   unsigned int max_level;
   unsigned int ext_level;
+  unsigned int vendor[4] = { 0 };
+  unsigned int model[12];

   /* See if we can use cpuid.  */
   asm volatile ("pushfl; pushfl; popl %0; movl %0,%1; xorl %2,%0;"
@@ -78,7 +80,10 @@
     goto done;

   /* Check the highest input value for eax.  */
-  cpuid (0, eax, ebx, ecx, edx);
+  cpuid (0, eax, vendor[0], vendor[2], vendor[1]);
+  /* Save the CPU vendor ID string. */
+  grub_env_set ("cpuid_vendor_id", (const char *)vendor);
+  grub_env_export ("cpuid_vendor_id");
   /* We only look at the first four characters.  */
   max_level = eax;
   if (max_level == 0)
@@ -91,6 +96,16 @@

   cpuid (0x80000001, eax, ebx, ecx, edx);
   grub_cpuid_has_longmode = !!(edx & bit_LM);
+
+  /* Assemble the CPU model name. */
+  if (ext_level >= 0x80000004)
+    {
+      cpuid(0x80000002, model[0], model[1], model[ 2], model[ 3]);
+      cpuid(0x80000003, model[4], model[5], model[ 6], model[ 7]);
+      cpuid(0x80000004, model[8], model[9], model[10], model[11]);
+      grub_env_set ("cpuid_model_name", (const char *)model);
+      grub_env_export ("cpuid_model_name");
+    }
 done:
 #endif


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH/RFC] cpuid.c: Provide CPU model information
  2013-01-30 20:36 [PATCH/RFC] cpuid.c: Provide CPU model information David Michael
@ 2013-04-03  9:19 ` Vladimir 'φ-coder/phcoder' Serbinenko
  2013-04-15 15:07   ` David Michael
  0 siblings, 1 reply; 3+ messages in thread
From: Vladimir 'φ-coder/phcoder' Serbinenko @ 2013-04-03  9:19 UTC (permalink / raw)
  To: The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 228 bytes --]

On 30.01.2013 21:36, David Michael wrote:

>     insmod cpuid
>     if [ "$cpuid_model_name" = "QEMU Virtual CPU ...

This is not a proper way to detect qemu (see -cpu ?). You should
probably rather parse smbios tables


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 294 bytes --]

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH/RFC] cpuid.c: Provide CPU model information
  2013-04-03  9:19 ` Vladimir 'φ-coder/phcoder' Serbinenko
@ 2013-04-15 15:07   ` David Michael
  0 siblings, 0 replies; 3+ messages in thread
From: David Michael @ 2013-04-15 15:07 UTC (permalink / raw)
  To: The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 472 bytes --]

Hi,

On Wed, Apr 3, 2013 at 5:19 AM, Vladimir 'φ-coder/phcoder' Serbinenko
<phcoder@gmail.com> wrote:
> You should probably rather parse smbios tables

I couldn't find a way to access SMBIOS information with configuration
files, so about a week ago I made a module for this purpose.  I've
been toying with it since, and this is also a good solution for my
original situation.

Attached is a patch, in case it can be of use to anyone else.

Thanks.

David

[-- Attachment #2: 0001-Add-a-module-for-SMBIOS-parsing.patch --]
[-- Type: application/octet-stream, Size: 14539 bytes --]

From: David Michael <fedora.dm0@gmail.com>
Date: Mon, 15 Apr 2013 10:14:02 -0400
Subject: [PATCH] Add a module for SMBIOS parsing

Build a command "smbios" which allows arbitrary selection of numeric
and string fields from SMBIOS table entries.

Human-readable field selection for predefined entries is unsupported
at this point; the abstract field selection allows access to unknown
table entries, such as OEM-specific or custom entries.
---
 grub-core/Makefile.core.def      |   6 +
 grub-core/commands/i386/smbios.c | 362 +++++++++++++++++++++++++++++++++++++++
 po/POTFILES.in                   |   1 +
 3 files changed, 369 insertions(+)
 create mode 100644 grub-core/commands/i386/smbios.c

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 4b4c024..28966bc 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -857,6 +857,12 @@ module = {
 };
 
 module = {
+  name = smbios;
+  x86 = commands/i386/smbios.c;
+  enable = x86;
+};
+
+module = {
   name = sleep;
   common = commands/sleep.c;
 };
diff --git a/grub-core/commands/i386/smbios.c b/grub-core/commands/i386/smbios.c
new file mode 100644
index 0000000..323f499
--- /dev/null
+++ b/grub-core/commands/i386/smbios.c
@@ -0,0 +1,362 @@
+/* Expose SMBIOS data to the console and configuration files */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013  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/acpi.h>
+#include <grub/dl.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+\f
+/* 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;
+  };
+
+static struct grub_smbios_eps *eps = NULL;
+
+\f
+/* 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)
+{
+  grub_uint8_t *ptr;
+
+  for (ptr = 0x000F0000; ptr < 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 ptr;
+      }
+
+  grub_dprintf ("smbios", "Failed to locate entry point structure\n");
+  return NULL;
+}
+
+\f
+/* Reference: DMTF Standard DSP0134 2.7.1 Sections 6.1.2-6.1.3 */
+
+/*
+ * Given a pointer to the first bytes of an entry, print its contents in a
+ * semi-human-readable format.  Return the size of the entry printed.
+ */
+static grub_uint16_t
+grub_smbios_dump_entry (const grub_uint8_t *entry)
+{
+  grub_uint8_t *ptr = entry;
+  grub_uint8_t newstr = 1;
+  grub_uint8_t length;
+
+  /* Write the entry's mandatory four header bytes. */
+  length = ptr[1];
+  grub_printf ("Entry: Type=0x%02X Length=0x%02X Handle=0x%04X\n",
+               ptr[0], length, *(1 + (grub_uint16_t *)ptr));
+
+  /* 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 list. */
+  while (ptr[0] != 0 || ptr[1] != 0)
+    {
+      if (newstr)
+        grub_printf (" String: %s\n", ptr);
+      newstr = *ptr++ == 0;
+    }
+  ptr += 2;
+
+  /* Return the total number of bytes covered. */
+  return ptr - entry;
+}
+
+\f
+/* Reference: DMTF Standard DSP0134 2.7.1 Sections 6.1.2-6.1.3 */
+
+/*
+ * Return or print a matched table entry.  Multiple entries can be matched if
+ * they are being printed.
+ *
+ * This method can handle up to three criteria for selecting an entry:
+ *   - The entry's "type" field             (use outside [0,0xFF] to ignore)
+ *   - The entry's "handle" field           (use outside [0,0xFFFF] to ignore)
+ *   - The entry 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_entry (const struct grub_smbios_eps *eps,
+                         const grub_int16_t type,
+                         const grub_int32_t handle,
+                         const grub_uint16_t match,
+                         const grub_uint8_t print)
+{
+  grub_uint8_t *table_address = eps->intermediate.table_address;
+  grub_uint16_t table_length = eps->intermediate.table_length;
+  grub_uint16_t structures = eps->intermediate.structures;
+  grub_uint8_t *ptr = table_address;
+  grub_uint16_t structure = 0;
+  grub_uint16_t matches = 0;
+
+  while (ptr - table_address < table_length && structure++ < structures)
+
+    /* Test if the current entry matches the given parameters. */
+    if ((handle < 0 || 65535 < handle || handle == *(1 + (grub_uint16_t *)ptr))
+        && (type < 0 || 255 < type || type == ptr[0])
+        && (match == 0 || match == ++matches))
+      {
+        if (print)
+          {
+            ptr += grub_smbios_dump_entry (ptr);
+            if (match > 0)
+              break;
+          }
+        else
+          return ptr;
+      }
+
+    /* If the entry didn't match, skip it (formatted area and string list). */
+    else
+      {
+        ptr += ptr[1];
+        while (*ptr++ != 0 || *ptr++ != 0);
+      }
+
+  return NULL;
+}
+
+\f
+/* Reference: DMTF Standard DSP0134 2.7.1 Section 6.1.3 */
+
+/*
+ * Given a pointer to a string list and a number, iterate over each of the
+ * strings until the desired item is reached.  (The value of 1 indicates the
+ * first string, etc.)
+ */
+static const char *
+grub_smbios_get_string (const grub_uint8_t *strings, const grub_uint8_t number)
+{
+  grub_uint8_t *ptr = strings;
+  grub_uint8_t newstr = 1;
+  grub_uint8_t index = 1;
+
+  /* A string referenced with zero is interpreted as unset. */
+  if (number == 0)
+    return NULL;
+
+  /* Search the string table, incrementing the counter as each null passes. */
+  while (ptr[0] != 0 || ptr[1] != 0)
+    {
+      if (newstr && number == index++)
+        return ptr;
+      newstr = *ptr++ == 0;
+    }
+
+  /* The requested string index is greater than the number of table entries. */
+  return NULL;
+}
+
+\f
+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;
+
+  grub_uint8_t *entry;
+  grub_uint8_t accessors;
+  grub_uint8_t i;
+  char buffer[24]; /* 64-bit number -> maximum 20 decimal digits */
+  char *value = buffer;
+
+  if (eps == NULL)
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("missing entry point"));
+
+  /* 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_("too many -b|-w|-d|-q|-s"));
+
+  /* Reject the environment variable if no value was selected. */
+  if (accessors == 0 && state[8].set)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("-v without -b|-w|-d|-q|-s"));
+
+  /* Read the given matching parameters. */
+  if (state[0].set)
+    type = grub_strtol (state[0].arg, NULL, 0);
+  if (state[1].set)
+    handle = grub_strtol (state[1].arg, NULL, 0);
+  if (state[2].set)
+    match = grub_strtoul (state[2].arg, NULL, 0);
+
+  /* When not selecting a value, print all matching entries and quit. */
+  if (accessors == 0)
+    {
+      grub_smbios_match_entry (eps, type, handle, match, 1);
+      return GRUB_ERR_NONE;
+    }
+
+  /* Select a single entry from the matching parameters. */
+  entry = grub_smbios_match_entry (eps, type, handle, match, 0);
+  if (entry == NULL)
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("no such entry"));
+
+  /* Ensure the specified offset+length is within the matched entry. */
+  for (i = 3; i <= 7; i++)
+    if (state[i].set)
+      {
+        offset = grub_strtoul (state[i].arg, NULL, 0);
+        if (offset + (1 << (i == 7 ? 0 : i - 3)) > entry[1])
+          return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("value outside entry"));
+        break;
+      }
+
+  /* If a string was requested, try to find its pointer. */
+  if (state[7].set)
+    {
+      value = grub_smbios_get_string (entry + entry[1], entry[offset]);
+      if (value == NULL)
+        return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("string not defined"));
+    }
+
+  /* Create a string from a numeric value suitable for printing. */
+  else if (state[3].set)
+    grub_snprintf (buffer, sizeof (buffer), "%u", entry[offset]);
+  else if (state[4].set)
+    grub_snprintf (buffer, sizeof (buffer), "%u",
+                   *(grub_uint16_t *)(entry + offset));
+  else if (state[5].set)
+    grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT32_T,
+                   *(grub_uint32_t *)(entry + offset));
+  else if (state[6].set)
+    grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT64_T,
+                   *(grub_uint64_t *)(entry + 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;
+}
+
+\f
+static grub_extcmd_t cmd;
+
+static const struct grub_arg_option options[] =
+  {
+    {"type",   't', 0, N_("Match entries with the given type\n\t\t\t"
+                          "\"byte\" is an integer in the range 0-255\n\t\t\t"
+                          "[default: -1 (ignore types while matching)]"),
+                       N_("byte"), ARG_TYPE_INT},
+    {"handle", 'h', 0, N_("Match entries with the given handle\n\t\t\t"
+                          "\"word\" is an integer in the range 0-65535\n\t\t\t"
+                          "[default: -1 (ignore handles while matching)]"),
+                       N_("word"), ARG_TYPE_INT},
+    {"match",  'm', 0, N_("Select the entry to use when several match\n\t\t\t"
+                          "\"number\" is a positive integer\n\t\t\t"
+                          "[default: 0 (all matches) for entry dumps\n\t\t\t"
+                          "          1 (first match) for value retrieval]"),
+                       N_("number"), 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 referenced at the given offset"),
+                           N_("offset"), ARG_TYPE_INT},
+    {"variable", 'v', 0, N_("Store the value in the given variable name"),
+                         N_("name"), 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 byte] [-h word] [-m number] "
+                                 "[{-b|-w|-d|-q|-s} offset [-v name]]"),
+                              N_("Expose SMBIOS data.\n\n"
+"The options -t, -h, -m are used to match specific entries.  Their defaults "
+"match all entries.  Without any additional options, matched entries are "
+"printed to the console.\n\n"
+"Only one of -b, -w, -d, -q, -s can be given to return a specific value from "
+"a single matched entry.  The value is read at a byte offset, and the option "
+"determines the data type.  (See the SMBIOS reference specification for "
+"standard values, or review you your OEM's documentation for others.)  The "
+"selected value is printed to the console, or if -v is given, the value is "
+"stored in the named environment variable.\n\n"
+"Example to save the name of the manufacturer of the system's second CPU:"
+"\n\tsmbios -t 4 -m 2 -s 7 -v cpu2manu\n\techo $cpu2manu\n"
+"It says to match type 4 entries (Processor Information) and use the second "
+"match, then find the string referenced by its seventh byte."), options);
+}
+
+GRUB_MOD_FINI(smbios)
+{
+  grub_unregister_extcmd (cmd);
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ec79914..bf02f28 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -57,6 +57,7 @@
 ./grub-core/commands/i386/pc/lsapm.c
 ./grub-core/commands/i386/pc/play.c
 ./grub-core/commands/i386/pc/sendkey.c
+./grub-core/commands/i386/smbios.c
 ./grub-core/commands/ieee1275/suspend.c
 ./grub-core/commands/iorw.c
 ./grub-core/commands/keylayouts.c
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2013-04-15 15:07 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-01-30 20:36 [PATCH/RFC] cpuid.c: Provide CPU model information David Michael
2013-04-03  9:19 ` Vladimir 'φ-coder/phcoder' Serbinenko
2013-04-15 15:07   ` David Michael

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).