| 1 | // SPDX-License-Identifier: GPL-2.0-only | 
|---|
| 2 | /* | 
|---|
| 3 | * Export SMBIOS/DMI info via sysfs to userspace | 
|---|
| 4 | * | 
|---|
| 5 | * Copyright 2007, Lennart Poettering | 
|---|
| 6 | */ | 
|---|
| 7 |  | 
|---|
| 8 | #include <linux/module.h> | 
|---|
| 9 | #include <linux/kernel.h> | 
|---|
| 10 | #include <linux/init.h> | 
|---|
| 11 | #include <linux/dmi.h> | 
|---|
| 12 | #include <linux/device.h> | 
|---|
| 13 | #include <linux/slab.h> | 
|---|
| 14 |  | 
|---|
| 15 | struct dmi_device_attribute{ | 
|---|
| 16 | struct device_attribute dev_attr; | 
|---|
| 17 | int field; | 
|---|
| 18 | }; | 
|---|
| 19 | #define to_dmi_dev_attr(_dev_attr) \ | 
|---|
| 20 | container_of(_dev_attr, struct dmi_device_attribute, dev_attr) | 
|---|
| 21 |  | 
|---|
| 22 | static ssize_t sys_dmi_field_show(struct device *dev, | 
|---|
| 23 | struct device_attribute *attr, | 
|---|
| 24 | char *page) | 
|---|
| 25 | { | 
|---|
| 26 | int field = to_dmi_dev_attr(attr)->field; | 
|---|
| 27 | ssize_t len; | 
|---|
| 28 | len = scnprintf(buf: page, PAGE_SIZE, fmt: "%s\n", dmi_get_system_info(field)); | 
|---|
| 29 | page[len-1] = '\n'; | 
|---|
| 30 | return len; | 
|---|
| 31 | } | 
|---|
| 32 |  | 
|---|
| 33 | #define DMI_ATTR(_name, _mode, _show, _field)			\ | 
|---|
| 34 | { .dev_attr = __ATTR(_name, _mode, _show, NULL),	\ | 
|---|
| 35 | .field = _field } | 
|---|
| 36 |  | 
|---|
| 37 | #define DEFINE_DMI_ATTR_WITH_SHOW(_name, _mode, _field)		\ | 
|---|
| 38 | static struct dmi_device_attribute sys_dmi_##_name##_attr =	\ | 
|---|
| 39 | DMI_ATTR(_name, _mode, sys_dmi_field_show, _field); | 
|---|
| 40 |  | 
|---|
| 41 | DEFINE_DMI_ATTR_WITH_SHOW(bios_vendor,		0444, DMI_BIOS_VENDOR); | 
|---|
| 42 | DEFINE_DMI_ATTR_WITH_SHOW(bios_version,		0444, DMI_BIOS_VERSION); | 
|---|
| 43 | DEFINE_DMI_ATTR_WITH_SHOW(bios_date,		0444, DMI_BIOS_DATE); | 
|---|
| 44 | DEFINE_DMI_ATTR_WITH_SHOW(sys_vendor,		0444, DMI_SYS_VENDOR); | 
|---|
| 45 | DEFINE_DMI_ATTR_WITH_SHOW(bios_release,		0444, DMI_BIOS_RELEASE); | 
|---|
| 46 | DEFINE_DMI_ATTR_WITH_SHOW(ec_firmware_release,	0444, DMI_EC_FIRMWARE_RELEASE); | 
|---|
| 47 | DEFINE_DMI_ATTR_WITH_SHOW(product_name,		0444, DMI_PRODUCT_NAME); | 
|---|
| 48 | DEFINE_DMI_ATTR_WITH_SHOW(product_version,	0444, DMI_PRODUCT_VERSION); | 
|---|
| 49 | DEFINE_DMI_ATTR_WITH_SHOW(product_serial,	0400, DMI_PRODUCT_SERIAL); | 
|---|
| 50 | DEFINE_DMI_ATTR_WITH_SHOW(product_uuid,		0400, DMI_PRODUCT_UUID); | 
|---|
| 51 | DEFINE_DMI_ATTR_WITH_SHOW(product_sku,		0444, DMI_PRODUCT_SKU); | 
|---|
| 52 | DEFINE_DMI_ATTR_WITH_SHOW(product_family,	0444, DMI_PRODUCT_FAMILY); | 
|---|
| 53 | DEFINE_DMI_ATTR_WITH_SHOW(board_vendor,		0444, DMI_BOARD_VENDOR); | 
|---|
| 54 | DEFINE_DMI_ATTR_WITH_SHOW(board_name,		0444, DMI_BOARD_NAME); | 
|---|
| 55 | DEFINE_DMI_ATTR_WITH_SHOW(board_version,	0444, DMI_BOARD_VERSION); | 
|---|
| 56 | DEFINE_DMI_ATTR_WITH_SHOW(board_serial,		0400, DMI_BOARD_SERIAL); | 
|---|
| 57 | DEFINE_DMI_ATTR_WITH_SHOW(board_asset_tag,	0444, DMI_BOARD_ASSET_TAG); | 
|---|
| 58 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_vendor,	0444, DMI_CHASSIS_VENDOR); | 
|---|
| 59 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_type,		0444, DMI_CHASSIS_TYPE); | 
|---|
| 60 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_version,	0444, DMI_CHASSIS_VERSION); | 
|---|
| 61 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_serial,	0400, DMI_CHASSIS_SERIAL); | 
|---|
| 62 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_asset_tag,	0444, DMI_CHASSIS_ASSET_TAG); | 
|---|
| 63 |  | 
|---|
| 64 | static void ascii_filter(char *d, const char *s) | 
|---|
| 65 | { | 
|---|
| 66 | /* Filter out characters we don't want to see in the modalias string */ | 
|---|
| 67 | for (; *s; s++) | 
|---|
| 68 | if (*s > ' ' && *s < 127 && *s != ':') | 
|---|
| 69 | *(d++) = *s; | 
|---|
| 70 |  | 
|---|
| 71 | *d = 0; | 
|---|
| 72 | } | 
|---|
| 73 |  | 
|---|
| 74 | static ssize_t get_modalias(char *buffer, size_t buffer_size) | 
|---|
| 75 | { | 
|---|
| 76 | /* | 
|---|
| 77 | * Note new fields need to be added at the end to keep compatibility | 
|---|
| 78 | * with udev's hwdb which does matches on "`cat dmi/id/modalias`*". | 
|---|
| 79 | */ | 
|---|
| 80 | static const struct mafield { | 
|---|
| 81 | const char *prefix; | 
|---|
| 82 | int field; | 
|---|
| 83 | } fields[] = { | 
|---|
| 84 | { "bvn", DMI_BIOS_VENDOR }, | 
|---|
| 85 | { .prefix: "bvr", .field: DMI_BIOS_VERSION }, | 
|---|
| 86 | { .prefix: "bd",  .field: DMI_BIOS_DATE }, | 
|---|
| 87 | { .prefix: "br",  .field: DMI_BIOS_RELEASE }, | 
|---|
| 88 | { .prefix: "efr", .field: DMI_EC_FIRMWARE_RELEASE }, | 
|---|
| 89 | { .prefix: "svn", .field: DMI_SYS_VENDOR }, | 
|---|
| 90 | { .prefix: "pn",  .field: DMI_PRODUCT_NAME }, | 
|---|
| 91 | { .prefix: "pvr", .field: DMI_PRODUCT_VERSION }, | 
|---|
| 92 | { .prefix: "rvn", .field: DMI_BOARD_VENDOR }, | 
|---|
| 93 | { .prefix: "rn",  .field: DMI_BOARD_NAME }, | 
|---|
| 94 | { .prefix: "rvr", .field: DMI_BOARD_VERSION }, | 
|---|
| 95 | { .prefix: "cvn", .field: DMI_CHASSIS_VENDOR }, | 
|---|
| 96 | { .prefix: "ct",  .field: DMI_CHASSIS_TYPE }, | 
|---|
| 97 | { .prefix: "cvr", .field: DMI_CHASSIS_VERSION }, | 
|---|
| 98 | { .prefix: "sku", .field: DMI_PRODUCT_SKU }, | 
|---|
| 99 | { NULL,  .field: DMI_NONE } | 
|---|
| 100 | }; | 
|---|
| 101 |  | 
|---|
| 102 | ssize_t l, left; | 
|---|
| 103 | char *p; | 
|---|
| 104 | const struct mafield *f; | 
|---|
| 105 |  | 
|---|
| 106 | strcpy(buffer, "dmi"); | 
|---|
| 107 | p = buffer + 3; left = buffer_size - 4; | 
|---|
| 108 |  | 
|---|
| 109 | for (f = fields; f->prefix && left > 0; f++) { | 
|---|
| 110 | const char *c; | 
|---|
| 111 | char *t; | 
|---|
| 112 |  | 
|---|
| 113 | c = dmi_get_system_info(field: f->field); | 
|---|
| 114 | if (!c) | 
|---|
| 115 | continue; | 
|---|
| 116 |  | 
|---|
| 117 | t = kmalloc(strlen(c) + 1, GFP_KERNEL); | 
|---|
| 118 | if (!t) | 
|---|
| 119 | break; | 
|---|
| 120 | ascii_filter(d: t, s: c); | 
|---|
| 121 | l = scnprintf(buf: p, size: left, fmt: ":%s%s", f->prefix, t); | 
|---|
| 122 | kfree(objp: t); | 
|---|
| 123 |  | 
|---|
| 124 | p += l; | 
|---|
| 125 | left -= l; | 
|---|
| 126 | } | 
|---|
| 127 |  | 
|---|
| 128 | p[0] = ':'; | 
|---|
| 129 | p[1] = 0; | 
|---|
| 130 |  | 
|---|
| 131 | return p - buffer + 1; | 
|---|
| 132 | } | 
|---|
| 133 |  | 
|---|
| 134 | static ssize_t sys_dmi_modalias_show(struct device *dev, | 
|---|
| 135 | struct device_attribute *attr, char *page) | 
|---|
| 136 | { | 
|---|
| 137 | ssize_t r; | 
|---|
| 138 | r = get_modalias(buffer: page, PAGE_SIZE-1); | 
|---|
| 139 | page[r] = '\n'; | 
|---|
| 140 | page[r+1] = 0; | 
|---|
| 141 | return r+1; | 
|---|
| 142 | } | 
|---|
| 143 |  | 
|---|
| 144 | static struct device_attribute sys_dmi_modalias_attr = | 
|---|
| 145 | __ATTR(modalias, 0444, sys_dmi_modalias_show, NULL); | 
|---|
| 146 |  | 
|---|
| 147 | static struct attribute *sys_dmi_attributes[DMI_STRING_MAX+2]; | 
|---|
| 148 |  | 
|---|
| 149 | static struct attribute_group sys_dmi_attribute_group = { | 
|---|
| 150 | .attrs = sys_dmi_attributes, | 
|---|
| 151 | }; | 
|---|
| 152 |  | 
|---|
| 153 | static const struct attribute_group* sys_dmi_attribute_groups[] = { | 
|---|
| 154 | &sys_dmi_attribute_group, | 
|---|
| 155 | NULL | 
|---|
| 156 | }; | 
|---|
| 157 |  | 
|---|
| 158 | static int dmi_dev_uevent(const struct device *dev, struct kobj_uevent_env *env) | 
|---|
| 159 | { | 
|---|
| 160 | ssize_t len; | 
|---|
| 161 |  | 
|---|
| 162 | if (add_uevent_var(env, format: "MODALIAS=")) | 
|---|
| 163 | return -ENOMEM; | 
|---|
| 164 | len = get_modalias(buffer: &env->buf[env->buflen - 1], | 
|---|
| 165 | buffer_size: sizeof(env->buf) - env->buflen); | 
|---|
| 166 | if (len >= (sizeof(env->buf) - env->buflen)) | 
|---|
| 167 | return -ENOMEM; | 
|---|
| 168 | env->buflen += len; | 
|---|
| 169 | return 0; | 
|---|
| 170 | } | 
|---|
| 171 |  | 
|---|
| 172 | static void dmi_dev_release(struct device *dev) | 
|---|
| 173 | { | 
|---|
| 174 | kfree(objp: dev); | 
|---|
| 175 | } | 
|---|
| 176 |  | 
|---|
| 177 | static struct class dmi_class = { | 
|---|
| 178 | .name = "dmi", | 
|---|
| 179 | .dev_release = dmi_dev_release, | 
|---|
| 180 | .dev_uevent = dmi_dev_uevent, | 
|---|
| 181 | }; | 
|---|
| 182 |  | 
|---|
| 183 | static struct device *dmi_dev; | 
|---|
| 184 |  | 
|---|
| 185 | /* Initialization */ | 
|---|
| 186 |  | 
|---|
| 187 | #define ADD_DMI_ATTR(_name, _field) \ | 
|---|
| 188 | if (dmi_get_system_info(_field)) \ | 
|---|
| 189 | sys_dmi_attributes[i++] = &sys_dmi_##_name##_attr.dev_attr.attr; | 
|---|
| 190 |  | 
|---|
| 191 | /* In a separate function to keep gcc 3.2 happy - do NOT merge this in | 
|---|
| 192 | dmi_id_init! */ | 
|---|
| 193 | static void __init dmi_id_init_attr_table(void) | 
|---|
| 194 | { | 
|---|
| 195 | int i; | 
|---|
| 196 |  | 
|---|
| 197 | /* Not necessarily all DMI fields are available on all | 
|---|
| 198 | * systems, hence let's built an attribute table of just | 
|---|
| 199 | * what's available */ | 
|---|
| 200 | i = 0; | 
|---|
| 201 | ADD_DMI_ATTR(bios_vendor,       DMI_BIOS_VENDOR); | 
|---|
| 202 | ADD_DMI_ATTR(bios_version,      DMI_BIOS_VERSION); | 
|---|
| 203 | ADD_DMI_ATTR(bios_date,         DMI_BIOS_DATE); | 
|---|
| 204 | ADD_DMI_ATTR(bios_release,      DMI_BIOS_RELEASE); | 
|---|
| 205 | ADD_DMI_ATTR(ec_firmware_release, DMI_EC_FIRMWARE_RELEASE); | 
|---|
| 206 | ADD_DMI_ATTR(sys_vendor,        DMI_SYS_VENDOR); | 
|---|
| 207 | ADD_DMI_ATTR(product_name,      DMI_PRODUCT_NAME); | 
|---|
| 208 | ADD_DMI_ATTR(product_version,   DMI_PRODUCT_VERSION); | 
|---|
| 209 | ADD_DMI_ATTR(product_serial,    DMI_PRODUCT_SERIAL); | 
|---|
| 210 | ADD_DMI_ATTR(product_uuid,      DMI_PRODUCT_UUID); | 
|---|
| 211 | ADD_DMI_ATTR(product_family,    DMI_PRODUCT_FAMILY); | 
|---|
| 212 | ADD_DMI_ATTR(product_sku,       DMI_PRODUCT_SKU); | 
|---|
| 213 | ADD_DMI_ATTR(board_vendor,      DMI_BOARD_VENDOR); | 
|---|
| 214 | ADD_DMI_ATTR(board_name,        DMI_BOARD_NAME); | 
|---|
| 215 | ADD_DMI_ATTR(board_version,     DMI_BOARD_VERSION); | 
|---|
| 216 | ADD_DMI_ATTR(board_serial,      DMI_BOARD_SERIAL); | 
|---|
| 217 | ADD_DMI_ATTR(board_asset_tag,   DMI_BOARD_ASSET_TAG); | 
|---|
| 218 | ADD_DMI_ATTR(chassis_vendor,    DMI_CHASSIS_VENDOR); | 
|---|
| 219 | ADD_DMI_ATTR(chassis_type,      DMI_CHASSIS_TYPE); | 
|---|
| 220 | ADD_DMI_ATTR(chassis_version,   DMI_CHASSIS_VERSION); | 
|---|
| 221 | ADD_DMI_ATTR(chassis_serial,    DMI_CHASSIS_SERIAL); | 
|---|
| 222 | ADD_DMI_ATTR(chassis_asset_tag, DMI_CHASSIS_ASSET_TAG); | 
|---|
| 223 | sys_dmi_attributes[i++] = &sys_dmi_modalias_attr.attr; | 
|---|
| 224 | } | 
|---|
| 225 |  | 
|---|
| 226 | static int __init dmi_id_init(void) | 
|---|
| 227 | { | 
|---|
| 228 | int ret; | 
|---|
| 229 |  | 
|---|
| 230 | if (!dmi_available) | 
|---|
| 231 | return -ENODEV; | 
|---|
| 232 |  | 
|---|
| 233 | dmi_id_init_attr_table(); | 
|---|
| 234 |  | 
|---|
| 235 | ret = class_register(class: &dmi_class); | 
|---|
| 236 | if (ret) | 
|---|
| 237 | return ret; | 
|---|
| 238 |  | 
|---|
| 239 | dmi_dev = kzalloc(sizeof(*dmi_dev), GFP_KERNEL); | 
|---|
| 240 | if (!dmi_dev) { | 
|---|
| 241 | ret = -ENOMEM; | 
|---|
| 242 | goto fail_class_unregister; | 
|---|
| 243 | } | 
|---|
| 244 |  | 
|---|
| 245 | dmi_dev->class = &dmi_class; | 
|---|
| 246 | dev_set_name(dev: dmi_dev, name: "id"); | 
|---|
| 247 | dmi_dev->groups = sys_dmi_attribute_groups; | 
|---|
| 248 |  | 
|---|
| 249 | ret = device_register(dev: dmi_dev); | 
|---|
| 250 | if (ret) | 
|---|
| 251 | goto fail_put_dmi_dev; | 
|---|
| 252 |  | 
|---|
| 253 | return 0; | 
|---|
| 254 |  | 
|---|
| 255 | fail_put_dmi_dev: | 
|---|
| 256 | put_device(dev: dmi_dev); | 
|---|
| 257 |  | 
|---|
| 258 | fail_class_unregister: | 
|---|
| 259 | class_unregister(class: &dmi_class); | 
|---|
| 260 |  | 
|---|
| 261 | return ret; | 
|---|
| 262 | } | 
|---|
| 263 |  | 
|---|
| 264 | arch_initcall(dmi_id_init); | 
|---|
| 265 |  | 
|---|