Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables), and an entry point structure (etc/smbios/smbios-anchor). If found, no longer build smbios tables locally; instead, replace only the type 0 table with a default of our own, and (re)calculate only the minimum set of necessary fields from the provided entry point.
Signed-off-by: Gabriel L. Somlo somlo@cmu.edu ---
This patch leaves the behavior of SeaBIOS completely unchanged, unless full tables are provided via fw_cfg by QEMU (e.g. by my QEMU SMBIOS patch set v5, which I'll email out shortly).
Thanks, Gabriel
src/fw/smbios.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+)
diff --git a/src/fw/smbios.c b/src/fw/smbios.c index 0c6a5b2..00faef0 100644 --- a/src/fw/smbios.c +++ b/src/fw/smbios.c @@ -166,6 +166,60 @@ get_external(int type, char **p, unsigned *nr_structs, } \ } while (0)
+#define set_str_field_or_skip(type, field, value) \ + do { \ + int size = (value != NULL) ? strlen(value) + 1 : 0; \ + if (size > 1) { \ + memcpy(end, value, size); \ + end += size; \ + p->field = ++str_index; \ + } else { \ + p->field = 0; \ + } \ + } while (0) + +static void * +smbios_new_type_0(void *start, + const char *vendor, const char *version, const char *rel_date) +{ + struct smbios_type_0 *p = (struct smbios_type_0 *)start; + char *end = (char *)start + sizeof(struct smbios_type_0); + int str_index = 0; + + p->header.type = 0; + p->header.length = sizeof(struct smbios_type_0); + p->header.handle = 0; + + set_str_field_or_skip(0, vendor_str, vendor); + set_str_field_or_skip(0, bios_version_str, version); + p->bios_starting_address_segment = 0xe800; + set_str_field_or_skip(0, bios_release_date_str, rel_date); + + p->bios_rom_size = 0; /* FIXME */ + + /* BIOS characteristics not supported */ + memset(p->bios_characteristics, 0, 8); + p->bios_characteristics[0] = 0x08; + + /* Enable targeted content distribution (needed for SVVP, per SeaBIOS) */ + p->bios_characteristics_extension_bytes[0] = 0; + p->bios_characteristics_extension_bytes[1] = 4; + + p->system_bios_major_release = 0; + p->system_bios_minor_release = 0; + p->embedded_controller_major_release = 0xFF; + p->embedded_controller_minor_release = 0xFF; + + *end = 0; + end++; + if (!str_index) { + *end = 0; + end++; + } + + return end; +} + /* Type 0 -- BIOS Information */ #define RELEASE_DATE_STR "01/01/2011" static void * @@ -507,6 +561,91 @@ smbios_init_type_127(void *start) return start + 2; }
+static int +smbios_try_fw_cfg_setup(void) +{ + struct romfile_s *f_anchor = romfile_find("etc/smbios/smbios-anchor"); + struct romfile_s *f_tables = romfile_find("etc/smbios/smbios-tables"); + struct smbios_entry_point *ep; + u8 *tables, *t; + u16 t_len; + + if (!f_anchor || !f_tables || f_anchor->size != sizeof(*ep)) + return 0; + + ep = malloc_fseg(sizeof(*ep)); + if (!ep) { + warn_noalloc(); + return 0; + } + f_anchor->copy(f_anchor, ep, f_anchor->size); + + if (f_tables->size != ep->structure_table_length) { + free(ep); + return 0; + } + + tables = malloc_tmphigh(f_tables->size); + if (!tables) { + warn_noalloc(); + free(ep); + return 0; + } + f_tables->copy(f_tables, tables, f_tables->size); + + /* rip out any type 0 tables we may have received */ + t = tables; + t_len = ep->structure_table_length; + while (t < tables + t_len) { + struct smbios_structure_header *h = (struct smbios_structure_header *)t; + u8 *next; + + /* find start of next structure (past the double-'\0' terminator) */ + for (next = t + h->length; *next || *(next+1); next++); + next += 2; + + if (h->type == 0) { + /* move remaining tables down over t */ + memmove(t, next, t_len - (next - tables)); + /* shorten total blob length */ + t_len -= (next - t); + } else { + t = next; + } + } + + /* final blob length adds our own type 0 with 3 strings and 4 '\0's */ + ep->structure_table_length = t_len + sizeof(struct smbios_type_0) + + strlen("SeaBIOS") + strlen(VERSION) + + strlen(RELEASE_DATE_STR) + 4; + + /* allocate final blob */ + if (ep->structure_table_length > BUILD_MAX_SMBIOS_FSEG) + t = malloc_high(ep->structure_table_length); + else + t = malloc_fseg(ep->structure_table_length); + if (!t) { + warn_noalloc(); + free(ep); + free(tables); + return 0; + } + + /* finalize entry point */ + ep->structure_table_address = (u32)t; + ep->checksum = -checksum(ep, 0x10); + ep->intermediate_checksum = -checksum((void *)ep + 0x10, ep->length - 0x10); + + /* populate final blob */ + t = smbios_new_type_0(t, "SeaBIOS", VERSION, RELEASE_DATE_STR); + memcpy(t, tables, t_len); + free(tables); + + /* success */ + SMBiosAddr = ep; + return 1; +} + #define TEMPSMBIOSSIZE (32 * 1024)
void @@ -517,6 +656,9 @@ smbios_setup(void)
dprintf(3, "init SMBIOS tables\n");
+ if (smbios_try_fw_cfg_setup()) + return; /* got full tables via fw_cfg, we're done */ + char *start = malloc_tmphigh(TEMPSMBIOSSIZE); if (! start) { warn_noalloc();
Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables), and an entry point structure (etc/smbios/smbios-anchor). If found, we no longer build smbios tables locally; instead, we replace only the type 0 table with a default of our own, and (re)calculate only the minimum set of necessary fields from the provided entry point.
Signed-off-by: Gabriel L. Somlo somlo@cmu.edu ---
Reworked to fit on top of 028f3487cfaa136815dd8d1896310c763402e969. Thanks, Gabriel
src/fw/smbios.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+)
diff --git a/src/fw/smbios.c b/src/fw/smbios.c index 0ac9ff5..b38d199 100644 --- a/src/fw/smbios.c +++ b/src/fw/smbios.c @@ -160,6 +160,60 @@ get_external(int type, char **p, unsigned *nr_structs, } \ } while (0)
+#define set_str_field_or_skip(type, field, value) \ + do { \ + int size = (value != NULL) ? strlen(value) + 1 : 0; \ + if (size > 1) { \ + memcpy(end, value, size); \ + end += size; \ + p->field = ++str_index; \ + } else { \ + p->field = 0; \ + } \ + } while (0) + +static void * +smbios_new_type_0(void *start, + const char *vendor, const char *version, const char *rel_date) +{ + struct smbios_type_0 *p = (struct smbios_type_0 *)start; + char *end = (char *)start + sizeof(struct smbios_type_0); + int str_index = 0; + + p->header.type = 0; + p->header.length = sizeof(struct smbios_type_0); + p->header.handle = 0; + + set_str_field_or_skip(0, vendor_str, vendor); + set_str_field_or_skip(0, bios_version_str, version); + p->bios_starting_address_segment = 0xe800; + set_str_field_or_skip(0, bios_release_date_str, rel_date); + + p->bios_rom_size = 0; /* FIXME */ + + /* BIOS characteristics not supported */ + memset(p->bios_characteristics, 0, 8); + p->bios_characteristics[0] = 0x08; + + /* Enable targeted content distribution (needed for SVVP, per SeaBIOS) */ + p->bios_characteristics_extension_bytes[0] = 0; + p->bios_characteristics_extension_bytes[1] = 4; + + p->system_bios_major_release = 0; + p->system_bios_minor_release = 0; + p->embedded_controller_major_release = 0xFF; + p->embedded_controller_minor_release = 0xFF; + + *end = 0; + end++; + if (!str_index) { + *end = 0; + end++; + } + + return end; +} + /* Type 0 -- BIOS Information */ #define RELEASE_DATE_STR "01/01/2011" static void * @@ -501,6 +555,82 @@ smbios_init_type_127(void *start) return start + 2; }
+static int +smbios_try_fw_cfg_setup(void) +{ + struct romfile_s *f_anchor = romfile_find("etc/smbios/smbios-anchor"); + struct romfile_s *f_tables = romfile_find("etc/smbios/smbios-tables"); + struct smbios_entry_point ep; + u8 *tables, *t; + u16 t_len; + + if (!f_anchor || !f_tables || f_anchor->size != sizeof(ep)) + return 0; + + f_anchor->copy(f_anchor, &ep, f_anchor->size); + + if (f_tables->size != ep.structure_table_length) + return 0; + + tables = malloc_tmphigh(f_tables->size); + if (!tables) { + warn_noalloc(); + return 0; + } + f_tables->copy(f_tables, tables, f_tables->size); + + /* rip out any type 0 tables we may have received */ + t = tables; + t_len = ep.structure_table_length; + while (t < tables + t_len) { + struct smbios_structure_header *h = (struct smbios_structure_header *)t; + u8 *next; + + /* find start of next structure (past the double-'\0' terminator) */ + for (next = t + h->length; *next || *(next+1); next++); + next += 2; + + if (h->type == 0) { + /* move remaining tables down over t */ + memmove(t, next, t_len - (next - tables)); + /* shorten total blob length */ + t_len -= (next - t); + } else { + t = next; + } + } + + /* final blob length adds our own type 0 with 3 strings and 4 '\0's */ + ep.structure_table_length = t_len + sizeof(struct smbios_type_0) + + strlen("SeaBIOS") + strlen(VERSION) + + strlen(RELEASE_DATE_STR) + 4; + + /* allocate final blob */ + if (ep.structure_table_length > BUILD_MAX_SMBIOS_FSEG) + t = malloc_high(ep.structure_table_length); + else + t = malloc_fseg(ep.structure_table_length); + if (!t) { + warn_noalloc(); + free(tables); + return 0; + } + + /* finalize entry point */ + ep.structure_table_address = (u32)t; + ep.checksum = -checksum(&ep, 0x10); + ep.intermediate_checksum = -checksum((void *)&ep + 0x10, ep.length - 0x10); + + /* populate final blob */ + t = smbios_new_type_0(t, "SeaBIOS", VERSION, RELEASE_DATE_STR); + memcpy(t, tables, t_len); + free(tables); + + /* success */ + copy_smbios(&ep); + return 1; +} + #define TEMPSMBIOSSIZE (32 * 1024)
void @@ -511,6 +641,9 @@ smbios_setup(void)
dprintf(3, "init SMBIOS tables\n");
+ if (smbios_try_fw_cfg_setup()) + return; /* got full tables via fw_cfg, we're done */ + char *start = malloc_tmphigh(TEMPSMBIOSSIZE); if (! start) { warn_noalloc();
On Fri, Apr 11, 2014 at 12:42:52PM -0400, Gabriel L. Somlo wrote:
Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables), and an entry point structure (etc/smbios/smbios-anchor). If found, we no longer build smbios tables locally; instead, we replace only the type 0 table with a default of our own, and (re)calculate only the minimum set of necessary fields from the provided entry point.
Thanks for looking at this.
[...]
diff --git a/src/fw/smbios.c b/src/fw/smbios.c index 0ac9ff5..b38d199 100644 --- a/src/fw/smbios.c +++ b/src/fw/smbios.c
I'd prefer to add this code to src/fw/paravirt.c and src/fw/biostables.c instead of src/fw/smbios.c. That way, the smbios.c file is limited to the legacy smbios generation and we can more clearly document that the whole file is deprecated.
[...]
- /* rip out any type 0 tables we may have received */
I think we should make the type0 table replacement optional. Something like:
void *anchor, *tables = ... from qemu ...; ... if (romfile_loadint("etc/update-smbios-type0", 0)) { ... do update to tables ... } ... copy_smbios(anchor, tables);
QEMU currently has command-line options that can modify the fields of the type0 tables (-smbios type=0,vendor='foo'). To continue to support that, I think QEMU should be able to build the type0 table as it feels fit to, and SeaBIOS should be able to pass it through. Of course, if there's no specific request from the end user, then I think QEMU can tell SeaBIOS that it may replace the type0 content with its own data (eg, via "etc/update-smbios-type0").
[...]
- while (t < tables + t_len) {
struct smbios_structure_header *h = (struct smbios_structure_header *)t;
u8 *next;
/* find start of next structure (past the double-'\0' terminator) */
for (next = t + h->length; *next || *(next+1); next++);
next += 2;
The display_uuid() code also has sub-table iteration code. It would be nice to factor that out and use it in both places. I'll send a patch separately with a refactor of display_uuid.
-Kevin
Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables) and an entry point structure (etc/smbios/smbios-anchor), and, if found, use them instead of generating our own copies locally.
We ensure the presence of a type 0 (bios information) structure by generating one locally if necessary, which is expected to be the common case.
Signed-off-by: Gabriel L. Somlo somlo@cmu.edu ---
This now goes on top of Kevin's patch factoring out smbios table walking into smbios_next().
On Sat, Apr 12, 2014 at 11:56:08AM -0400, Kevin O'Connor wrote:
I'd prefer to add this code to src/fw/paravirt.c and src/fw/biostables.c instead of src/fw/smbios.c. That way, the smbios.c file is limited to the legacy smbios generation and we can more clearly document that the whole file is deprecated.
OK, so most of the new code is now in biostables.c, hope that's OK.
QEMU currently has command-line options that can modify the fields of the type0 tables (-smbios type=0,vendor='foo'). To continue to support that, I think QEMU should be able to build the type0 table as it feels fit to, and SeaBIOS should be able to pass it through. Of course, if there's no specific request from the end user, then I think QEMU can tell SeaBIOS that it may replace the type0 content with its own data (eg, via "etc/update-smbios-type0").
Something like that, except (hopefully) even simpler :)
If QEMU adds its (still optional) type 0 structure, SeaBIOS just uses everything as-is. If no type 0 structure is found, we generate and prepend our own, and update the entry point fields accordingly.
I expect the latter to be the common case (having QEMU add its own type 0 sub-table is rare, and discouraged in practice, according to the QEMU guys).
Finally, it feels like BIOS_NAME and BIOS_DATE should maybe come from some other, more central place in the SeaBIOS tree, but didn't find anything applicable...
Thanks, Gabriel
src/fw/biostables.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/fw/smbios.c | 2 +- src/util.h | 3 +- 3 files changed, 138 insertions(+), 2 deletions(-)
diff --git a/src/fw/biostables.c b/src/fw/biostables.c index 818a8fe..838168b 100644 --- a/src/fw/biostables.c +++ b/src/fw/biostables.c @@ -13,6 +13,7 @@ #include "std/mptable.h" // MPTABLE_SIGNATURE #include "std/pirtable.h" // struct pir_header #include "std/smbios.h" // struct smbios_entry_point +#include "romfile.h" #include "string.h" // memcpy #include "util.h" // copy_table #include "x86.h" // outb @@ -314,6 +315,140 @@ display_uuid(void) } }
+#define set_str_field_or_skip(type, field, value) \ + do { \ + int size = (value != NULL) ? strlen(value) + 1 : 0; \ + if (size > 1) { \ + memcpy(end, value, size); \ + end += size; \ + p->field = ++str_index; \ + } else { \ + p->field = 0; \ + } \ + } while (0) + +static void * +smbios_new_type_0(void *start, + const char *vendor, const char *version, const char *date) +{ + struct smbios_type_0 *p = (struct smbios_type_0 *)start; + char *end = (char *)start + sizeof(struct smbios_type_0); + int str_index = 0; + + p->header.type = 0; + p->header.length = sizeof(struct smbios_type_0); + p->header.handle = 0; + + set_str_field_or_skip(0, vendor_str, vendor); + set_str_field_or_skip(0, bios_version_str, version); + p->bios_starting_address_segment = 0xe800; + set_str_field_or_skip(0, bios_release_date_str, date); + + p->bios_rom_size = 0; /* FIXME */ + + /* BIOS characteristics not supported */ + memset(p->bios_characteristics, 0, 8); + p->bios_characteristics[0] = 0x08; + + /* Enable targeted content distribution (needed for SVVP, per SeaBIOS) */ + p->bios_characteristics_extension_bytes[0] = 0; + p->bios_characteristics_extension_bytes[1] = 4; + + p->system_bios_major_release = 0; + p->system_bios_minor_release = 0; + p->embedded_controller_major_release = 0xFF; + p->embedded_controller_minor_release = 0xFF; + + *end = 0; + end++; + if (!str_index) { + *end = 0; + end++; + } + + return end; +} + +#define BIOS_NAME "SeaBIOS" +#define BIOS_DATE "04/01/2014" + +static int +smbios_romfile_setup(void) +{ + struct romfile_s *f_anchor = romfile_find("etc/smbios/smbios-anchor"); + struct romfile_s *f_tables = romfile_find("etc/smbios/smbios-tables"); + struct smbios_entry_point ep; + struct smbios_type_0 *t0; + u16 qtables_len, need_t0 = 1; + u8 *qtables, *tables; + + if (!f_anchor || !f_tables || f_anchor->size != sizeof(ep)) + return 0; + + f_anchor->copy(f_anchor, &ep, f_anchor->size); + + if (f_tables->size != ep.structure_table_length) + return 0; + + qtables = malloc_tmphigh(f_tables->size); + if (!qtables) { + warn_noalloc(); + return 0; + } + f_tables->copy(f_tables, qtables, f_tables->size); + ep.structure_table_address = (u32)qtables; /* for smbios_next(), below */ + + /* did we get a type 0 structure ? */ + for (t0 = smbios_next(&ep, NULL); t0; t0 = smbios_next(&ep, t0)) + if (t0->header.type == 0) { + need_t0 = 0; + break; + } + + qtables_len = ep.structure_table_length; + if (need_t0) { + /* common case: add our own type 0, with 3 strings and 4 '\0's */ + u16 t0_len = sizeof(struct smbios_type_0) + strlen(BIOS_NAME) + + strlen(VERSION) + strlen(BIOS_DATE) + 4; + ep.structure_table_length += t0_len; + if (t0_len > ep.max_structure_size) + ep.max_structure_size = t0_len; + ep.number_of_structures++; + } + + /* allocate final blob and record its address in the entry point */ + if (ep.structure_table_length > BUILD_MAX_SMBIOS_FSEG) + tables = malloc_high(ep.structure_table_length); + else + tables = malloc_fseg(ep.structure_table_length); + if (!tables) { + warn_noalloc(); + free(qtables); + return 0; + } + ep.structure_table_address = (u32)tables; + + /* populate final blob */ + if (need_t0) + tables = smbios_new_type_0(tables, BIOS_NAME, VERSION, BIOS_DATE); + memcpy(tables, qtables, qtables_len); + free(qtables); + + /* finalize entry point */ + ep.checksum -= checksum(&ep, 0x10); + ep.intermediate_checksum -= checksum((void *)&ep + 0x10, ep.length - 0x10); + + copy_smbios(&ep); + return 1; +} + +void +smbios_setup(void) +{ + if (smbios_romfile_setup()) + return; + smbios_legacy_setup(); +}
void copy_table(void *pos) diff --git a/src/fw/smbios.c b/src/fw/smbios.c index 0ac9ff5..dba0541 100644 --- a/src/fw/smbios.c +++ b/src/fw/smbios.c @@ -504,7 +504,7 @@ smbios_init_type_127(void *start) #define TEMPSMBIOSSIZE (32 * 1024)
void -smbios_setup(void) +smbios_legacy_setup(void) { if (! CONFIG_SMBIOS) return; diff --git a/src/util.h b/src/util.h index 4f242bf..9557581 100644 --- a/src/util.h +++ b/src/util.h @@ -78,6 +78,7 @@ extern struct smbios_entry_point *SMBiosAddr; void copy_smbios(void *pos); void display_uuid(void); void copy_table(void *pos); +void smbios_setup(void);
// fw/coreboot.c extern const char *CBvendor, *CBpart; @@ -117,7 +118,7 @@ void make_bios_readonly(void); void qemu_prep_reset(void);
// fw/smbios.c -void smbios_setup(void); +void smbios_legacy_setup(void);
// fw/smm.c void smm_device_setup(void);
On Mon, Apr 14, 2014 at 03:30:14PM -0400, Gabriel L. Somlo wrote:
Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables) and an entry point structure (etc/smbios/smbios-anchor), and, if found, use them instead of generating our own copies locally.
We ensure the presence of a type 0 (bios information) structure by generating one locally if necessary, which is expected to be the common case.
Thanks. Looks good to me.
-Kevin
"Gabriel L. Somlo" gsomlo@gmail.com writes:
Check fw_cfg for the presence of a complete set of smbios tables (etc/smbios/smbios-tables) and an entry point structure (etc/smbios/smbios-anchor), and, if found, use them instead of generating our own copies locally.
We ensure the presence of a type 0 (bios information) structure by generating one locally if necessary, which is expected to be the common case.
Signed-off-by: Gabriel L. Somlo somlo@cmu.edu
This now goes on top of Kevin's patch factoring out smbios table walking into smbios_next().
On Sat, Apr 12, 2014 at 11:56:08AM -0400, Kevin O'Connor wrote:
I'd prefer to add this code to src/fw/paravirt.c and src/fw/biostables.c instead of src/fw/smbios.c. That way, the smbios.c file is limited to the legacy smbios generation and we can more clearly document that the whole file is deprecated.
OK, so most of the new code is now in biostables.c, hope that's OK.
QEMU currently has command-line options that can modify the fields of the type0 tables (-smbios type=0,vendor='foo'). To continue to support that, I think QEMU should be able to build the type0 table as it feels fit to, and SeaBIOS should be able to pass it through. Of course, if there's no specific request from the end user, then I think QEMU can tell SeaBIOS that it may replace the type0 content with its own data (eg, via "etc/update-smbios-type0").
Something like that, except (hopefully) even simpler :)
If QEMU adds its (still optional) type 0 structure, SeaBIOS just uses everything as-is. If no type 0 structure is found, we generate and prepend our own, and update the entry point fields accordingly.
I expect the latter to be the common case (having QEMU add its own type 0 sub-table is rare, and discouraged in practice, according to the QEMU guys).
Messing with SMBIOS entries is a rather obscure feature, and messing with type 0 is perhaps more obscure than messing with type 1.
QEMU provides two ways to mess with SMBIOS entries:
* -smbios type=T,KEY=VAL...
T can be either 0 or 1.
* -smbios file=F
F contains an SMBIOS struct. Its type is parsed from file F.
If -smbios file=F is given, it completely defines the type T entry. Else, the type T entry is to be constructed from SeaBIOS defaults and any -smbios type=T,... given.
If I read your discussion correctly, you're proposing to change "from SeaBIOS defaults" to "from QEMU defaults". Correct?
If I read your discussion correctly, you're proposing to change "from SeaBIOS defaults" to "from QEMU defaults". Correct?
That is correct, where "qemu defaults" will be "seabios defaults of the current stable release aka 1.7.4".
cheers, Gerd
On Mo, 2014-04-14 at 15:30 -0400, Gabriel L. Somlo wrote:
If QEMU adds its (still optional) type 0 structure, SeaBIOS just uses everything as-is. If no type 0 structure is found, we generate and prepend our own, and update the entry point fields accordingly.
I expect the latter to be the common case (having QEMU add its own type 0 sub-table is rare, and discouraged in practice, according to the QEMU guys).
[ it's probably a good idea to update the qemu docs explicitly say so ]
Finally, it feels like BIOS_NAME and BIOS_DATE should maybe come from some other, more central place in the SeaBIOS tree, but didn't find anything applicable...
Looks good to me too. The remaining issues which are to be sorted are on the qemu side only.
cheers, Gerd