#include <sys/param.h>
#include <assert.h>
#include <errno.h>
#include <md5.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <uuid.h>
#include <machine/vmm.h>
#include <vmmapi.h>
#include "bhyverun.h"
#include "config.h"
#include "debug.h"
#include "smbiostbl.h"
#define MB (1024*1024)
#define GB (1024ULL*1024*1024)
#define SMBIOS_BASE 0xF1000
#define FIRMWARE_VERSION "14.0"
#define FIRMWARE_RELEASE_DATE "10/17/2021"
#define SMBIOS_MAX_LENGTH (0xF2400 - 0xF1000)
#define SMBIOS_TYPE_BIOS 0
#define SMBIOS_TYPE_SYSTEM 1
#define SMBIOS_TYPE_BOARD 2
#define SMBIOS_TYPE_CHASSIS 3
#define SMBIOS_TYPE_PROCESSOR 4
#define SMBIOS_TYPE_MEMARRAY 16
#define SMBIOS_TYPE_MEMDEVICE 17
#define SMBIOS_TYPE_MEMARRAYMAP 19
#define SMBIOS_TYPE_BOOT 32
#define SMBIOS_TYPE_EOT 127
struct smbios_structure {
uint8_t type;
uint8_t length;
uint16_t handle;
} __packed;
struct smbios_string {
const char *node;
const char *value;
};
typedef int (*initializer_func_t)(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
struct smbios_template_entry {
const struct smbios_structure *entry;
const struct smbios_string *strings;
initializer_func_t initializer;
};
#define SMBIOS_ENTRY_EANCHOR "_SM_"
#define SMBIOS_ENTRY_EANCHORLEN 4
#define SMBIOS_ENTRY_IANCHOR "_DMI_"
#define SMBIOS_ENTRY_IANCHORLEN 5
struct smbios_entry_point {
char eanchor[4];
uint8_t echecksum;
uint8_t eplen;
uint8_t major;
uint8_t minor;
uint16_t maxssize;
uint8_t revision;
uint8_t format[5];
char ianchor[5];
uint8_t ichecksum;
uint16_t stlen;
uint32_t staddr;
uint16_t stnum;
uint8_t bcdrev;
} __packed;
#define SMBIOS_FL_ISA 0x00000010
#define SMBIOS_FL_PCI 0x00000080
#define SMBIOS_FL_SHADOW 0x00001000
#define SMBIOS_FL_CDBOOT 0x00008000
#define SMBIOS_FL_SELBOOT 0x00010000
#define SMBIOS_FL_EDD 0x00080000
#define SMBIOS_XB1_FL_ACPI 0x00000001
#define SMBIOS_XB2_FL_BBS 0x00000001
#define SMBIOS_XB2_FL_VM 0x00000010
struct smbios_table_type0 {
struct smbios_structure header;
uint8_t vendor;
uint8_t version;
uint16_t segment;
uint8_t rel_date;
uint8_t size;
uint64_t cflags;
uint8_t xc_bytes[2];
uint8_t sb_major_rel;
uint8_t sb_minor_rele;
uint8_t ecfw_major_rel;
uint8_t ecfw_minor_rel;
} __packed;
#define SMBIOS_WAKEUP_SWITCH 0x06
struct smbios_table_type1 {
struct smbios_structure header;
uint8_t manufacturer;
uint8_t product;
uint8_t version;
uint8_t serial;
uint8_t uuid[16];
uint8_t wakeup;
uint8_t sku;
uint8_t family;
} __packed;
#define SMBIOS_BRF_HOSTING 0x1
#define SMBIOS_BRT_MOTHERBOARD 0xa
struct smbios_table_type2 {
struct smbios_structure header;
uint8_t manufacturer;
uint8_t product;
uint8_t version;
uint8_t serial;
uint8_t asset;
uint8_t fflags;
uint8_t location;
uint16_t chandle;
uint8_t type;
uint8_t n_objs;
} __packed;
#define SMBIOS_CHT_UNKNOWN 0x02
#define SMBIOS_CHT_DESKTOP 0x03
#define SMBIOS_CHST_SAFE 0x03
#define SMBIOS_CHSC_NONE 0x03
struct smbios_table_type3 {
struct smbios_structure header;
uint8_t manufacturer;
uint8_t type;
uint8_t version;
uint8_t serial;
uint8_t asset;
uint8_t bustate;
uint8_t psstate;
uint8_t tstate;
uint8_t security;
uint32_t oemdata;
uint8_t uheight;
uint8_t cords;
uint8_t elems;
uint8_t elemlen;
uint8_t sku;
} __packed;
#define SMBIOS_PRT_CENTRAL 0x03
#define SMBIOS_PRF_OTHER 0x01
#define SMBIOS_PRS_PRESENT 0x40
#define SMBIOS_PRS_ENABLED 0x1
#define SMBIOS_PRU_NONE 0x06
#define SMBIOS_PFL_64B 0x04
struct smbios_table_type4 {
struct smbios_structure header;
uint8_t socket;
uint8_t type;
uint8_t family;
uint8_t manufacturer;
uint64_t cpuid;
uint8_t version;
uint8_t voltage;
uint16_t clkspeed;
uint16_t maxspeed;
uint16_t curspeed;
uint8_t status;
uint8_t upgrade;
uint16_t l1handle;
uint16_t l2handle;
uint16_t l3handle;
uint8_t serial;
uint8_t asset;
uint8_t part;
uint8_t cores;
uint8_t ecores;
uint8_t threads;
uint16_t cflags;
uint16_t family2;
} __packed;
#define SMBIOS_MAL_SYSMB 0x03
#define SMBIOS_MAU_SYSTEM 0x03
#define SMBIOS_MAE_NONE 0x03
struct smbios_table_type16 {
struct smbios_structure header;
uint8_t location;
uint8_t use;
uint8_t ecc;
uint32_t size;
uint16_t errhand;
uint16_t ndevs;
uint64_t xsize;
} __packed;
#define SMBIOS_MDFF_UNKNOWN 0x02
#define SMBIOS_MDT_UNKNOWN 0x02
#define SMBIOS_MDF_UNKNOWN 0x0004
struct smbios_table_type17 {
struct smbios_structure header;
uint16_t arrayhand;
uint16_t errhand;
uint16_t twidth;
uint16_t dwidth;
uint16_t size;
uint8_t form;
uint8_t set;
uint8_t dloc;
uint8_t bloc;
uint8_t type;
uint16_t flags;
uint16_t maxspeed;
uint8_t manufacturer;
uint8_t serial;
uint8_t asset;
uint8_t part;
uint8_t attributes;
uint32_t xsize;
uint16_t curspeed;
uint16_t minvoltage;
uint16_t maxvoltage;
uint16_t curvoltage;
} __packed;
struct smbios_table_type19 {
struct smbios_structure header;
uint32_t saddr;
uint32_t eaddr;
uint16_t arrayhand;
uint8_t width;
uint64_t xsaddr;
uint64_t xeaddr;
} __packed;
#define SMBIOS_BOOT_NORMAL 0
struct smbios_table_type32 {
struct smbios_structure header;
uint8_t reserved[6];
uint8_t status;
} __packed;
struct smbios_table_type127 {
struct smbios_structure header;
} __packed;
static const struct smbios_table_type0 smbios_type0_template = {
{ SMBIOS_TYPE_BIOS, sizeof (struct smbios_table_type0), 0 },
1,
2,
0xF000,
3,
0x0,
SMBIOS_FL_ISA | SMBIOS_FL_PCI | SMBIOS_FL_SHADOW |
SMBIOS_FL_CDBOOT | SMBIOS_FL_EDD,
{ SMBIOS_XB1_FL_ACPI, SMBIOS_XB2_FL_BBS | SMBIOS_XB2_FL_VM },
0x0,
0x0,
0xff,
0xff
};
static const struct smbios_string smbios_type0_strings[] = {
{ "bios.vendor", "BHYVE" },
{ "bios.version", FIRMWARE_VERSION },
{ "bios.release_date", FIRMWARE_RELEASE_DATE },
{ 0 }
};
static const struct smbios_table_type1 smbios_type1_template = {
{ SMBIOS_TYPE_SYSTEM, sizeof (struct smbios_table_type1), 0 },
1,
2,
3,
4,
{ 0 },
SMBIOS_WAKEUP_SWITCH,
5,
6
};
static int smbios_type1_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static const struct smbios_string smbios_type1_strings[] = {
{ "system.manufacturer", "FreeBSD" },
{ "system.product_name", "BHYVE" },
{ "system.version", "1.0" },
{ "system.serial_number", "None" },
{ "system.sku", "None" },
{ "system.family_name", "Virtual Machine" },
{ 0 }
};
static const struct smbios_table_type2 smbios_type2_template = {
{ SMBIOS_TYPE_BOARD, sizeof (struct smbios_table_type2), 0 },
1,
2,
3,
4,
5,
SMBIOS_BRF_HOSTING,
6,
SMBIOS_CHT_DESKTOP,
SMBIOS_BRT_MOTHERBOARD,
0
};
static const struct smbios_string smbios_type2_strings[] = {
{ "board.manufacturer", "FreeBSD" },
{ "board.product_name", "BHYVE" },
{ "board.version", "1.0" },
{ "board.serial_number", "None" },
{ "board.asset_tag", "None" },
{ "board.location", "None" },
{ 0 }
};
static const struct smbios_table_type3 smbios_type3_template = {
{ SMBIOS_TYPE_CHASSIS, sizeof (struct smbios_table_type3), 0 },
1,
SMBIOS_CHT_UNKNOWN,
2,
3,
4,
SMBIOS_CHST_SAFE,
SMBIOS_CHST_SAFE,
SMBIOS_CHST_SAFE,
SMBIOS_CHSC_NONE,
0,
0,
0,
0,
0,
5
};
static const struct smbios_string smbios_type3_strings[] = {
{ "chassis.manufacturer", "FreeBSD" },
{ "chassis.version", "1.0" },
{ "chassis.serial_number", "None" },
{ "chassis.asset_tag", "None" },
{ "chassis.sku", "None" },
{ 0 }
};
static const struct smbios_table_type4 smbios_type4_template = {
{ SMBIOS_TYPE_PROCESSOR, sizeof (struct smbios_table_type4), 0 },
1,
SMBIOS_PRT_CENTRAL,
SMBIOS_PRF_OTHER,
2,
0,
3,
0,
0,
0,
0,
SMBIOS_PRS_PRESENT | SMBIOS_PRS_ENABLED,
SMBIOS_PRU_NONE,
-1,
-1,
-1,
4,
5,
6,
0,
0,
0,
SMBIOS_PFL_64B,
SMBIOS_PRF_OTHER
};
static const struct smbios_string smbios_type4_strings[] = {
{ NULL, " " },
{ NULL, " " },
{ NULL, " " },
{ NULL, "None" },
{ NULL, "None" },
{ NULL, "None" },
{ 0 }
};
static int smbios_type4_initializer(
const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static const struct smbios_table_type16 smbios_type16_template = {
{ SMBIOS_TYPE_MEMARRAY, sizeof (struct smbios_table_type16), 0 },
SMBIOS_MAL_SYSMB,
SMBIOS_MAU_SYSTEM,
SMBIOS_MAE_NONE,
0x80000000,
-1,
0,
0
};
static int smbios_type16_initializer(
const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static const struct smbios_table_type17 smbios_type17_template = {
{ SMBIOS_TYPE_MEMDEVICE, sizeof (struct smbios_table_type17), 0 },
-1,
-1,
64,
64,
0,
SMBIOS_MDFF_UNKNOWN,
0,
1,
2,
SMBIOS_MDT_UNKNOWN,
SMBIOS_MDF_UNKNOWN,
0,
3,
4,
5,
6,
0,
0,
0,
0,
0,
0
};
static const struct smbios_string smbios_type17_strings[] = {
{ NULL, " " },
{ NULL, " " },
{ NULL, " " },
{ NULL, "None" },
{ NULL, "None" },
{ NULL, "None" },
{ 0 }
};
static int smbios_type17_initializer(
const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static const struct smbios_table_type19 smbios_type19_template = {
{ SMBIOS_TYPE_MEMARRAYMAP, sizeof (struct smbios_table_type19), 0 },
0xffffffff,
0xffffffff,
-1,
1,
0,
0
};
static int smbios_type19_initializer(
const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static struct smbios_table_type32 smbios_type32_template = {
{ SMBIOS_TYPE_BOOT, sizeof (struct smbios_table_type32), 0 },
{ 0, 0, 0, 0, 0, 0 },
SMBIOS_BOOT_NORMAL
};
static const struct smbios_table_type127 smbios_type127_template = {
{ SMBIOS_TYPE_EOT, sizeof (struct smbios_table_type127), 0 }
};
static int smbios_generic_initializer(
const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n);
static struct smbios_template_entry smbios_template[] = {
{ (const struct smbios_structure *)&smbios_type0_template,
smbios_type0_strings,
smbios_generic_initializer },
{ (const struct smbios_structure *)&smbios_type1_template,
smbios_type1_strings,
smbios_type1_initializer },
{ (const struct smbios_structure *)&smbios_type2_template,
smbios_type2_strings,
smbios_generic_initializer },
{ (const struct smbios_structure *)&smbios_type3_template,
smbios_type3_strings,
smbios_generic_initializer },
{ (const struct smbios_structure *)&smbios_type4_template,
smbios_type4_strings,
smbios_type4_initializer },
{ (const struct smbios_structure *)&smbios_type16_template,
NULL,
smbios_type16_initializer },
{ (const struct smbios_structure *)&smbios_type17_template,
smbios_type17_strings,
smbios_type17_initializer },
{ (const struct smbios_structure *)&smbios_type19_template,
NULL,
smbios_type19_initializer },
{ (const struct smbios_structure *)&smbios_type32_template,
NULL,
smbios_generic_initializer },
{ (const struct smbios_structure *)&smbios_type127_template,
NULL,
smbios_generic_initializer },
{ NULL,NULL, NULL }
};
static uint64_t guest_lomem, guest_himem, guest_himem_base;
static uint16_t type16_handle;
static int
smbios_generic_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
struct smbios_structure *entry;
memcpy(curaddr, template_entry, template_entry->length);
entry = (struct smbios_structure *)curaddr;
entry->handle = *n + 1;
curaddr += entry->length;
if (template_strings != NULL) {
int i;
for (i = 0; template_strings[i].value != NULL; i++) {
const char *string;
int len;
if (template_strings[i].node == NULL) {
string = template_strings[i].value;
} else {
set_config_value_if_unset(
template_strings[i].node,
template_strings[i].value);
string = get_config_value(
template_strings[i].node);
}
len = strlen(string) + 1;
memcpy(curaddr, string, len);
curaddr += len;
}
*curaddr = '\0';
curaddr++;
} else {
*curaddr = '\0';
curaddr++;
*curaddr = '\0';
curaddr++;
}
(*n)++;
*endaddr = curaddr;
return (0);
}
static int
smbios_type1_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
struct smbios_table_type1 *type1;
const char *guest_uuid_str;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type1 = (struct smbios_table_type1 *)curaddr;
guest_uuid_str = get_config_value("uuid");
if (guest_uuid_str != NULL) {
uuid_t uuid;
uint32_t status;
uuid_from_string(guest_uuid_str, &uuid, &status);
if (status != uuid_s_ok) {
EPRINTLN("Invalid UUID");
return (-1);
}
uuid_enc_le(&type1->uuid, &uuid);
} else {
MD5_CTX mdctx;
u_char digest[16];
char hostname[MAXHOSTNAMELEN];
const char *vmname;
if (gethostname(hostname, sizeof(hostname)))
return (-1);
MD5Init(&mdctx);
vmname = get_config_value("name");
MD5Update(&mdctx, vmname, strlen(vmname));
MD5Update(&mdctx, hostname, sizeof(hostname));
MD5Final(digest, &mdctx);
digest[6] &= 0x0F;
digest[6] |= 0x30;
digest[8] &= 0x3F;
digest[8] |= 0x80;
memcpy(&type1->uuid, digest, sizeof (digest));
}
return (0);
}
static int
smbios_type4_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
int i;
for (i = 0; i < cpu_sockets; i++) {
struct smbios_table_type4 *type4;
char *p;
int nstrings, len;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type4 = (struct smbios_table_type4 *)curaddr;
p = curaddr + sizeof (struct smbios_table_type4);
nstrings = 0;
while (p < *endaddr - 1) {
if (*p++ == '\0')
nstrings++;
}
len = sprintf(*endaddr - 1, "CPU #%d", i) + 1;
*endaddr += len - 1;
*(*endaddr) = '\0';
(*endaddr)++;
type4->socket = nstrings + 1;
if (cpu_cores > 254)
type4->cores = 0;
else
type4->cores = cpu_cores;
if (cpu_cores * cpu_threads > 254)
type4->threads = 0;
else
type4->threads = cpu_cores * cpu_threads;
curaddr = *endaddr;
}
return (0);
}
static int
smbios_type16_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
struct smbios_table_type16 *type16;
type16_handle = *n;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type16 = (struct smbios_table_type16 *)curaddr;
type16->xsize = guest_lomem + guest_himem;
type16->ndevs = guest_himem > 0 ? 2 : 1;
return (0);
}
static int
smbios_type17_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
struct smbios_table_type17 *type17;
uint64_t memsize, size_KB, size_MB;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type17 = (struct smbios_table_type17 *)curaddr;
type17->arrayhand = type16_handle;
memsize = guest_lomem + guest_himem;
size_KB = memsize / 1024;
size_MB = memsize / MB;
if (size_MB > 0x7FFFFFFF) {
printf("Warning: guest memory too big for SMBIOS Type 17 table: "
"%luMB greater than max supported 2147483647MB\n", size_MB);
size_MB = 0x7FFFFFFF;
}
if (size_KB <= 0x7FFF) {
type17->size = size_KB | (1 << 15);
} else if (size_MB < 0x7FFF) {
type17->size = size_MB & 0x7FFF;
} else {
type17->size = 0x7FFF;
type17->xsize = size_MB & 0x7FFFFFFF;
}
return (0);
}
static int
smbios_type19_initializer(const struct smbios_structure *template_entry,
const struct smbios_string *template_strings, char *curaddr, char **endaddr,
uint16_t *n)
{
struct smbios_table_type19 *type19;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type19 = (struct smbios_table_type19 *)curaddr;
type19->arrayhand = type16_handle;
type19->xsaddr = 0;
type19->xeaddr = guest_lomem;
if (guest_himem > 0) {
curaddr = *endaddr;
smbios_generic_initializer(template_entry, template_strings,
curaddr, endaddr, n);
type19 = (struct smbios_table_type19 *)curaddr;
type19->arrayhand = type16_handle;
type19->xsaddr = guest_himem_base;
type19->xeaddr = guest_himem_base + guest_himem;
}
return (0);
}
static void
smbios_ep_initializer(struct smbios_entry_point *smbios_ep, uint32_t staddr)
{
memset(smbios_ep, 0, sizeof(*smbios_ep));
memcpy(smbios_ep->eanchor, SMBIOS_ENTRY_EANCHOR,
SMBIOS_ENTRY_EANCHORLEN);
smbios_ep->eplen = 0x1F;
assert(sizeof (struct smbios_entry_point) == smbios_ep->eplen);
smbios_ep->major = 2;
smbios_ep->minor = 6;
smbios_ep->revision = 0;
memcpy(smbios_ep->ianchor, SMBIOS_ENTRY_IANCHOR,
SMBIOS_ENTRY_IANCHORLEN);
smbios_ep->staddr = staddr;
smbios_ep->bcdrev = (smbios_ep->major & 0xf) << 4 | (smbios_ep->minor & 0xf);
}
static void
smbios_ep_finalizer(struct smbios_entry_point *smbios_ep, uint16_t len,
uint16_t num, uint16_t maxssize)
{
uint8_t checksum;
int i;
smbios_ep->maxssize = maxssize;
smbios_ep->stlen = len;
smbios_ep->stnum = num;
checksum = 0;
for (i = 0x10; i < 0x1f; i++) {
checksum -= ((uint8_t *)smbios_ep)[i];
}
smbios_ep->ichecksum = checksum;
checksum = 0;
for (i = 0; i < 0x1f; i++) {
checksum -= ((uint8_t *)smbios_ep)[i];
}
smbios_ep->echecksum = checksum;
}
int
smbios_build(struct vmctx *ctx)
{
struct smbios_entry_point *smbios_ep;
uint16_t n;
uint16_t maxssize;
char *curaddr, *startaddr, *ststartaddr;
int i;
int err;
guest_lomem = vm_get_lowmem_size(ctx);
guest_himem = vm_get_highmem_size(ctx);
guest_himem_base = vm_get_highmem_base(ctx);
startaddr = paddr_guest2host(ctx, SMBIOS_BASE, SMBIOS_MAX_LENGTH);
if (startaddr == NULL) {
EPRINTLN("smbios table requires mapped mem");
return (ENOMEM);
}
curaddr = startaddr;
smbios_ep = (struct smbios_entry_point *)curaddr;
smbios_ep_initializer(smbios_ep, SMBIOS_BASE +
sizeof(struct smbios_entry_point));
curaddr += sizeof(struct smbios_entry_point);
ststartaddr = curaddr;
n = 0;
maxssize = 0;
for (i = 0; smbios_template[i].entry != NULL; i++) {
const struct smbios_structure *entry;
const struct smbios_string *strings;
initializer_func_t initializer;
char *endaddr;
size_t size;
entry = smbios_template[i].entry;
strings = smbios_template[i].strings;
initializer = smbios_template[i].initializer;
err = (*initializer)(entry, strings, curaddr, &endaddr, &n);
if (err != 0)
return (err);
size = endaddr - curaddr;
assert(size <= UINT16_MAX);
if (size > maxssize)
maxssize = (uint16_t)size;
curaddr = endaddr;
}
assert(curaddr - startaddr < SMBIOS_MAX_LENGTH);
smbios_ep_finalizer(smbios_ep, curaddr - ststartaddr, n, maxssize);
return (0);
}