diff --git a/repos/os/run/smbios_decoder.run b/repos/os/run/smbios_decoder.run new file mode 100644 index 000000000..899ca8851 --- /dev/null +++ b/repos/os/run/smbios_decoder.run @@ -0,0 +1,74 @@ +assert_spec x86_64 + +if { "[board]" ne "pc" || [expr ![have_spec nova] && ![have_spec hw]] } { + puts "Run script is only supported on hw/pc and nova/pc" + exit 0 +} + +set build_components { app/smbios_decoder core init timer server/report_rom } + +source ${genode_dir}/repos/base/run/platform_drv.inc + +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +append config { + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append config { + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +set boot_modules { + report_rom + core init ld.lib.so + smbios_decoder +} + +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +append qemu_args " -nographic" + +run_genode_until "report_rom] .*?report_rom] .*?\n" 20 diff --git a/repos/os/src/app/smbios_decoder/main.cc b/repos/os/src/app/smbios_decoder/main.cc new file mode 100644 index 000000000..795d8d4c3 --- /dev/null +++ b/repos/os/src/app/smbios_decoder/main.cc @@ -0,0 +1,789 @@ +/* + * \brief Decode information from SMBIOS table and report it as XML + * \author Martin Stein + * \date 2019-07-04 + * + */ + +/* + * Copyright (C) 2019 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include +#include +#include + +using namespace Genode; + + +class Table +{ + private: + + /* two 0..255 numbers, 1 dot, terminating null*/ + using Version_2_string = String<3 * 2 + 1 + 1>; + + /* address value in hex. 2 chars prefix, terminating null */ + using Addr_string = String; + + /* 64bit value, 2 char unit, terminating null */ + using Size_string = String<20 + 2 + 1>; + + /* 16 two-digit hex values, 4 hyphen, terminating null */ + using Uuid_string = String<2 * 16 + 4 + 1>; + + /* 2 digit hex value with padding but w/o prefix */ + struct Uuid_hex : Hex + { + Uuid_hex(char digit) : Hex(digit, Hex::OMIT_PREFIX, Hex::PAD) { } + }; + + addr_t const _base; + addr_t const _end; + addr_t const _verbose; + uint8_t _version_major { (uint8_t)~0 }; + uint8_t _version_minor { (uint8_t)~0 }; + + void _warn(char const *msg) const; + + const char *_string_set_item(Smbios_structure const &header, + uint8_t idx) const; + + char const *_bios_character_0(uint8_t idx) const; + + char const *_system_wake_up_type(uint8_t idx) const; + + const char *_base_board_type(uint8_t idx) const; + + char const *_base_board_feature(uint8_t idx) const; + + void _report_base_board_features(Reporter::Xml_generator &xml, + uint8_t code) const; + + void _report_base_board_handles(Reporter::Xml_generator &xml, + uint8_t count, + uint8_t const *data) const; + + void _report_base_board(Reporter::Xml_generator &xml, + Smbios_structure const &header) const; + + void _report_bios_character_0(Reporter::Xml_generator &xml, + uint64_t code) const; + + char const *_bios_character_1(uint8_t idx) const; + + void _report_bios_character_1(Reporter::Xml_generator &xml, + uint8_t code) const; + + char const *_bios_character_2(uint8_t idx) const; + + void _report_bios_character_2(Reporter::Xml_generator &xml, + uint8_t code) const; + + void _report_bios_rom_size(Reporter::Xml_generator &xml, + uint8_t code_1, + uint16_t code_2) const; + + void _report_string(Reporter::Xml_generator &xml, + char const *type, + char const *value) const; + + void _report_string_set_item(Reporter::Xml_generator &xml, + Smbios_structure const &header, + char const *type, + unsigned idx) const; + + void _report_system_uuid(Reporter::Xml_generator &xml, + uint8_t const *data) const; + + void _report_system(Reporter::Xml_generator &xml, + Smbios_structure const &header) const; + + void _report_bios(Reporter::Xml_generator &xml, + Smbios_structure const &header) const; + + void _report_smbios_struct(Reporter::Xml_generator &xml, + Smbios_structure const &smbios_struct) const; + + void _report_smbios_structs(Reporter::Xml_generator &xml, + Dmi_entry_point const &smbios_ep) const; + + void _report_dmi_ep(Reporter::Xml_generator &xml, + Dmi_entry_point const &ep) const; + + void _report_smbios_ep(Reporter::Xml_generator &xml, + Smbios_entry_point const &smbios_ep) const; + + public: + + Table(addr_t base, + size_t size, + bool verbose) + : + _base { base }, + _end { base + size }, + _verbose { verbose } + { } + + void report(Reporter::Xml_generator &xml) const; +}; + + +class Main +{ + private: + + Env &_env; + Attached_rom_dataspace _config_ds { _env, "config" }; + Xml_node _config { _config_ds.xml() }; + bool const _verbose { _config.attribute_value("verbose", false) }; + Expanding_reporter _reporter { _env, "result", "result" }; + Attached_rom_dataspace _table_ds { _env, "smbios_table" }; + Signal_handler
_table_ds_sigh { _env.ep(), *this, &Main::_handle_table_ds }; + Constructible _table { }; + + void _handle_table_ds(); + + public: + + Main(Env &env); +}; + + +/*************** + ** Component ** + ***************/ + +void Component::construct(Genode::Env &env) +{ + static Main main { env }; +} + + +/********** + ** Main ** + **********/ + +Main::Main(Env &env) : _env { env } +{ + _table_ds.sigh(_table_ds_sigh); + _handle_table_ds(); +} + + +void Main::_handle_table_ds() +{ + _table_ds.update(); + if (!_table_ds.valid()) { + return; + } + _table.construct((addr_t)_table_ds.local_addr(), _table_ds.size(), _verbose); + _reporter.generate([&] (Reporter::Xml_generator &xml) { + _table->report(xml); + }); +} + + +/*********** + ** Table ** + ***********/ + +void Table::_report_string(Reporter::Xml_generator &xml, + char const *type, + char const *value) const +{ + xml.node(type, [&] () { xml.attribute("value", value); }); +} + + +const char *Table::_string_set_item(Smbios_structure const &header, + uint8_t idx) const +{ + if (idx == 0) { + return "[not specified]"; + } + uint8_t const *data { (uint8_t *)&header }; + char const *result = (char *)data + header.length; + while (idx > 1 && *result) { + result += strlen(result); + result++; + idx--; + } + if (!*result) { + return "[bad index]"; + } + return result; +} + + +void Table::_warn(char const *msg) const +{ + if (!_verbose) { + return; + } + warning(msg); +} + + +char const *Table::_system_wake_up_type(uint8_t idx) const +{ + switch (idx) { + case 0: return "Reserved"; + case 1: return "Other"; + case 2: return "Unknown"; + case 3: return "APM Timer"; + case 4: return "Modem Ring"; + case 5: return "LAN Remote"; + case 6: return "Power Switch"; + case 7: return "PCI PME#"; + case 8: return "AC Power Restored"; + default: return "[out of spec]"; + } +} + + +char const *Table::_bios_character_0(uint8_t idx) const +{ + switch (idx) { + case 4: return "ISA is supported"; + case 5: return "MCA is supported"; + case 6: return "EISA is supported"; + case 7: return "PCI is supported"; + case 8: return "PC Card (PCMCIA) is supported"; + case 9: return "PNP is supported"; + case 10: return "APM is supported"; + case 11: return "BIOS is upgradeable"; + case 12: return "BIOS shadowing is allowed"; + case 13: return "VLB is supported"; + case 14: return "ESCD support is available"; + case 15: return "Boot from CD is supported"; + case 16: return "Selectable boot is supported"; + case 17: return "BIOS ROM is socketed"; + case 18: return "Boot from PC Card (PCMCIA) is supported"; + case 19: return "EDD is supported"; + case 20: return "Japanese floppy for NEC 9800 1.2 MB is supported (int 13h)"; + case 21: return "Japanese floppy for Toshiba 1.2 MB is supported (int 13h)"; + case 22: return "5.25"/360 kB floppy services are supported (int 13h)"; + case 23: return "5.25"/1.2 MB floppy services are supported (int 13h)"; + case 24: return "3.5"/720 kB floppy services are supported (int 13h)"; + case 25: return "3.5"/2.88 MB floppy services are supported (int 13h)"; + case 26: return "Print screen service is supported (int 5h)"; + case 27: return "8042 keyboard services are supported (int 9h)"; + case 28: return "Serial services are supported (int 14h)"; + case 29: return "Printer services are supported (int 17h)"; + case 30: return "CGA/mono video services are supported (int 10h)"; + case 31: return "NEC PC-98"; + default: return "[bad index]"; + } +} + + +char const *Table::_bios_character_1(uint8_t idx) const +{ + switch (idx) { + case 0: return "ACPI is supported"; + case 1: return "USB legacy is supported"; + case 2: return "AGP is supported"; + case 3: return "I2O boot is supported"; + case 4: return "LS-120 boot is supported"; + case 5: return "ATAPI Zip drive boot is supported"; + case 6: return "IEEE 1394 boot is supported"; + case 7: return "Smart battery is supported"; + default: return "[bad index]"; + } +} + + +char const *Table::_bios_character_2(uint8_t idx) const +{ + switch (idx) { + case 0: return "BIOS boot specification is supported"; + case 1: return "Function key-initiated network boot is supported"; + case 2: return "Targeted content distribution is supported"; + case 3: return "UEFI is supported"; + case 4: return "System is a virtual machine"; + default: return "[bad index]"; + } +} + + +char const *Table::_base_board_feature(uint8_t idx) const +{ + switch (idx) { + case 0: return "Board is a hosting board"; + case 1: return "Board requires at least one daughter board"; + case 2: return "Board is removable"; + case 3: return "Board is replaceable"; + case 4: return "Board is hot swappable"; + default: return "[bad index]"; + } +} + + +void Table::_report_base_board_features(Reporter::Xml_generator &xml, + uint8_t code) const +{ + if ((code & 0x1f) == 0) { + _report_string(xml, "feature", "[none]"); + return; + } + for (uint8_t idx = 0; idx < 5; idx++) { + if (code & (1 << idx)) { + _report_string(xml, "feature", _base_board_feature(idx)); + } + } +} + + +void Table::_report_bios_character_0(Reporter::Xml_generator &xml, + uint64_t code) const +{ + if (code & (1 << 3)) { + xml.node("characteristic", [&] () { + xml.attribute("value", "BIOS characteristics not supported"); + }); + return; + } + for (uint8_t idx = 4; idx <= 31; idx++) { + if ((code & (1 << idx)) == 0) { + continue; + } + xml.node("characteristic", [&] () { + xml.attribute("value", _bios_character_0(idx)); + }); + } +} + + +void Table::_report_bios_character_1(Reporter::Xml_generator &xml, + uint8_t code) const +{ + for (uint8_t idx = 0; idx <= 7; idx++) { + if ((code & (1 << idx)) == 0) { + continue; + } + xml.node("characteristic", [&] () { + xml.attribute("value", _bios_character_1(idx)); + }); + } +} + + +void Table::_report_bios_character_2(Reporter::Xml_generator &xml, + uint8_t code) const +{ + for (uint8_t idx = 0; idx <= 4; idx++) { + if ((code & (1 << idx)) == 0) { + continue; + } + xml.node("characteristic", [&] () { + xml.attribute("value", _bios_character_2(idx)); + }); + } +} + + +void Table::_report_bios_rom_size(Reporter::Xml_generator &xml, + uint8_t code_1, + uint16_t code_2) const +{ + xml.node("rom-size", [&] () { + if (code_1 != 0xff) { + xml.attribute("value", Size_string(((size_t)code_1 + 1) << 6, " KB")); + return; + } + switch (code_2 >> 14) { + case 0: xml.attribute("value", Size_string(code_2 & 0x3fff, " MB")); return; + case 1: xml.attribute("value", Size_string(code_2 & 0x3fff, " GB")); return; + default: xml.attribute("value", "[bad unit]"); return; + } + }); +} + + +void Table::_report_string_set_item(Reporter::Xml_generator &xml, + Smbios_structure const &header, + char const *type, + unsigned idx) const +{ + uint8_t const *data { (uint8_t *)&header }; + _report_string(xml, type, _string_set_item(header, data[idx])); +} + + +void Table::_report_bios(Reporter::Xml_generator &xml, + Smbios_structure const &header) const +{ + uint8_t const *data { (uint8_t *)&header }; + xml.attribute("description", "BIOS Information"); + if (header.length < 18) { + _warn("SMBIOS BIOS structure has bad length"); + return; + } + _report_string_set_item(xml, header, "vendor", 4); + _report_string_set_item(xml, header, "version", 5); + _report_string_set_item(xml, header, "release-date", 8); + { + addr_t const code { *(uint16_t const *)(data + 6) }; + if (code) { + xml.node("address", [&] () { + xml.attribute("value", Addr_string(Hex(code << 4))); }); + + xml.node("runtime-size", [&] () { + xml.attribute("value", (0x10000 - code) << 4); }); + } + } + { + uint16_t code_2; + if (header.length < 26) { + code_2 = 16; + } else { + code_2 = *(uint16_t const *)(data + 24); + } + _report_bios_rom_size(xml, data[9], code_2); + } + _report_bios_character_0(xml, *(uint64_t const *)(data + 10)); + if (header.length < 0x13) { + return; } + + _report_bios_character_1(xml, data[0x12]); + if (header.length < 0x14) { + return; } + + _report_bios_character_2(xml, data[0x13]); + if (header.length < 0x18) { + return; } + + if (data[20] != 0xff && data[21] != 0xff) { + xml.node("bios-revision", [&] () { + xml.attribute("value", Version_2_string(data[20], ".", data[21])); + }); + } + if (data[22] != 0xff && data[23] != 0xff) { + xml.node("firmware-revision", [&] () { + xml.attribute("value", Version_2_string(data[22], ".", data[23])); + }); + } +} + + +void Table::_report_system_uuid(Reporter::Xml_generator &xml, + uint8_t const *data) const +{ + bool only_zeros { true }; + bool only_ones { true }; + for (unsigned off = 0; off < 16 && (only_zeros || only_ones); off++) + { + if (data[off] != 0x00) { + only_zeros = false; + } + if (data[off] != 0xff) { + only_ones = false; + } + } + + xml.node("uuid", [&] () { + if (only_ones) { + xml.attribute("value", "[not present]"); + return; + } + if (only_zeros) { + xml.attribute("value", "[not settable]"); + return; + } + if ( _version_major > 2 || + (_version_major == 2 && _version_minor >= 6)) + { + xml.attribute("value", Uuid_string( + Uuid_hex(data[3]), Uuid_hex(data[2]), + Uuid_hex(data[1]), Uuid_hex(data[0]), + "-", + Uuid_hex(data[5]), Uuid_hex(data[4]), + "-", + Uuid_hex(data[7]), Uuid_hex(data[6]), + "-", + Uuid_hex(data[8]), Uuid_hex(data[9]), + "-", + Uuid_hex(data[10]), Uuid_hex(data[11]), + Uuid_hex(data[12]), Uuid_hex(data[13]), + Uuid_hex(data[14]), Uuid_hex(data[15]))); + + } else { + xml.attribute("value", Uuid_string( + Uuid_hex(data[0]), Uuid_hex(data[1]), + Uuid_hex(data[2]), Uuid_hex(data[3]), + "-", + Uuid_hex(data[4]), Uuid_hex(data[5]), + "-", + Uuid_hex(data[6]), Uuid_hex(data[7]), + "-", + Uuid_hex(data[8]), Uuid_hex(data[9]), + "-", + Uuid_hex(data[10]), Uuid_hex(data[11]), + Uuid_hex(data[12]), Uuid_hex(data[13]), + Uuid_hex(data[14]), Uuid_hex(data[15]))); + } + }); +} + + +const char *Table::_base_board_type(uint8_t idx) const +{ + switch (idx) { + case 1: return "Unknown"; + case 2: return "Other"; + case 3: return "Server Blade"; + case 4: return "Connectivity Switch"; + case 5: return "System Management Module"; + case 6: return "Processor Module"; + case 7: return "I/O Module"; + case 8: return "Memory Module"; + case 9: return "Daughter Board"; + case 10: return "Motherboard"; + case 11: return "Processor+Memory Module"; + case 12: return "Processor+I/O Module"; + case 13: return "Interconnect Board"; + default: return "[out of spec]"; + } +} + + +void Table::_report_base_board_handles(Reporter::Xml_generator &xml, + uint8_t count, + uint8_t const *data) const +{ + for (uint8_t idx = 0; idx < count; idx++) { + xml.node("contained-object-handle", [&] () { + uint8_t const *value { data + sizeof(uint16_t) * idx }; + xml.attribute("value", Addr_string(*(uint16_t const *)value)); + }); + } +} + + +void Table::_report_base_board(Reporter::Xml_generator &xml, + Smbios_structure const &header) const +{ + xml.attribute("name", "Base Board Information"); + if (header.length < 8) { + return; } + + _report_string_set_item(xml, header, "manufacturer", 4); + _report_string_set_item(xml, header, "product-name", 5); + _report_string_set_item(xml, header, "version", 6); + _report_string_set_item(xml, header, "serial-number", 7); + if (header.length < 9) { + return; } + + _report_string_set_item(xml, header, "asset-tag", 8); + if (header.length < 10) { + return; } + + uint8_t const *data { (uint8_t *)&header }; + _report_base_board_features(xml, data[9]); + if (header.length < 14) { + return; } + + _report_string_set_item(xml, header, "location-in-chassis", 10); + + xml.node("chassis-handle", [&] () { + xml.attribute("value", *(uint16_t const *)(data + 11)); }); + + _report_string(xml, "type", _base_board_type(data[13])); + if (header.length < 15) { + return; } + + if (header.length < 15 + data[14] * sizeof(uint16_t)) { + return; } + + _report_base_board_handles(xml, data[14], data + 15); +} + + +void Table::_report_system(Reporter::Xml_generator &xml, + Smbios_structure const &header) const +{ + uint8_t const *data { (uint8_t *)&header }; + xml.attribute("description", "System Information"); + if (header.length < 8) { + return; } + + _report_string_set_item(xml, header, "manufacturer", 4); + _report_string_set_item(xml, header, "product-name", 5); + _report_string_set_item(xml, header, "version", 6); + _report_string_set_item(xml, header, "serial-number", 7); + if (header.length < 25) { + return; } + + _report_system_uuid(xml, data + 8); + _report_string(xml, "wake-up-type", _system_wake_up_type(data[24])); + if (header.length < 27) { + return; } + + _report_string_set_item(xml, header, "sku-number", 25); + _report_string_set_item(xml, header, "family", 26); +} + + +void Table::_report_smbios_struct(Reporter::Xml_generator &xml, + Smbios_structure const &smbios_struct) const +{ + xml.node("structure", [&] () { + + xml.attribute("type", smbios_struct.type); + xml.attribute("length", smbios_struct.length); + xml.attribute("handle", smbios_struct.handle); + + switch (smbios_struct.type) { + case Smbios_structure::BIOS: _report_bios (xml, smbios_struct); break; + case Smbios_structure::SYSTEM: _report_system (xml, smbios_struct); break; + case Smbios_structure::BASE_BOARD: _report_base_board(xml, smbios_struct); break; + default: _warn("structure type not supported"); break; + } + }); +} + + +void Table::_report_smbios_structs(Reporter::Xml_generator &xml, + Dmi_entry_point const &ep) const +{ + Smbios_structure *smbios_struct { (Smbios_structure *)( + (addr_t)&ep + ep.LENGTH) }; + + for (uint16_t idx = 0; idx < ep.nr_of_structs; idx++) { + + if ((addr_t)smbios_struct + sizeof(*smbios_struct) > _end) { + _warn("SMBIOS structure header exceeds ROM"); + break; + } else if ((addr_t)smbios_struct + smbios_struct->length > _end) { + _warn("SMBIOS structure body exceeds ROM"); + break; + } + _report_smbios_struct(xml, *smbios_struct); + + /* seek next SMBIOS structure */ + bool next_exceeds_rom { false }; + uint8_t const *next { (uint8_t *)( + (addr_t)smbios_struct + smbios_struct->length) }; + + while (1) { + if ((addr_t)next + 2 * sizeof(*next) > _end) { + next_exceeds_rom = true; + break; + } + if (next[0] == 0 && next[1] == 0) { + next += 2; + break; + } + next++; + } + if (next_exceeds_rom) { + _warn("SMBIOS structure string-set exceeds ROM"); + break; + } + smbios_struct = (Smbios_structure *)next; + } +} + + +void Table::_report_smbios_ep(Reporter::Xml_generator &xml, + Smbios_entry_point const &smbios_ep) const +{ + /* fix weird versions reported by some systems */ + uint8_t _version_major = smbios_ep.version_major; + uint8_t _version_minor = smbios_ep.version_minor; + if ((_version_major == 2 && _version_minor == 31) || + (_version_major == 2 && _version_minor == 33)) + { + _warn("fixed weird SMBIOS version"); + _version_major = 2; + _version_minor = 3; + + } else if (_version_major == 2 && _version_minor == 51) { + + _warn("fixed weird SMBIOS version"); + _version_major = 2; + _version_minor = 6; + } + xml.node("smbios", [&] () { + xml.attribute("version", + Version_2_string(_version_major, ".", _version_minor)); + + xml.attribute("structures", smbios_ep.nr_of_structs); + xml.attribute("structures-size", smbios_ep.struct_table_length); + + _report_smbios_structs(xml, smbios_ep.dmi_ep()); + }); +} + + +void Table::_report_dmi_ep(Reporter::Xml_generator &xml, + Dmi_entry_point const &ep) const +{ + /* fix weird versions reported by some systems */ + uint8_t ver_maj { (uint8_t)(ep.bcd_revision >> 4) }; + uint8_t ver_min { (uint8_t)(ep.bcd_revision & 0xf) }; + xml.node("dmi", [&] () { + xml.attribute("version", + Version_2_string(ver_maj, ".", ver_min)); + + xml.attribute("structures", ep.nr_of_structs); + xml.attribute("structures-size", ep.struct_table_length); + + _report_smbios_structs(xml, ep); + }); +} + + +void Table::report(Reporter::Xml_generator &xml) const +{ + + /* check if entry point is valid and of which type it is */ + char const *const anchor_string { (char *)_base }; + if (((addr_t)anchor_string + 5 * sizeof(*anchor_string)) > _end) { + _warn("anchor string of entry point exceeds ROM"); + } else if (String<5>(anchor_string) == "_SM_") { + + /* it's an SMBIOS entry point */ + Smbios_entry_point const &smbios_ep { + *(Smbios_entry_point *)anchor_string }; + + /* check if SMBIOS entry point is valid */ + if ((addr_t)&smbios_ep + sizeof(smbios_ep) > _end) { + _warn("SMBIOS entry point exceeds ROM"); + } else if (!smbios_ep.length_valid()) { + _warn("SMBIOS entry point has bad length"); + } else if (!smbios_ep.checksum_correct()) { + _warn("SMBIOS entry point has bad checksum"); + } else if (String<6>((char const *)&smbios_ep.interm_anchor_string) != + "_DMI_") + { + _warn("SMBIOS entry point has bad intermediate anchor string"); + } else if (!smbios_ep.interm_checksum_correct()) { + _warn("SMBIOS entry point has bad intermediate checksum"); + } else { + + /* report information from SMBIOS entry point */ + _report_smbios_ep(xml, smbios_ep); + } + } else if (String<6>(anchor_string) == "_SM3_") { + _warn("SMBIOS3 entry point found, not supported"); + + } else if (String<6>(anchor_string) == "_DMI_") { + + Dmi_entry_point const &ep { *(Dmi_entry_point *)anchor_string }; + + if (!ep.checksum_correct()) { + warning("DMI entry point has bad checksum"); + } else { + _report_dmi_ep(xml, ep); + } + } else { + _warn("entry point has bad anchor string"); + } +} diff --git a/repos/os/src/app/smbios_decoder/target.mk b/repos/os/src/app/smbios_decoder/target.mk new file mode 100644 index 000000000..10e726a4f --- /dev/null +++ b/repos/os/src/app/smbios_decoder/target.mk @@ -0,0 +1,3 @@ +TARGET = smbios_decoder +SRC_CC = main.cc +LIBS = base diff --git a/tool/autopilot.list b/tool/autopilot.list index e8fdcd336..fcd994e98 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -50,6 +50,7 @@ rump_ext2 sd_card_bench seoul-auto smartcard +smbios_decoder smp solo5 sub_rm