From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.71) id 1YLVYb-0007Hj-O1 for mharc-grub-devel@gnu.org; Wed, 11 Feb 2015 06:34:13 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:48666) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YLVYW-0007FV-9y for grub-devel@gnu.org; Wed, 11 Feb 2015 06:34:11 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YLVYS-00071g-PQ for grub-devel@gnu.org; Wed, 11 Feb 2015 06:34:08 -0500 Received: from mail-la0-f41.google.com ([209.85.215.41]:41116) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YLVYS-00071U-AD for grub-devel@gnu.org; Wed, 11 Feb 2015 06:34:04 -0500 Received: by labpv20 with SMTP id pv20so2662228lab.8 for ; Wed, 11 Feb 2015 03:34:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=date:from:to:cc:subject:message-id:in-reply-to:references :mime-version:content-type:content-transfer-encoding; bh=kZgqbi/r0NrWzSohBBOkeDIdHIya6oJA671d6KCFmBU=; b=xS1EkuzWUg5C8jXgIzZP/JDuSbDsixX7Hy1tYDRgenzn1PMGzIex3tPAJ5lDT3tqPq MpG19u1PenBTjtMH0pO5BiRIV/EzVBeQNN5x7tKn4YCgxMaBis2bO65HGQ4pj9SHQXYM 7STEGMGOBWQf4HAnLo6Qjt1DBiJM4RTPzuD0lcz86BU7OobpJEnTM8//MtVay6C8e6Vb JnI5Tzj1uzKPTo7O2Lr4/U7sfsefxipM1bln5QrDD6pEBZ1LGv2Q0bU4NTIzLlGExLYg fKfUl4uIv9Y70SI64EzbFl8V9Gg1m8ZEedxxj4u0/oMV5rOaY2jJheQtcT5PtgeZSvVp VRoQ== X-Received: by 10.152.8.104 with SMTP id q8mr28071956laa.56.1423654443571; Wed, 11 Feb 2015 03:34:03 -0800 (PST) Received: from opensuse.site (ppp91-76-14-38.pppoe.mtu-net.ru. [91.76.14.38]) by mx.google.com with ESMTPSA id yc3sm122909lbb.4.2015.02.11.03.34.01 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 11 Feb 2015 03:34:02 -0800 (PST) Date: Wed, 11 Feb 2015 14:34:00 +0300 From: Andrei Borzenkov To: David Michael Subject: Re: [PATCH v2] Add a module for retrieving SMBIOS information Message-ID: <20150211143400.7e52be2a@opensuse.site> In-Reply-To: <87wq3s5j49.fsf@gmail.com> References: <87wq3s5j49.fsf@gmail.com> X-Mailer: Claws Mail 3.11.0 (GTK+ 2.24.25; x86_64-suse-linux-gnu) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.85.215.41 Cc: Prarit Bhargava , grub-devel@gnu.org, Raghuraman Thirumalairajan , Rajat Jain , Leif Lindholm , Sanjay Jain , Stu Grossman X-BeenThere: grub-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list Reply-To: The development of GNU GRUB List-Id: The development of GNU GRUB List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 11 Feb 2015 11:34:11 -0000 =D0=92 Sun, 08 Feb 2015 13:54:46 -0500 David Michael =D0=BF=D0=B8=D1=88=D0=B5=D1=82: > The following are two use cases from Rajat Jain : >=20 > 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 t= o the kernel based on the "CHASSIS_TYPE" information that is passed by the = bios in the DMI / SMBIOS tables. >=20 > 2) We may have a USB stick that can go into multiple boards, and the exac= t kernel to be loaded depends on the machine information (PRODUCT_NAME etc)= passed via the DMI. >=20 > * grub-core/commands/smbios.c: New file. > * grub-core/Makefile.core.def (smbios): New module. > * docs/grub.texi (smbios): New node. > (Command-line and menu entry commands): Add a menu entry for smbios. That's not needed. ChangeLog is now generated from GIT commits. > --- >=20 > Changes since v1: >=20 > * Removed formfeeds and quoted use cases in the commit messages as > suggested by Prarit Bhargava. >=20 > * Enabled building the module on all EFI platforms as suggested by Leif > Lindholm. >=20 > docs/grub.texi | 67 ++++++++ > grub-core/Makefile.core.def | 7 + > grub-core/commands/smbios.c | 361 ++++++++++++++++++++++++++++++++++++++= ++++++ > 3 files changed, 435 insertions(+) > create mode 100644 grub-core/commands/smbios.c >=20 > diff --git a/docs/grub.texi b/docs/grub.texi > index 46b9e7f..f4e187f 100644 > --- a/docs/grub.texi > +++ b/docs/grub.texi > @@ -3830,6 +3830,7 @@ you forget a command, you can run the command @comm= and{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,72 @@ if timeout was interrupted by @key{ESC}. > @end deffn > =20 > =20 > +@node smbios > +@subsection smbios > + > +@deffn Command smbios @ > + [@option{-t} @var{type}] @ > + [@option{-h} @var{handle}] @ > + [@option{-m} @var{match}] @ > + [ (@option{-b} | @option{-w} | @option{-d} | @option{-q} | @option{-s})= @ > + @var{offset} [@option{-v} @var{variable}] ] I think that in general we should endorse and advertise long options - they make scripts much more readable - and mention single letter options in description (if at all) or help output. Look e.g. at search command description. In other places we use "--set var" to set variable. Let's remain consistent with this usage. If you want to spare some typing, make it optional, like smbios offset [[--set] var] > +Retrieve SMBIOS information. This command is only available on x86 and = EFI > +systems. > + > +When run with no options, @command{smbios} will print the SMBIOS table e= ntries > +to the console as the header values, a hex dump, and a string list. The > +following options can filter which table entries are printed. > + > +@itemize @bullet > +@item > +Specifying @option{-t} will only print entries with a matching @var{type= }. The > +type can be any value from 0 to 255. Specify a value outside this range= to > +match entries with any type. The default is -1. I think out of range value should result in error.=20 > +@item > +Specifying @option{-h} will only print entries with a matching @var{hand= le}. > +The handle can be any value from 0 to 65535. Specify a value outside th= is > +range to match entries with any handle. The default is -1. ditto. > +@item > +Specifying @option{-m} will only print number @var{match} in the list of= all > +filtered entries; e.g. @code{smbios -m 3} will print the third entry. T= he list > +is always ordered the same as the hardware's SMBIOS table. The match nu= mber > +can be any positive value. Specify 0 to match all entries. The default= is 0. > +@end itemize > + > +The remaining options print the value of a field in the matched SMBIOS t= able > +entry. Only one of these options may be specified. When they are used,= the > +option @code{-m 0} is synonymous with @code{-m 1} to match the first ent= ry. If user provides conflicting options this should really be an error. > + > +@itemize @bullet > +@item > +When given @option{-b}, print the value of the byte > +at @var{offset} bytes into the matched SMBIOS table entry. > +@item > +When given @option{-w}, print the value of the word (two bytes) > +at @var{offset} bytes into the matched SMBIOS table entry. > +@item > +When given @option{-d}, print the value of the dword (four bytes) > +at @var{offset} bytes into the matched SMBIOS table entry. > +@item > +When given @option{-q}, print the value of the qword (eight bytes) > +at @var{offset} bytes into the matched SMBIOS table entry. > +@item > +When given @option{-s}, print the string table entry with its index found > +at @var{offset} bytes into the matched SMBIOS table entry. > +@end itemize > + > +If any of the options in the above list were given, a variable name can = be > +specified with @option{-v} to store the value instead of printing it. > + > +For example, this will store and display the system manufacturer string. > + > +@example > +smbios -t 1 -s 4 -v system_manufacturer > +echo $system_manufacturer > +@end example > +@end deffn > + > + > @node source > @subsection source > =20 > diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def > index 42443bc..f87de70 100644 > --- a/grub-core/Makefile.core.def > +++ b/grub-core/Makefile.core.def > @@ -1023,6 +1023,13 @@ module =3D { > }; > =20 > module =3D { > + name =3D smbios; > + common =3D commands/smbios.c; > + enable =3D efi; > + enable =3D x86; > +}; > + > +module =3D { > name =3D suspend; > ieee1275 =3D commands/ieee1275/suspend.c; > enable =3D i386_ieee1275; > diff --git a/grub-core/commands/smbios.c b/grub-core/commands/smbios.c > new file mode 100644 > index 0000000..d8edeea > --- /dev/null > +++ b/grub-core/commands/smbios.c > @@ -0,0 +1,361 @@ > +/* 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; > + }; > + > +static const struct grub_smbios_eps *eps =3D 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 entr= y point > + * structure. This returns a pointer to a struct that identifies the fi= elds 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 =3D GRUB_EFI_SMBIOS_TABLE_GUI= D; > + const grub_efi_system_table_t *st =3D grub_efi_system_table; > + const grub_efi_configuration_table_t *t =3D st->configuration_table; > + unsigned int i; > + > + for (i =3D 0; i < st->num_table_entries; i++) > + if (grub_memcmp (&smbios_guid, &t->vendor_guid, sizeof (smbios_guid)= ) =3D=3D 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 =3D (grub_uint8_t *)0x000F0000; > + ptr < (grub_uint8_t *)0x00100000; > + ptr +=3D 16) > + if (grub_memcmp (ptr, "_SM_", 4) =3D=3D 0 > + && grub_byte_checksum (ptr, ptr[5]) =3D=3D 0) > + { > + grub_dprintf ("smbios", "Found entry point structure at %p\n", p= tr); > + return (const struct grub_smbios_eps *)ptr; > + } > +#endif > + > + grub_dprintf ("smbios", "Failed to locate entry point structure\n"); > + return NULL; > +} > + > +/* 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) > +{ > + const grub_uint8_t *ptr =3D entry; > + grub_uint8_t newstr =3D 1; > + grub_uint8_t length; > + > + /* Write the entry's mandatory four header bytes. */ > + length =3D ptr[1]; > + grub_printf ("Entry: Type=3D0x%02x Length=3D0x%02x Handle=3D0x%04x\n", > + ptr[0], length, *(1 + (grub_uint16_t *)ptr)); Could you reverse the order in 1 + (grub_uint16_t *)ptr and in other places too? > + > + /* 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] !=3D 0 || ptr[1] !=3D 0) I wonder do we have any ad hoc way to limit it? It looks like Linux kernel does more sanity checks and does not blindly trust data. > + { > + if (newstr) > + grub_printf (" String: %s\n", ptr); > + newstr =3D *ptr++ =3D=3D 0; > + } > + ptr +=3D 2; > + > + /* Return the total number of bytes covered. */ > + return ptr - entry; > +} > + > +/* 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 match= ed 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 ign= ore) > + * - The entry's "handle" field (use outside [0,0xFFFF] to i= gnore) > + * - 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 *ep, > + const grub_int16_t type, > + const grub_int32_t handle, > + const grub_uint16_t match, > + const grub_uint8_t print) > +{ > + grub_addr_t table_address =3D (grub_addr_t)ep->intermediate.table_addr= ess; > + grub_uint16_t table_length =3D ep->intermediate.table_length; > + grub_uint16_t structures =3D ep->intermediate.structures; > + grub_uint16_t structure =3D 0; > + grub_uint16_t matches =3D 0; > + const grub_uint8_t *table =3D (const grub_uint8_t *)table_address; > + const grub_uint8_t *ptr =3D table; > + > + while (ptr - table < table_length && structure++ < structures) > + > + /* Test if the current entry matches the given parameters. */ > + if ((handle < 0 || 65535 < handle || handle =3D=3D *(1 + (grub_uint1= 6_t *)ptr)) > + && (type < 0 || 255 < type || type =3D=3D ptr[0]) > + && (match =3D=3D 0 || match =3D=3D ++matches)) As said it is better to validate on input, so handle > 65535 etc can be omitted. > + { > + if (print) > + { > + ptr +=3D grub_smbios_dump_entry (ptr); > + if (match > 0) > + break; > + } > + else > + return ptr; > + } > + > + /* If the entry didn't match, skip it (formatted area and string lis= t). */ > + else > + { > + ptr +=3D ptr[1]; > + while (*ptr++ !=3D 0 || *ptr++ !=3D 0); Same here about more checks for table end. > + } > + > + return NULL; > +} > + > +/* 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 t= he > + * 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) > +{ > + const char *ptr =3D (const char *)strings; > + grub_uint8_t newstr =3D 1; > + grub_uint8_t index =3D 1; > + > + /* A string referenced with zero is interpreted as unset. */ > + if (number =3D=3D 0) > + return NULL; > + > + /* Search the string table, incrementing the counter as each null pass= es. */ > + while (ptr[0] !=3D 0 || ptr[1] !=3D 0) And here. > + { > + if (newstr && number =3D=3D index++) > + return ptr; > + newstr =3D *ptr++ =3D=3D 0; > + } > + > + /* The requested string index is greater than the number of table entr= ies. */ > + 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 =3D ctxt->state; > + > + grub_int16_t type =3D -1; > + grub_int32_t handle =3D -1; > + grub_uint16_t match =3D 0; > + grub_uint8_t offset =3D 0; > + > + const grub_uint8_t *entry; > + grub_uint8_t accessors; > + grub_uint8_t i; > + char buffer[24]; /* 64-bit number -> maximum 20 decimal digits */ > + const char *value =3D buffer; > + > + if (eps =3D=3D NULL) > + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("missing entry point"= )); "SMBIOS table not found" or something like this, so it is more understandable. > + > + /* Only one value can be returned at a time; reject multiple selection= s. */ > + accessors =3D !!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")); Only one of ... can be specified > + > + /* Reject the environment variable if no value was selected. */ > + if (accessors =3D=3D 0 && state[8].set) > + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("-v without -b|-w|-d|-q= |-s")); --set requires explicit value type. > + > + /* Read the given matching parameters. */ > + if (state[0].set) > + type =3D grub_strtol (state[0].arg, NULL, 0); > + if (state[1].set) > + handle =3D grub_strtol (state[1].arg, NULL, 0); > + if (state[2].set) > + match =3D grub_strtoul (state[2].arg, NULL, 0); > + > + /* When not selecting a value, print all matching entries and quit. */ > + if (accessors =3D=3D 0) > + { > + grub_smbios_match_entry (eps, type, handle, match, 1); > + return GRUB_ERR_NONE; > + } > + > + /* Select a single entry from the matching parameters. */ > + entry =3D grub_smbios_match_entry (eps, type, handle, match, 0); > + if (entry =3D=3D 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 =3D 3; i <=3D 7; i++) > + if (state[i].set) > + { > + offset =3D grub_strtoul (state[i].arg, NULL, 0); > + if (offset + (1 << (i =3D=3D 7 ? 0 : i - 3)) > entry[1]) Such optimizations will really strike back in future, as soon as options are changed and make code confusing. Just explicitly set entry size when parsing arguments. > + return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("value outside en= try")); > + break; > + } > + > + /* If a string was requested, try to find its pointer. */ > + if (state[7].set) > + { > + value =3D grub_smbios_get_string (entry + entry[1], entry[offset]); > + if (value =3D=3D 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)); Will it also work on other platforms (alignment)? > + else if (state[5].set) > + grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT32_T, > + *(grub_uint32_t *)(entry + offset)); Ditto. > + else if (state[6].set) > + grub_snprintf (buffer, sizeof (buffer), "%" PRIuGRUB_UINT64_T, > + *(grub_uint64_t *)(entry + offset)); > + Ditto. > + /* 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[] =3D > + { > + {"type", 't', 0, N_("Match entries with the given type."), > + N_("byte"), ARG_TYPE_INT}, > + {"handle", 'h', 0, N_("Match entries with the given handle."), > + N_("word"), ARG_TYPE_INT}, > + {"match", 'm', 0, N_("Select the entry to use when several matc= h."), > + 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 offse= t."), > + N_("offset"), ARG_TYPE_INT}, > + {"get-string", 's', 0, N_("Get the string specified at the given off= set."), > + N_("offset"), ARG_TYPE_INT}, > + {"variable", 'v', 0, N_("Store the value in the given variable nam= e."), > + 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 i= nit. */ > + eps =3D grub_smbios_locate_eps (); > + > + cmd =3D grub_register_extcmd ("smbios", grub_cmd_smbios, 0, > + N_("[-t byte] [-h word] [-m number] " > + "[(-b|-w|-d|-q|-s) offset [-v name]]"), > + N_("Retrieve SMBIOS information."), option= s); > +} > + > +GRUB_MOD_FINI(smbios) > +{ > + grub_unregister_extcmd (cmd); > +}