xref: /linux/drivers/firmware/google/vpd.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1d9523678SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2049a59dbSWei-Ning Huang /*
3049a59dbSWei-Ning Huang  * vpd.c
4049a59dbSWei-Ning Huang  *
5049a59dbSWei-Ning Huang  * Driver for exporting VPD content to sysfs.
6049a59dbSWei-Ning Huang  *
7049a59dbSWei-Ning Huang  * Copyright 2017 Google Inc.
8049a59dbSWei-Ning Huang  */
9049a59dbSWei-Ning Huang 
10049a59dbSWei-Ning Huang #include <linux/ctype.h>
11049a59dbSWei-Ning Huang #include <linux/init.h>
12049a59dbSWei-Ning Huang #include <linux/io.h>
13049a59dbSWei-Ning Huang #include <linux/kernel.h>
14049a59dbSWei-Ning Huang #include <linux/kobject.h>
15049a59dbSWei-Ning Huang #include <linux/list.h>
16049a59dbSWei-Ning Huang #include <linux/module.h>
1746505c80SDmitry Torokhov #include <linux/of_address.h>
1846505c80SDmitry Torokhov #include <linux/platform_device.h>
19049a59dbSWei-Ning Huang #include <linux/slab.h>
20049a59dbSWei-Ning Huang #include <linux/sysfs.h>
21049a59dbSWei-Ning Huang 
22049a59dbSWei-Ning Huang #include "coreboot_table.h"
23049a59dbSWei-Ning Huang #include "vpd_decode.h"
24049a59dbSWei-Ning Huang 
25049a59dbSWei-Ning Huang #define CB_TAG_VPD      0x2c
26049a59dbSWei-Ning Huang #define VPD_CBMEM_MAGIC 0x43524f53
27049a59dbSWei-Ning Huang 
28049a59dbSWei-Ning Huang static struct kobject *vpd_kobj;
29049a59dbSWei-Ning Huang 
30049a59dbSWei-Ning Huang struct vpd_cbmem {
31049a59dbSWei-Ning Huang 	u32 magic;
32049a59dbSWei-Ning Huang 	u32 version;
33049a59dbSWei-Ning Huang 	u32 ro_size;
34049a59dbSWei-Ning Huang 	u32 rw_size;
35aa125f31SGustavo A. R. Silva 	u8  blob[];
36049a59dbSWei-Ning Huang };
37049a59dbSWei-Ning Huang 
38049a59dbSWei-Ning Huang struct vpd_section {
39049a59dbSWei-Ning Huang 	bool enabled;
40049a59dbSWei-Ning Huang 	const char *name;
41049a59dbSWei-Ning Huang 	char *raw_name;                /* the string name_raw */
42049a59dbSWei-Ning Huang 	struct kobject *kobj;          /* vpd/name directory */
43049a59dbSWei-Ning Huang 	char *baseaddr;
44049a59dbSWei-Ning Huang 	struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
45049a59dbSWei-Ning Huang 	struct list_head attribs;      /* key/value in vpd_attrib_info list */
46049a59dbSWei-Ning Huang };
47049a59dbSWei-Ning Huang 
48049a59dbSWei-Ning Huang struct vpd_attrib_info {
49049a59dbSWei-Ning Huang 	char *key;
50049a59dbSWei-Ning Huang 	const char *value;
51049a59dbSWei-Ning Huang 	struct bin_attribute bin_attr;
52049a59dbSWei-Ning Huang 	struct list_head list;
53049a59dbSWei-Ning Huang };
54049a59dbSWei-Ning Huang 
55049a59dbSWei-Ning Huang static struct vpd_section ro_vpd;
56049a59dbSWei-Ning Huang static struct vpd_section rw_vpd;
57049a59dbSWei-Ning Huang 
vpd_attrib_read(struct file * filp,struct kobject * kobp,struct bin_attribute * bin_attr,char * buf,loff_t pos,size_t count)58049a59dbSWei-Ning Huang static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
59049a59dbSWei-Ning Huang 			       struct bin_attribute *bin_attr, char *buf,
60049a59dbSWei-Ning Huang 			       loff_t pos, size_t count)
61049a59dbSWei-Ning Huang {
62049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info = bin_attr->private;
63049a59dbSWei-Ning Huang 
64049a59dbSWei-Ning Huang 	return memory_read_from_buffer(buf, count, &pos, info->value,
65049a59dbSWei-Ning Huang 				       info->bin_attr.size);
66049a59dbSWei-Ning Huang }
67049a59dbSWei-Ning Huang 
68049a59dbSWei-Ning Huang /*
69049a59dbSWei-Ning Huang  * vpd_section_check_key_name()
70049a59dbSWei-Ning Huang  *
71049a59dbSWei-Ning Huang  * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
72049a59dbSWei-Ning Huang  * old firmware versions may have entries like "S/N" which are problematic when
73049a59dbSWei-Ning Huang  * exporting them as sysfs attributes. These keys present in old firmwares are
74049a59dbSWei-Ning Huang  * ignored.
75049a59dbSWei-Ning Huang  *
76049a59dbSWei-Ning Huang  * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
77049a59dbSWei-Ning Huang  *
78049a59dbSWei-Ning Huang  * @key: The key name to check
79049a59dbSWei-Ning Huang  * @key_len: key name length
80049a59dbSWei-Ning Huang  */
vpd_section_check_key_name(const u8 * key,s32 key_len)81049a59dbSWei-Ning Huang static int vpd_section_check_key_name(const u8 *key, s32 key_len)
82049a59dbSWei-Ning Huang {
83049a59dbSWei-Ning Huang 	int c;
84049a59dbSWei-Ning Huang 
85049a59dbSWei-Ning Huang 	while (key_len-- > 0) {
86049a59dbSWei-Ning Huang 		c = *key++;
87049a59dbSWei-Ning Huang 
88049a59dbSWei-Ning Huang 		if (!isalnum(c) && c != '_')
89049a59dbSWei-Ning Huang 			return VPD_FAIL;
90049a59dbSWei-Ning Huang 	}
91049a59dbSWei-Ning Huang 
92049a59dbSWei-Ning Huang 	return VPD_OK;
93049a59dbSWei-Ning Huang }
94049a59dbSWei-Ning Huang 
vpd_section_attrib_add(const u8 * key,u32 key_len,const u8 * value,u32 value_len,void * arg)954b708b7bSHung-Te Lin static int vpd_section_attrib_add(const u8 *key, u32 key_len,
964b708b7bSHung-Te Lin 				  const u8 *value, u32 value_len,
97049a59dbSWei-Ning Huang 				  void *arg)
98049a59dbSWei-Ning Huang {
99049a59dbSWei-Ning Huang 	int ret;
100049a59dbSWei-Ning Huang 	struct vpd_section *sec = arg;
101049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info;
102049a59dbSWei-Ning Huang 
103049a59dbSWei-Ning Huang 	/*
104049a59dbSWei-Ning Huang 	 * Return VPD_OK immediately to decode next entry if the current key
105049a59dbSWei-Ning Huang 	 * name contains invalid characters.
106049a59dbSWei-Ning Huang 	 */
107049a59dbSWei-Ning Huang 	if (vpd_section_check_key_name(key, key_len) != VPD_OK)
108049a59dbSWei-Ning Huang 		return VPD_OK;
109049a59dbSWei-Ning Huang 
110049a59dbSWei-Ning Huang 	info = kzalloc(sizeof(*info), GFP_KERNEL);
1119434cec1SChristophe JAILLET 	if (!info)
112049a59dbSWei-Ning Huang 		return -ENOMEM;
1133eec6a1cSDmitry Torokhov 
1143eec6a1cSDmitry Torokhov 	info->key = kstrndup(key, key_len, GFP_KERNEL);
1159434cec1SChristophe JAILLET 	if (!info->key) {
1169434cec1SChristophe JAILLET 		ret = -ENOMEM;
1179434cec1SChristophe JAILLET 		goto free_info;
1189434cec1SChristophe JAILLET 	}
119049a59dbSWei-Ning Huang 
120049a59dbSWei-Ning Huang 	sysfs_bin_attr_init(&info->bin_attr);
121049a59dbSWei-Ning Huang 	info->bin_attr.attr.name = info->key;
122049a59dbSWei-Ning Huang 	info->bin_attr.attr.mode = 0444;
123049a59dbSWei-Ning Huang 	info->bin_attr.size = value_len;
124049a59dbSWei-Ning Huang 	info->bin_attr.read = vpd_attrib_read;
125049a59dbSWei-Ning Huang 	info->bin_attr.private = info;
126049a59dbSWei-Ning Huang 
127049a59dbSWei-Ning Huang 	info->value = value;
128049a59dbSWei-Ning Huang 
129049a59dbSWei-Ning Huang 	INIT_LIST_HEAD(&info->list);
130049a59dbSWei-Ning Huang 
131049a59dbSWei-Ning Huang 	ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
1329434cec1SChristophe JAILLET 	if (ret)
1339434cec1SChristophe JAILLET 		goto free_info_key;
134049a59dbSWei-Ning Huang 
1357f53de20SDmitry Torokhov 	list_add_tail(&info->list, &sec->attribs);
136049a59dbSWei-Ning Huang 	return 0;
1379434cec1SChristophe JAILLET 
1389434cec1SChristophe JAILLET free_info_key:
1399434cec1SChristophe JAILLET 	kfree(info->key);
1409434cec1SChristophe JAILLET free_info:
1419434cec1SChristophe JAILLET 	kfree(info);
1429434cec1SChristophe JAILLET 
1439434cec1SChristophe JAILLET 	return ret;
144049a59dbSWei-Ning Huang }
145049a59dbSWei-Ning Huang 
vpd_section_attrib_destroy(struct vpd_section * sec)146049a59dbSWei-Ning Huang static void vpd_section_attrib_destroy(struct vpd_section *sec)
147049a59dbSWei-Ning Huang {
148049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info;
149049a59dbSWei-Ning Huang 	struct vpd_attrib_info *temp;
150049a59dbSWei-Ning Huang 
151049a59dbSWei-Ning Huang 	list_for_each_entry_safe(info, temp, &sec->attribs, list) {
152049a59dbSWei-Ning Huang 		sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
153ce57cba3SDmitry Torokhov 		kfree(info->key);
154049a59dbSWei-Ning Huang 		kfree(info);
155049a59dbSWei-Ning Huang 	}
156049a59dbSWei-Ning Huang }
157049a59dbSWei-Ning Huang 
vpd_section_read(struct file * filp,struct kobject * kobp,struct bin_attribute * bin_attr,char * buf,loff_t pos,size_t count)158049a59dbSWei-Ning Huang static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
159049a59dbSWei-Ning Huang 				struct bin_attribute *bin_attr, char *buf,
160049a59dbSWei-Ning Huang 				loff_t pos, size_t count)
161049a59dbSWei-Ning Huang {
162049a59dbSWei-Ning Huang 	struct vpd_section *sec = bin_attr->private;
163049a59dbSWei-Ning Huang 
164049a59dbSWei-Ning Huang 	return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
165049a59dbSWei-Ning Huang 				       sec->bin_attr.size);
166049a59dbSWei-Ning Huang }
167049a59dbSWei-Ning Huang 
vpd_section_create_attribs(struct vpd_section * sec)168049a59dbSWei-Ning Huang static int vpd_section_create_attribs(struct vpd_section *sec)
169049a59dbSWei-Ning Huang {
170049a59dbSWei-Ning Huang 	s32 consumed;
171049a59dbSWei-Ning Huang 	int ret;
172049a59dbSWei-Ning Huang 
173049a59dbSWei-Ning Huang 	consumed = 0;
174049a59dbSWei-Ning Huang 	do {
175049a59dbSWei-Ning Huang 		ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
176049a59dbSWei-Ning Huang 					&consumed, vpd_section_attrib_add, sec);
177049a59dbSWei-Ning Huang 	} while (ret == VPD_OK);
178049a59dbSWei-Ning Huang 
179049a59dbSWei-Ning Huang 	return 0;
180049a59dbSWei-Ning Huang }
181049a59dbSWei-Ning Huang 
vpd_section_init(const char * name,struct vpd_section * sec,phys_addr_t physaddr,size_t size)182049a59dbSWei-Ning Huang static int vpd_section_init(const char *name, struct vpd_section *sec,
183049a59dbSWei-Ning Huang 			    phys_addr_t physaddr, size_t size)
184049a59dbSWei-Ning Huang {
1859920a33eSDmitry Torokhov 	int err;
186049a59dbSWei-Ning Huang 
187049a59dbSWei-Ning Huang 	sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
188049a59dbSWei-Ning Huang 	if (!sec->baseaddr)
189049a59dbSWei-Ning Huang 		return -ENOMEM;
190049a59dbSWei-Ning Huang 
191049a59dbSWei-Ning Huang 	sec->name = name;
192049a59dbSWei-Ning Huang 
1937153d9afSColin Ian King 	/* We want to export the raw partition with name ${name}_raw */
1949920a33eSDmitry Torokhov 	sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
1959920a33eSDmitry Torokhov 	if (!sec->raw_name) {
1969920a33eSDmitry Torokhov 		err = -ENOMEM;
19775e5bae6SPan Bian 		goto err_memunmap;
1989920a33eSDmitry Torokhov 	}
199049a59dbSWei-Ning Huang 
200049a59dbSWei-Ning Huang 	sysfs_bin_attr_init(&sec->bin_attr);
201049a59dbSWei-Ning Huang 	sec->bin_attr.attr.name = sec->raw_name;
202049a59dbSWei-Ning Huang 	sec->bin_attr.attr.mode = 0444;
203049a59dbSWei-Ning Huang 	sec->bin_attr.size = size;
204049a59dbSWei-Ning Huang 	sec->bin_attr.read = vpd_section_read;
205049a59dbSWei-Ning Huang 	sec->bin_attr.private = sec;
206049a59dbSWei-Ning Huang 
2079920a33eSDmitry Torokhov 	err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
2089920a33eSDmitry Torokhov 	if (err)
2099920a33eSDmitry Torokhov 		goto err_free_raw_name;
210049a59dbSWei-Ning Huang 
211049a59dbSWei-Ning Huang 	sec->kobj = kobject_create_and_add(name, vpd_kobj);
212049a59dbSWei-Ning Huang 	if (!sec->kobj) {
2139920a33eSDmitry Torokhov 		err = -EINVAL;
2149920a33eSDmitry Torokhov 		goto err_sysfs_remove;
215049a59dbSWei-Ning Huang 	}
216049a59dbSWei-Ning Huang 
217049a59dbSWei-Ning Huang 	INIT_LIST_HEAD(&sec->attribs);
218049a59dbSWei-Ning Huang 	vpd_section_create_attribs(sec);
219049a59dbSWei-Ning Huang 
220049a59dbSWei-Ning Huang 	sec->enabled = true;
221049a59dbSWei-Ning Huang 
222049a59dbSWei-Ning Huang 	return 0;
223049a59dbSWei-Ning Huang 
2249920a33eSDmitry Torokhov err_sysfs_remove:
225049a59dbSWei-Ning Huang 	sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
2269920a33eSDmitry Torokhov err_free_raw_name:
227049a59dbSWei-Ning Huang 	kfree(sec->raw_name);
22875e5bae6SPan Bian err_memunmap:
22975e5bae6SPan Bian 	memunmap(sec->baseaddr);
2309920a33eSDmitry Torokhov 	return err;
231049a59dbSWei-Ning Huang }
232049a59dbSWei-Ning Huang 
vpd_section_destroy(struct vpd_section * sec)233049a59dbSWei-Ning Huang static int vpd_section_destroy(struct vpd_section *sec)
234049a59dbSWei-Ning Huang {
235049a59dbSWei-Ning Huang 	if (sec->enabled) {
236049a59dbSWei-Ning Huang 		vpd_section_attrib_destroy(sec);
2372ee1cc70SDmitry Torokhov 		kobject_put(sec->kobj);
238049a59dbSWei-Ning Huang 		sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
239049a59dbSWei-Ning Huang 		kfree(sec->raw_name);
24075e5bae6SPan Bian 		memunmap(sec->baseaddr);
24145ca3f76SAnton Vasilyev 		sec->enabled = false;
242049a59dbSWei-Ning Huang 	}
243049a59dbSWei-Ning Huang 
244049a59dbSWei-Ning Huang 	return 0;
245049a59dbSWei-Ning Huang }
246049a59dbSWei-Ning Huang 
vpd_sections_init(phys_addr_t physaddr)247049a59dbSWei-Ning Huang static int vpd_sections_init(phys_addr_t physaddr)
248049a59dbSWei-Ning Huang {
249ae21f41eSStephen Boyd 	struct vpd_cbmem *temp;
250049a59dbSWei-Ning Huang 	struct vpd_cbmem header;
251049a59dbSWei-Ning Huang 	int ret = 0;
252049a59dbSWei-Ning Huang 
253049a59dbSWei-Ning Huang 	temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
254049a59dbSWei-Ning Huang 	if (!temp)
255049a59dbSWei-Ning Huang 		return -ENOMEM;
256049a59dbSWei-Ning Huang 
257ae21f41eSStephen Boyd 	memcpy(&header, temp, sizeof(struct vpd_cbmem));
25875e5bae6SPan Bian 	memunmap(temp);
259049a59dbSWei-Ning Huang 
260049a59dbSWei-Ning Huang 	if (header.magic != VPD_CBMEM_MAGIC)
261049a59dbSWei-Ning Huang 		return -ENODEV;
262049a59dbSWei-Ning Huang 
263049a59dbSWei-Ning Huang 	if (header.ro_size) {
264049a59dbSWei-Ning Huang 		ret = vpd_section_init("ro", &ro_vpd,
265049a59dbSWei-Ning Huang 				       physaddr + sizeof(struct vpd_cbmem),
266049a59dbSWei-Ning Huang 				       header.ro_size);
267049a59dbSWei-Ning Huang 		if (ret)
268049a59dbSWei-Ning Huang 			return ret;
269049a59dbSWei-Ning Huang 	}
270049a59dbSWei-Ning Huang 
271049a59dbSWei-Ning Huang 	if (header.rw_size) {
272049a59dbSWei-Ning Huang 		ret = vpd_section_init("rw", &rw_vpd,
273049a59dbSWei-Ning Huang 				       physaddr + sizeof(struct vpd_cbmem) +
274049a59dbSWei-Ning Huang 				       header.ro_size, header.rw_size);
27545ca3f76SAnton Vasilyev 		if (ret) {
27645ca3f76SAnton Vasilyev 			vpd_section_destroy(&ro_vpd);
277049a59dbSWei-Ning Huang 			return ret;
278049a59dbSWei-Ning Huang 		}
27945ca3f76SAnton Vasilyev 	}
280049a59dbSWei-Ning Huang 
281049a59dbSWei-Ning Huang 	return 0;
282049a59dbSWei-Ning Huang }
283049a59dbSWei-Ning Huang 
vpd_probe(struct coreboot_device * dev)284a43eb6b3SSamuel Holland static int vpd_probe(struct coreboot_device *dev)
28546505c80SDmitry Torokhov {
28646505c80SDmitry Torokhov 	int ret;
28746505c80SDmitry Torokhov 
288e4b28b3cSGuenter Roeck 	vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
289e4b28b3cSGuenter Roeck 	if (!vpd_kobj)
290e4b28b3cSGuenter Roeck 		return -ENOMEM;
291e4b28b3cSGuenter Roeck 
292a43eb6b3SSamuel Holland 	ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);
293e4b28b3cSGuenter Roeck 	if (ret) {
294e4b28b3cSGuenter Roeck 		kobject_put(vpd_kobj);
295e4b28b3cSGuenter Roeck 		return ret;
296e4b28b3cSGuenter Roeck 	}
297e4b28b3cSGuenter Roeck 
298e4b28b3cSGuenter Roeck 	return 0;
29946505c80SDmitry Torokhov }
30046505c80SDmitry Torokhov 
vpd_remove(struct coreboot_device * dev)3015f680532SUwe Kleine-König static void vpd_remove(struct coreboot_device *dev)
302811d7e02SGuenter Roeck {
303811d7e02SGuenter Roeck 	vpd_section_destroy(&ro_vpd);
304811d7e02SGuenter Roeck 	vpd_section_destroy(&rw_vpd);
305811d7e02SGuenter Roeck 
306e4b28b3cSGuenter Roeck 	kobject_put(vpd_kobj);
307811d7e02SGuenter Roeck }
308811d7e02SGuenter Roeck 
3098a0a6294SNícolas F. R. A. Prado static const struct coreboot_device_id vpd_ids[] = {
3108a0a6294SNícolas F. R. A. Prado 	{ .tag = CB_TAG_VPD },
3118a0a6294SNícolas F. R. A. Prado 	{ /* sentinel */ }
3128a0a6294SNícolas F. R. A. Prado };
3138a0a6294SNícolas F. R. A. Prado MODULE_DEVICE_TABLE(coreboot, vpd_ids);
3148a0a6294SNícolas F. R. A. Prado 
315a43eb6b3SSamuel Holland static struct coreboot_driver vpd_driver = {
31646505c80SDmitry Torokhov 	.probe = vpd_probe,
317811d7e02SGuenter Roeck 	.remove = vpd_remove,
318a43eb6b3SSamuel Holland 	.drv = {
31946505c80SDmitry Torokhov 		.name = "vpd",
32046505c80SDmitry Torokhov 	},
3218a0a6294SNícolas F. R. A. Prado 	.id_table = vpd_ids,
32246505c80SDmitry Torokhov };
32335463503SStephen Boyd module_coreboot_driver(vpd_driver);
324049a59dbSWei-Ning Huang 
325049a59dbSWei-Ning Huang MODULE_AUTHOR("Google, Inc.");
326*fc2c1d71SJeff Johnson MODULE_DESCRIPTION("Driver for exporting Vital Product Data content to sysfs");
327049a59dbSWei-Ning Huang MODULE_LICENSE("GPL");
328