// SPDX-License-Identifier: GPL-2.0-only #define pr_fmt(fmt) "DMI: " fmt #include <linux/types.h>#include <linux/string.h>#include <linux/init.h>#include <linux/module.h>#include <linux/ctype.h>#include <linux/dmi.h>#include <linux/efi.h>#include <linux/memblock.h>#include <linux/random.h>#include <asm/dmi.h>#include <linux/unaligned.h> #ifndef SMBIOS_ENTRY_POINT_SCAN_START#define SMBIOS_ENTRY_POINT_SCAN_START 0xF0000#endif struct kobject *dmi_kobj;EXPORT_SYMBOL_GPL(dmi_kobj); /* * DMI stands for "Desktop Management Interface". It is part * of and an antecedent to, SMBIOS, which stands for System * Management BIOS. See further: https://www.dmtf.org/standards */static const char dmi_empty_string[] = ""; static u32 dmi_ver __initdata;static u32 dmi_len;static u16 dmi_num;static u8 smbios_entry_point[32];static int smbios_entry_point_size; /* DMI system identification string used during boot */static char dmi_ids_string[128] __initdata; static struct dmi_memdev_info { const char *device; const char *bank; u64 size; /* bytes */ u16 handle; u8 type; /* DDR2, DDR3, DDR4 etc */} *dmi_memdev;static int dmi_memdev_nr;static int dmi_memdev_populated_nr __initdata; const char *dmi_string_nosave(const struct dmi_header *dm, u8 s){ const u8 *bp = ((u8 *) dm) + dm->length; const u8 *nsp; if (s) { while (--s > 0 && *bp) bp += strlen(bp) + 1; /* Strings containing only spaces are considered empty */ nsp = bp; while (*nsp == ' ') nsp++; if (*nsp != '\0') return bp; } return dmi_empty_string;}EXPORT_SYMBOL_GPL(dmi_string_nosave); static const char * __init dmi_string(const struct dmi_header *dm, u8 s){ const char *bp = dmi_string_nosave(dm, s); char *str; size_t len; if (bp == dmi_empty_string) return dmi_empty_string; len = strlen(bp) + 1; str = dmi_alloc(len); if (str != NULL) strcpy(str, bp); return str;} /* * We have to be cautious here. We have seen BIOSes with DMI pointers * pointing to completely the wrong place for example */static void dmi_decode_table(u8 *buf, void (*decode)(const struct dmi_header *, void *), void *private_data){ u8 *data = buf; int i = 0; /* * Stop when we have seen all the items the table claimed to have * (SMBIOS < 3.0 only) OR we reach an end-of-table marker (SMBIOS * >= 3.0 only) OR we run off the end of the table (should never * happen but sometimes does on bogus implementations.) */ while ((!dmi_num || i < dmi_num) && (data - buf + sizeof(struct dmi_header)) <= dmi_len) { const struct dmi_header *dm = (const struct dmi_header *)data; /* * If a short entry is found (less than 4 bytes), not only it * is invalid, but we cannot reliably locate the next entry. */ if (dm->length < sizeof(struct dmi_header)) { pr_warn(FW_BUG "Corrupted DMI table, offset %zd (only %d entries processed)\n", data - buf, i); break; } /* * We want to know the total length (formatted area and * strings) before decoding to make sure we won't run off the * table in dmi_decode or dmi_string */ data += dm->length; while ((data - buf < dmi_len - 1) && (data[0] || data[1])) data++; if (data - buf < dmi_len - 1) decode(dm, private_data); data += 2; i++; /* * 7.45 End-of-Table (Type 127) [SMBIOS reference spec v3.0.0] * For tables behind a 64-bit entry point, we have no item * count and no exact table length, so stop on end-of-table * marker. For tables behind a 32-bit entry point, we have * seen OEM structures behind the end-of-table marker on * some systems, so don't trust it. */ if (!dmi_num && dm->type == DMI_ENTRY_END_OF_TABLE) break; } /* Trim DMI table length if needed */ if (dmi_len > data - buf) dmi_len = data - buf;} static phys_addr_t dmi_base; static int __init dmi_walk_early(void (*decode)(const struct dmi_header *, void *)){ u8 *buf; u32 orig_dmi_len = dmi_len; buf = dmi_early_remap(dmi_base, orig_dmi_len); if (buf == NULL) return -ENOMEM; dmi_decode_table(buf, decode, NULL); add_device_randomness(buf, dmi_len); dmi_early_unmap(buf, orig_dmi_len); return 0;} static int __init dmi_checksum(const u8 *buf, u8 len){ u8 sum = 0; int a; for (a = 0; a < len; a++) sum += buf[a]; return sum == 0;} static const char *dmi_ident[DMI_STRING_MAX];static LIST_HEAD(dmi_devices);int dmi_available;EXPORT_SYMBOL_GPL(dmi_available); /* * Save a DMI string */static void __init dmi_save_ident(const struct dmi_header *dm, int slot, int string){ const char *d = (const char *) dm; const char *p; if (dmi_ident[slot] || dm->length <= string) return; p = dmi_string(dm, d[string]); if (p == NULL) return; dmi_ident[slot] = p;} static void __init dmi_save_release(const struct dmi_header *dm, int slot, int index){ const u8 *minor, *major; char *s; /* If the table doesn't have the field, let's return */ if (dmi_ident[slot] || dm->length < index) return; minor = (u8 *) dm + index; major = (u8 *) dm + index - 1; /* As per the spec, if the system doesn't support this field, * the value is FF */ if (*major == 0xFF && *minor == 0xFF) return; s = dmi_alloc(8); if (!s) return; sprintf(s, "%u.%u", *major, *minor); dmi_ident[slot] = s;} static void __init dmi_save_uuid(const struct dmi_header *dm, int slot, int index){ const u8 *d; char *s; int is_ff = 1, is_00 = 1, i; if (dmi_ident[slot] || dm->length < index + 16) return; d = (u8 *) dm + index; for (i = 0; i < 16 && (is_ff || is_00); i++) { if (d[i] != 0x00) is_00 = 0; if (d[i] != 0xFF) is_ff = 0; } if (is_ff || is_00) return; s = dmi_alloc(16*2+4+1); if (!s) return; /* * As of version 2.6 of the SMBIOS specification, the first 3 fields of * the UUID are supposed to be little-endian encoded. The specification * says that this is the defacto standard. */ if (dmi_ver >= 0x020600) sprintf(s, "%pUl", d); else sprintf(s, "%pUb", d); dmi_ident[slot] = s;} static void __init dmi_save_type(const struct dmi_header *dm, int slot, int index){ const u8 *d; char *s; if (dmi_ident[slot] || dm->length <= index) return; s = dmi_alloc(4); if (!s) return; d = (u8 *) dm + index; sprintf(s, "%u", *d & 0x7F); dmi_ident[slot] = s;} static void __init dmi_save_one_device(int type, const char *name){ struct dmi_device *dev; /* No duplicate device */ if (dmi_find_device(type, name, NULL)) return; dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1); if (!dev) return; dev->type = type; strcpy((char *)(dev + 1), name); dev->name = (char *)(dev + 1); dev->device_data = NULL; list_add(&dev->list, &dmi_devices);} static void __init dmi_save_devices(const struct dmi_header *dm){ int i, count = (dm->length - sizeof(struct dmi_header)) / 2; for (i = 0; i < count; i++) { const char *d = (char *)(dm + 1) + (i * 2); /* Skip disabled device */ if ((*d & 0x80) == 0) continue; dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1))); }} static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm){ int i, count; struct dmi_device *dev; if (dm->length < 0x05) return; count = *(u8 *)(dm + 1); for (i = 1; i <= count; i++) { const char *devname = dmi_string(dm, i); if (devname == dmi_empty_string) continue; dev = dmi_alloc(sizeof(*dev)); if (!dev) break; dev->type = DMI_DEV_TYPE_OEM_STRING; dev->name = devname; dev->device_data = NULL; list_add(&dev->list, &dmi_devices); }} static void __init dmi_save_ipmi_device(const struct dmi_header *dm){ struct dmi_device *dev; void *data; data = dmi_alloc(dm->length); if (data == NULL) return; memcpy(data, dm, dm->length); dev = dmi_alloc(sizeof(*dev)); if (!dev) return; dev->type = DMI_DEV_TYPE_IPMI; dev->name = "IPMI controller"; dev->device_data = data; list_add_tail(&dev->list, &dmi_devices);} static void __init dmi_save_dev_pciaddr(int instance, int segment, int bus, int devfn, const char *name, int type){ struct dmi_dev_onboard *dev; /* Ignore invalid values */ if (type == DMI_DEV_TYPE_DEV_SLOT && segment == 0xFFFF && bus == 0xFF && devfn == 0xFF) return; dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1); if (!dev) return; dev->instance = instance; dev->segment = segment; dev->bus = bus; dev->devfn = devfn; strcpy((char *)&dev[1], name); dev->dev.type = type; dev->dev.name = (char *)&dev[1]; dev->dev.device_data = dev; list_add(&dev->dev.list, &dmi_devices);} static void __init dmi_save_extended_devices(const struct dmi_header *dm){ const char *name; const u8 *d = (u8 *)dm; if (dm->length < 0x0B) return; /* Skip disabled device */ if ((d[0x5] & 0x80) == 0) return; name = dmi_string_nosave(dm, d[0x4]); dmi_save_dev_pciaddr(d[0x6], *(u16 *)(d + 0x7), d[0x9], d[0xA], name, DMI_DEV_TYPE_DEV_ONBOARD); dmi_save_one_device(d[0x5] & 0x7f, name);} static void __init dmi_save_system_slot(const struct dmi_header *dm){ const u8 *d = (u8 *)dm; /* Need SMBIOS 2.6+ structure */ if (dm->length < 0x11) return; dmi_save_dev_pciaddr(*(u16 *)(d + 0x9), *(u16 *)(d + 0xD), d[0xF], d[0x10], dmi_string_nosave(dm, d[0x4]), DMI_DEV_TYPE_DEV_SLOT);} static void __init count_mem_devices(const struct dmi_header *dm, void *v){ if (dm->type != DMI_ENTRY_MEM_DEVICE) return; dmi_memdev_nr++;} static void __init save_mem_devices(const struct dmi_header *dm, void *v){ const char *d = (const char *)dm; static int nr; u64 bytes; u16 size; if (dm->type != DMI_ENTRY_MEM_DEVICE || dm->length < 0x13) return; if (nr >= dmi_memdev_nr) { pr_warn(FW_BUG "Too many DIMM entries in SMBIOS table\n"); return; } dmi_memdev[nr].handle = get_unaligned(&dm->handle); dmi_memdev[nr].device = dmi_string(dm, d[0x10]); dmi_memdev[nr].bank = dmi_string(dm, d[0x11]); dmi_memdev[nr].type = d[0x12]; size = get_unaligned((u16 *)&d[0xC]); if (size == 0) bytes = 0; else if (size == 0xffff) bytes = ~0ull; else if (size & 0x8000) bytes = (u64)(size & 0x7fff) << 10; else if (size != 0x7fff || dm->length < 0x20) bytes = (u64)size << 20; else bytes = (u64)get_unaligned((u32 *)&d[0x1C]) << 20; if (bytes) dmi_memdev_populated_nr++; dmi_memdev[nr].size = bytes; nr++;} static void __init dmi_memdev_walk(void){ if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) { dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr); if (dmi_memdev) dmi_walk_early(save_mem_devices); }} /* * Process a DMI table entry. Right now all we care about are the BIOS * and machine entries. For 2.5 we should pull the smbus controller info * out of here. */static void __init dmi_decode(const struct dmi_header *dm, void *dummy){ switch (dm->type) { case DMI_ENTRY_BIOS: dmi_save_ident(dm, DMI_BIOS_VENDOR, 4); dmi_save_ident(dm, DMI_BIOS_VERSION, 5); dmi_save_ident(dm, DMI_BIOS_DATE, 8); dmi_save_release(dm, DMI_BIOS_RELEASE, 21); dmi_save_release(dm, DMI_EC_FIRMWARE_RELEASE, 23); break; case DMI_ENTRY_SYSTEM: dmi_save_ident(dm, DMI_SYS_VENDOR, 4); dmi_save_ident(dm, DMI_PRODUCT_NAME, 5);Lines 1–500 of 1223. Load more
// SPDX-License-Identifier: GPL-2.0-only #define pr_fmt(fmt) "DMI: " fmt #include <linux/types.h> #include <linux/string.h> #include <linux/init.h> #include <linux/module.h> #include <linux/ctype.h> #include <linux/dmi.h> #include <linux/efi.h> #include <linux/memblock.h> #include <linux/random.h> #include <asm/dmi.h> #include <linux/unaligned.h> #ifndef SMBIOS_ENTRY_POINT_SCAN_START #define SMBIOS_ENTRY_POINT_SCAN_START 0xF0000 #endif struct kobject *dmi_kobj; EXPORT_SYMBOL_GPL(dmi_kobj); /* * DMI stands for "Desktop Management Interface". It is part * of and an antecedent to, SMBIOS, which stands for System * Management BIOS. See further: https://www.dmtf.org/standards */ static const char dmi_empty_string[] = ""; static u32 dmi_ver __initdata; static u32 dmi_len; static u16 dmi_num; static u8 smbios_entry_point[32]; static int smbios_entry_point_size; /* DMI system identification string used during boot */ static char dmi_ids_string[128] __initdata; static struct dmi_memdev_info { const char *device; const char *bank; u64 size; /* bytes */ u16 handle; u8 type; /* DDR2, DDR3, DDR4 etc */ } *dmi_memdev; static int dmi_memdev_nr; static int dmi_memdev_populated_nr __initdata; const char *dmi_string_nosave(const struct dmi_header *dm, u8 s) { const u8 *bp = ((u8 *) dm) + dm->length; const u8 *nsp; if (s) { while (--s > 0 && *bp) bp += strlen(bp) + 1; /* Strings containing only spaces are considered empty */ nsp = bp; while (*nsp == ' ') nsp++; if (*nsp != '\0') return bp; } return dmi_empty_string; } EXPORT_SYMBOL_GPL(dmi_string_nosave); static const char * __init dmi_string(const struct dmi_header *dm, u8 s) { const char *bp = dmi_string_nosave(dm, s); char *str; size_t len; if (bp == dmi_empty_string) return dmi_empty_string; len = strlen(bp) + 1; str = dmi_alloc(len); if (str != NULL) strcpy(str, bp); return str; } /* * We have to be cautious here. We have seen BIOSes with DMI pointers * pointing to completely the wrong place for example */ static void dmi_decode_table(u8 *buf, void (*decode)(const struct dmi_header *, void *), void *private_data) { u8 *data = buf; int i = 0; /* * Stop when we have seen all the items the table claimed to have * (SMBIOS < 3.0 only) OR we reach an end-of-table marker (SMBIOS * >= 3.0 only) OR we run off the end of the table (should never * happen but sometimes does on bogus implementations.) */ while ((!dmi_num || i < dmi_num) && (data - buf + sizeof(struct dmi_header)) <= dmi_len) { const struct dmi_header *dm = (const struct dmi_header *)data; /* * If a short entry is found (less than 4 bytes), not only it * is invalid, but we cannot reliably locate the next entry. */ if (dm->length < sizeof(struct dmi_header)) { pr_warn(FW_BUG "Corrupted DMI table, offset %zd (only %d entries processed)\n", data - buf, i); break; } /* * We want to know the total length (formatted area and * strings) before decoding to make sure we won't run off the * table in dmi_decode or dmi_string */ data += dm->length; while ((data - buf < dmi_len - 1) && (data[0] || data[1])) data++; if (data - buf < dmi_len - 1) decode(dm, private_data); data += 2; i++; /* * 7.45 End-of-Table (Type 127) [SMBIOS reference spec v3.0.0] * For tables behind a 64-bit entry point, we have no item * count and no exact table length, so stop on end-of-table * marker. For tables behind a 32-bit entry point, we have * seen OEM structures behind the end-of-table marker on * some systems, so don't trust it. */ if (!dmi_num && dm->type == DMI_ENTRY_END_OF_TABLE) break; } /* Trim DMI table length if needed */ if (dmi_len > data - buf) dmi_len = data - buf; } static phys_addr_t dmi_base; static int __init dmi_walk_early(void (*decode)(const struct dmi_header *, void *)) { u8 *buf; u32 orig_dmi_len = dmi_len; buf = dmi_early_remap(dmi_base, orig_dmi_len); if (buf == NULL) return -ENOMEM; dmi_decode_table(buf, decode, NULL); add_device_randomness(buf, dmi_len); dmi_early_unmap(buf, orig_dmi_len); return 0; } static int __init dmi_checksum(const u8 *buf, u8 len) { u8 sum = 0; int a; for (a = 0; a < len; a++) sum += buf[a]; return sum == 0; } static const char *dmi_ident[DMI_STRING_MAX]; static LIST_HEAD(dmi_devices); int dmi_available; EXPORT_SYMBOL_GPL(dmi_available); /* * Save a DMI string */ static void __init dmi_save_ident(const struct dmi_header *dm, int slot, int string) { const char *d = (const char *) dm; const char *p; if (dmi_ident[slot] || dm->length <= string) return; p = dmi_string(dm, d[string]); if (p == NULL) return; dmi_ident[slot] = p; } static void __init dmi_save_release(const struct dmi_header *dm, int slot, int index) { const u8 *minor, *major; char *s; /* If the table doesn't have the field, let's return */ if (dmi_ident[slot] || dm->length < index) return; minor = (u8 *) dm + index; major = (u8 *) dm + index - 1; /* As per the spec, if the system doesn't support this field, * the value is FF */ if (*major == 0xFF && *minor == 0xFF) return; s = dmi_alloc(8); if (!s) return; sprintf(s, "%u.%u", *major, *minor); dmi_ident[slot] = s; } static void __init dmi_save_uuid(const struct dmi_header *dm, int slot, int index) { const u8 *d; char *s; int is_ff = 1, is_00 = 1, i; if (dmi_ident[slot] || dm->length < index + 16) return; d = (u8 *) dm + index; for (i = 0; i < 16 && (is_ff || is_00); i++) { if (d[i] != 0x00) is_00 = 0; if (d[i] != 0xFF) is_ff = 0; } if (is_ff || is_00) return; s = dmi_alloc(16*2+4+1); if (!s) return; /* * As of version 2.6 of the SMBIOS specification, the first 3 fields of * the UUID are supposed to be little-endian encoded. The specification * says that this is the defacto standard. */ if (dmi_ver >= 0x020600) sprintf(s, "%pUl", d); else sprintf(s, "%pUb", d); dmi_ident[slot] = s; } static void __init dmi_save_type(const struct dmi_header *dm, int slot, int index) { const u8 *d; char *s; if (dmi_ident[slot] || dm->length <= index) return; s = dmi_alloc(4); if (!s) return; d = (u8 *) dm + index; sprintf(s, "%u", *d & 0x7F); dmi_ident[slot] = s; } static void __init dmi_save_one_device(int type, const char *name) { struct dmi_device *dev; /* No duplicate device */ if (dmi_find_device(type, name, NULL)) return; dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1); if (!dev) return; dev->type = type; strcpy((char *)(dev + 1), name); dev->name = (char *)(dev + 1); dev->device_data = NULL; list_add(&dev->list, &dmi_devices); } static void __init dmi_save_devices(const struct dmi_header *dm) { int i, count = (dm->length - sizeof(struct dmi_header)) / 2; for (i = 0; i < count; i++) { const char *d = (char *)(dm + 1) + (i * 2); /* Skip disabled device */ if ((*d & 0x80) == 0) continue; dmi_save_one_device(*d & 0x7f, dmi_string_nosave(dm, *(d + 1))); } } static void __init dmi_save_oem_strings_devices(const struct dmi_header *dm) { int i, count; struct dmi_device *dev; if (dm->length < 0x05) return; count = *(u8 *)(dm + 1); for (i = 1; i <= count; i++) { const char *devname = dmi_string(dm, i); if (devname == dmi_empty_string) continue; dev = dmi_alloc(sizeof(*dev)); if (!dev) break; dev->type = DMI_DEV_TYPE_OEM_STRING; dev->name = devname; dev->device_data = NULL; list_add(&dev->list, &dmi_devices); } } static void __init dmi_save_ipmi_device(const struct dmi_header *dm) { struct dmi_device *dev; void *data; data = dmi_alloc(dm->length); if (data == NULL) return; memcpy(data, dm, dm->length); dev = dmi_alloc(sizeof(*dev)); if (!dev) return; dev->type = DMI_DEV_TYPE_IPMI; dev->name = "IPMI controller"; dev->device_data = data; list_add_tail(&dev->list, &dmi_devices); } static void __init dmi_save_dev_pciaddr(int instance, int segment, int bus, int devfn, const char *name, int type) { struct dmi_dev_onboard *dev; /* Ignore invalid values */ if (type == DMI_DEV_TYPE_DEV_SLOT && segment == 0xFFFF && bus == 0xFF && devfn == 0xFF) return; dev = dmi_alloc(sizeof(*dev) + strlen(name) + 1); if (!dev) return; dev->instance = instance; dev->segment = segment; dev->bus = bus; dev->devfn = devfn; strcpy((char *)&dev[1], name); dev->dev.type = type; dev->dev.name = (char *)&dev[1]; dev->dev.device_data = dev; list_add(&dev->dev.list, &dmi_devices); } static void __init dmi_save_extended_devices(const struct dmi_header *dm) { const char *name; const u8 *d = (u8 *)dm; if (dm->length < 0x0B) return; /* Skip disabled device */ if ((d[0x5] & 0x80) == 0) return; name = dmi_string_nosave(dm, d[0x4]); dmi_save_dev_pciaddr(d[0x6], *(u16 *)(d + 0x7), d[0x9], d[0xA], name, DMI_DEV_TYPE_DEV_ONBOARD); dmi_save_one_device(d[0x5] & 0x7f, name); } static void __init dmi_save_system_slot(const struct dmi_header *dm) { const u8 *d = (u8 *)dm; /* Need SMBIOS 2.6+ structure */ if (dm->length < 0x11) return; dmi_save_dev_pciaddr(*(u16 *)(d + 0x9), *(u16 *)(d + 0xD), d[0xF], d[0x10], dmi_string_nosave(dm, d[0x4]), DMI_DEV_TYPE_DEV_SLOT); } static void __init count_mem_devices(const struct dmi_header *dm, void *v) { if (dm->type != DMI_ENTRY_MEM_DEVICE) return; dmi_memdev_nr++; } static void __init save_mem_devices(const struct dmi_header *dm, void *v) { const char *d = (const char *)dm; static int nr; u64 bytes; u16 size; if (dm->type != DMI_ENTRY_MEM_DEVICE || dm->length < 0x13) return; if (nr >= dmi_memdev_nr) { pr_warn(FW_BUG "Too many DIMM entries in SMBIOS table\n"); return; } dmi_memdev[nr].handle = get_unaligned(&dm->handle); dmi_memdev[nr].device = dmi_string(dm, d[0x10]); dmi_memdev[nr].bank = dmi_string(dm, d[0x11]); dmi_memdev[nr].type = d[0x12]; size = get_unaligned((u16 *)&d[0xC]); if (size == 0) bytes = 0; else if (size == 0xffff) bytes = ~0ull; else if (size & 0x8000) bytes = (u64)(size & 0x7fff) << 10; else if (size != 0x7fff || dm->length < 0x20) bytes = (u64)size << 20; else bytes = (u64)get_unaligned((u32 *)&d[0x1C]) << 20; if (bytes) dmi_memdev_populated_nr++; dmi_memdev[nr].size = bytes; nr++; } static void __init dmi_memdev_walk(void) { if (dmi_walk_early(count_mem_devices) == 0 && dmi_memdev_nr) { dmi_memdev = dmi_alloc(sizeof(*dmi_memdev) * dmi_memdev_nr); if (dmi_memdev) dmi_walk_early(save_mem_devices); } } /* * Process a DMI table entry. Right now all we care about are the BIOS * and machine entries. For 2.5 we should pull the smbus controller info * out of here. */ static void __init dmi_decode(const struct dmi_header *dm, void *dummy) { switch (dm->type) { case DMI_ENTRY_BIOS: dmi_save_ident(dm, DMI_BIOS_VENDOR, 4); dmi_save_ident(dm, DMI_BIOS_VERSION, 5); dmi_save_ident(dm, DMI_BIOS_DATE, 8); dmi_save_release(dm, DMI_BIOS_RELEASE, 21); dmi_save_release(dm, DMI_EC_FIRMWARE_RELEASE, 23); break; case DMI_ENTRY_SYSTEM: dmi_save_ident(dm, DMI_SYS_VENDOR, 4); dmi_save_ident(dm, DMI_PRODUCT_NAME, 5);