Signed-off-by: Ivan Mironov <mironov.i...@gmail.com> --- hw/i386/smbios.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++- include/hw/i386/smbios.h | 11 +++ qemu-options.hx | 42 ++++++++- 3 files changed, 268 insertions(+), 4 deletions(-)
diff --git a/hw/i386/smbios.c b/hw/i386/smbios.c index d3f1ee6..c5fccba 100644 --- a/hw/i386/smbios.c +++ b/hw/i386/smbios.c @@ -19,6 +19,7 @@ #include "qemu/error-report.h" #include "sysemu/sysemu.h" #include "hw/i386/smbios.h" +#include "hw/pci/pci.h" #include "hw/loader.h" /* @@ -66,6 +67,48 @@ static struct { /* uuid is in qemu_uuid[] */ } type1; +#define ONBOARD_DEV_TYPE_MIN 0x01 +#define ONBOARD_DEV_TYPE_MAX 0x0A +#define ONBOARD_DEV_TYPE_NUM \ + (ONBOARD_DEV_TYPE_MAX - ONBOARD_DEV_TYPE_MIN + 1) + +static const struct { + const char *type_str; + uint8_t type_num; +} onboard_dev_type_str[ONBOARD_DEV_TYPE_NUM] = { + { + .type_str = "other", + .type_num = 0x01, + },{ + .type_str = "unknown", + .type_num = 0x02, + },{ + .type_str = "video", + .type_num = 0x03, + },{ + .type_str = "scsi", + .type_num = 0x04, + },{ + .type_str = "ethernet", + .type_num = 0x05, + },{ + .type_str = "token-ring", + .type_num = 0x06, + },{ + .type_str = "sound", + .type_num = 0x07, + },{ + .type_str = "pata", + .type_num = 0x08, + },{ + .type_str = "sata", + .type_num = 0x09, + },{ + .type_str = "sas", + .type_num = 0x0a, + } +}; + static QemuOptsList qemu_smbios_opts = { .name = "smbios", .head = QTAILQ_HEAD_INITIALIZER(qemu_smbios_opts.head), @@ -149,6 +192,35 @@ static const QemuOptDesc qemu_smbios_type1_opts[] = { { /* end of list */ } }; +static const QemuOptDesc qemu_smbios_type41_opts[] = { + { + .name = "type", + .type = QEMU_OPT_NUMBER, + .help = "SMBIOS element type", + },{ + .name = "designation", + .type = QEMU_OPT_STRING, + .help = "reference designation", + },{ + .name = "status", + .type = QEMU_OPT_BOOL, + .help = "device status", + },{ + .name = "device-type", + .type = QEMU_OPT_STRING, + .help = "device type", + },{ + .name = "instance", + .type = QEMU_OPT_NUMBER, + .help = "device type instance", + },{ + .name = "address", + .type = QEMU_OPT_STRING, + .help = "pci bus address", + }, + { /* end of list */ } +}; + static void smbios_register_config(void) { qemu_add_opts(&qemu_smbios_opts); @@ -217,6 +289,11 @@ static void smbios_maybe_add_str(int type, int offset, const char *data) } } +static void smbios_add_fields_set_end_marker(int type) +{ + smbios_add_field(type, 0, NULL, 0); +} + static void smbios_build_type_0_fields(void) { smbios_maybe_add_str(0, offsetof(struct smbios_type_0, vendor_str), @@ -277,6 +354,119 @@ static void save_opt(const char **dest, QemuOpts *opts, const char *name) } } +static void smbios_check_onboard_device_instance(int type, int instance) +{ + static uint8_t instances[ONBOARD_DEV_TYPE_NUM][UINT8_MAX / 8]; + uint8_t *type_instances = instances[type]; + + if (type_instances[instance / 8] & (1 << (instance % 8))) { + error_report("instance %d is not unique within device-type %d", + instance, type); + exit(1); + } + type_instances[instance / 8] |= (uint8_t)(1 << (instance % 8)); +} + +static void smbios_entry_add_type_41(QemuOpts *opts) +{ + const char *designation; + bool dev_status; + const char *dev_type_str; + unsigned long dev_type = 0x02 /* Unknown */; + uint64_t dev_instance; + const char *address; + int seg, bus; + unsigned int dev, func; + + /* Reference Designation */ + designation = qemu_opt_get(opts, "designation"); + smbios_maybe_add_str(41, + offsetof(struct smbios_type_41, reference_designation_str), + designation); + + /* Device Type */ + dev_status = qemu_opt_get_bool(opts, "status", true /* Enabled */); + dev_type_str = qemu_opt_get(opts, "device-type"); + if (dev_type_str) { + char *endptr; + dev_type = strtoul(dev_type_str, &endptr, 0); + if (*dev_type_str && !*endptr) { + /* Got number. */ + if (dev_type < ONBOARD_DEV_TYPE_MIN) { + error_report("device-type is < %d", ONBOARD_DEV_TYPE_MIN); + exit(1); + } + if (dev_type > ONBOARD_DEV_TYPE_MAX) { + error_report("device-type is > %d", ONBOARD_DEV_TYPE_MAX); + exit(1); + } + } else { + /* Got string. */ + unsigned int i; + dev_type = 0; + for (i = 0; i < ONBOARD_DEV_TYPE_NUM; i++) { + if (!strcmp(dev_type_str, onboard_dev_type_str[i].type_str)) { + dev_type = onboard_dev_type_str[i].type_num; + break; + } + } + if (!dev_type) { + error_report("unknown device-type"); + exit(1); + } + } + } + smbios_add_field(41, + offsetof(struct smbios_type_41, device_type), + &(uint8_t){ ((dev_status ? 1 : 0) << 7) | dev_type }, + sizeof(uint8_t)); + + /* Device Type Instance */ + dev_instance = qemu_opt_get_number(opts, "instance", UINT64_MAX); + if (dev_instance == UINT64_MAX) { + error_report("You should specify instance"); + exit(1); + } + if (dev_instance < 1) { + error_report("instance is < 1"); + exit(1); + } + if (dev_instance > UINT8_MAX) { + error_report("instance is > %d", UINT8_MAX); + exit(1); + } + smbios_check_onboard_device_instance(dev_type, dev_instance); + smbios_add_field(41, + offsetof(struct smbios_type_41, device_type_instance), + &(uint8_t){ dev_instance }, + sizeof(uint8_t)); + + /* Segment Group Number, Bus Number and Device/Function Number */ + address = qemu_opt_get(opts, "address"); + if (!address) { + error_report("You should specify address"); + exit(1); + } + if (pci_parse_devaddr(address, &seg, &bus, &dev, &func) < 0) { + error_report("Invalid address"); + exit(1); + } + smbios_add_field(41, + offsetof(struct smbios_type_41, segment_group_number), + &(uint16_t){ cpu_to_le16(seg) }, + sizeof(uint16_t)); + smbios_add_field(41, + offsetof(struct smbios_type_41, bus_number), + &(uint8_t){ bus }, + sizeof(uint8_t)); + smbios_add_field(41, + offsetof(struct smbios_type_41, device_function_number), + &(uint8_t){ (dev << 3) | func }, + sizeof(uint8_t)); + + smbios_add_fields_set_end_marker(41); +} + void smbios_entry_add(QemuOpts *opts) { Error *local_err = NULL; @@ -319,8 +509,27 @@ void smbios_entry_add(QemuOpts *opts) header = (struct smbios_structure_header *)(table->data); smbios_check_collision(header->type, SMBIOS_TABLE_ENTRY); - if (header->type == 4) { + switch (header->type) { + case 4: smbios_type4_count++; + break; + case 41: + if (size != sizeof(struct smbios_type_41)) { + error_report("Failed to load SMBIOS file %s: invalid type 41 " + "table", val); + exit(1); + } + + { + uint8_t dev_type = table->data + [offsetof(struct smbios_type_41, device_type)]; + uint8_t dev_instance = table->data + [offsetof(struct smbios_type_41, device_type_instance)]; + smbios_check_onboard_device_instance(dev_type, dev_instance); + } + break; + default: + break; } smbios_entries_len += sizeof(*table) + size; @@ -377,6 +586,14 @@ void smbios_entry_add(QemuOpts *opts) qemu_uuid_set = true; } return; + case 41: + qemu_opts_validate(opts, qemu_smbios_type41_opts, &local_err); + if (local_err) { + error_report("%s", error_get_pretty(local_err)); + exit(1); + } + smbios_entry_add_type_41(opts); + return; default: error_report("Don't know how to build fields for SMBIOS type %ld", type); diff --git a/include/hw/i386/smbios.h b/include/hw/i386/smbios.h index b08ec71..a57ffc9 100644 --- a/include/hw/i386/smbios.h +++ b/include/hw/i386/smbios.h @@ -155,6 +155,17 @@ struct smbios_type_32 { uint8_t boot_status; } QEMU_PACKED; +/* SMBIOS type 41 - Onboard Devices Extended Information */ +struct smbios_type_41 { + struct smbios_structure_header header; + uint8_t reference_designation_str; + uint8_t device_type; + uint8_t device_type_instance; + uint16_t segment_group_number; + uint8_t bus_number; + uint8_t device_function_number; +} QEMU_PACKED; + /* SMBIOS type 127 -- End-of-table */ struct smbios_type_127 { struct smbios_structure_header header; diff --git a/qemu-options.hx b/qemu-options.hx index 5dc8b75..a3b4b78 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -154,8 +154,8 @@ Set default value of @var{driver}'s property @var{prop} to @var{value}, e.g.: qemu-system-i386 -global ide-drive.physical_block_size=4096 -drive file=file,if=ide,index=0,media=disk @end example -In particular, you can use this to set driver properties for devices which are -created automatically by the machine model. To create a device which is not +In particular, you can use this to set driver properties for devices which are +created automatically by the machine model. To create a device which is not created automatically and set properties on it, use -@option{device}. ETEXI @@ -1320,7 +1320,10 @@ DEF("smbios", HAS_ARG, QEMU_OPTION_smbios, " specify SMBIOS type 0 fields\n" "-smbios type=1[,manufacturer=str][,product=str][,version=str][,serial=str]\n" " [,uuid=uuid][,sku=str][,family=str]\n" - " specify SMBIOS type 1 fields\n", QEMU_ARCH_I386) + " specify SMBIOS type 1 fields\n" + "-smbios type=41,address=str,instance=n[,designation=str]\n" + " [,status=on|off][,device-type=str|n]\n" + " add SMBIOS type 41 fields (Onboard Devices Extended Information)\n" , QEMU_ARCH_I386) STEXI @item -smbios file=@var{binary} @findex -smbios @@ -1331,6 +1334,39 @@ Specify SMBIOS type 0 fields @item -smbios type=1[,manufacturer=@var{str}][,product=@var{str}] [,version=@var{str}][,serial=@var{str}][,uuid=@var{uuid}][,sku=@var{str}] [,family=@var{str}] Specify SMBIOS type 1 fields + +@item -smbios type=41,address=@var{str},instance=@var{n}[,designation=@var{str}][,status=on|off][,device-type=@var{str|n}] +Add SMBIOS type 41 fields (Onboard Devices Extended Information). Could be +specified multiple times for different devices. Mandatory options are +@option{address} in form "[[<domain>:]<bus>:]<slot>.<func>" and +@option{instance} number. @option{instance} shoud be in range [1, 255] and +should be unique within specified @option{device-type}. @option{designation} is +an optional string that somehow designates device. @option{status} is a device +status and is "on" by default. @option{device-type} could be specified in +numerical form or as string alias. Supported device types: +@example +n | string | description +------------------------------------ +1 | other | Other +2 | unknown | Unknown (default) +3 | video | Video +4 | scsi | SCSI Controller +5 | ethernet | Ethernet +6 | token-ring | Token Ring +7 | sound | Sound +8 | pata | PATA Controller +9 | sata | SATA Controller +10 | sas | SAS Controller +@end example +This option could be used in conjunction with biosdevname utility in linux guest +system to provide consistent network device naming. Usage example: +@example +qemu-i386 \ +-netdev user,id=hostnet0 \ +-device e1000,netdev=hostnet0,id=net0,bus=pci.0,addr=0x1f \ +-smbios type=41,address=00:1f.0,instance=1,designation="NIC 1",device-type=ethernet \ +< ... other qemu options ... > +@end example ETEXI STEXI -- 1.8.4.1