xref: /linux/drivers/firmware/google/vpd.c (revision 811d7e0215fb738fb9a9f0bcb1276516ad161ed1)
1049a59dbSWei-Ning Huang /*
2049a59dbSWei-Ning Huang  * vpd.c
3049a59dbSWei-Ning Huang  *
4049a59dbSWei-Ning Huang  * Driver for exporting VPD content to sysfs.
5049a59dbSWei-Ning Huang  *
6049a59dbSWei-Ning Huang  * Copyright 2017 Google Inc.
7049a59dbSWei-Ning Huang  *
8049a59dbSWei-Ning Huang  * This program is free software; you can redistribute it and/or modify
9049a59dbSWei-Ning Huang  * it under the terms of the GNU General Public License v2.0 as published by
10049a59dbSWei-Ning Huang  * the Free Software Foundation.
11049a59dbSWei-Ning Huang  *
12049a59dbSWei-Ning Huang  * This program is distributed in the hope that it will be useful,
13049a59dbSWei-Ning Huang  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14049a59dbSWei-Ning Huang  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15049a59dbSWei-Ning Huang  * GNU General Public License for more details.
16049a59dbSWei-Ning Huang  */
17049a59dbSWei-Ning Huang 
18049a59dbSWei-Ning Huang #include <linux/ctype.h>
19049a59dbSWei-Ning Huang #include <linux/init.h>
20049a59dbSWei-Ning Huang #include <linux/io.h>
21049a59dbSWei-Ning Huang #include <linux/kernel.h>
22049a59dbSWei-Ning Huang #include <linux/kobject.h>
23049a59dbSWei-Ning Huang #include <linux/list.h>
24049a59dbSWei-Ning Huang #include <linux/module.h>
2546505c80SDmitry Torokhov #include <linux/of_address.h>
2646505c80SDmitry Torokhov #include <linux/platform_device.h>
27049a59dbSWei-Ning Huang #include <linux/slab.h>
28049a59dbSWei-Ning Huang #include <linux/sysfs.h>
29049a59dbSWei-Ning Huang 
30049a59dbSWei-Ning Huang #include "coreboot_table.h"
31049a59dbSWei-Ning Huang #include "vpd_decode.h"
32049a59dbSWei-Ning Huang 
33049a59dbSWei-Ning Huang #define CB_TAG_VPD      0x2c
34049a59dbSWei-Ning Huang #define VPD_CBMEM_MAGIC 0x43524f53
35049a59dbSWei-Ning Huang 
36049a59dbSWei-Ning Huang static struct kobject *vpd_kobj;
37049a59dbSWei-Ning Huang 
38049a59dbSWei-Ning Huang struct vpd_cbmem {
39049a59dbSWei-Ning Huang 	u32 magic;
40049a59dbSWei-Ning Huang 	u32 version;
41049a59dbSWei-Ning Huang 	u32 ro_size;
42049a59dbSWei-Ning Huang 	u32 rw_size;
43049a59dbSWei-Ning Huang 	u8  blob[0];
44049a59dbSWei-Ning Huang };
45049a59dbSWei-Ning Huang 
46049a59dbSWei-Ning Huang struct vpd_section {
47049a59dbSWei-Ning Huang 	bool enabled;
48049a59dbSWei-Ning Huang 	const char *name;
49049a59dbSWei-Ning Huang 	char *raw_name;                /* the string name_raw */
50049a59dbSWei-Ning Huang 	struct kobject *kobj;          /* vpd/name directory */
51049a59dbSWei-Ning Huang 	char *baseaddr;
52049a59dbSWei-Ning Huang 	struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
53049a59dbSWei-Ning Huang 	struct list_head attribs;      /* key/value in vpd_attrib_info list */
54049a59dbSWei-Ning Huang };
55049a59dbSWei-Ning Huang 
56049a59dbSWei-Ning Huang struct vpd_attrib_info {
57049a59dbSWei-Ning Huang 	char *key;
58049a59dbSWei-Ning Huang 	const char *value;
59049a59dbSWei-Ning Huang 	struct bin_attribute bin_attr;
60049a59dbSWei-Ning Huang 	struct list_head list;
61049a59dbSWei-Ning Huang };
62049a59dbSWei-Ning Huang 
63049a59dbSWei-Ning Huang static struct vpd_section ro_vpd;
64049a59dbSWei-Ning Huang static struct vpd_section rw_vpd;
65049a59dbSWei-Ning Huang 
66049a59dbSWei-Ning Huang static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
67049a59dbSWei-Ning Huang 			       struct bin_attribute *bin_attr, char *buf,
68049a59dbSWei-Ning Huang 			       loff_t pos, size_t count)
69049a59dbSWei-Ning Huang {
70049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info = bin_attr->private;
71049a59dbSWei-Ning Huang 
72049a59dbSWei-Ning Huang 	return memory_read_from_buffer(buf, count, &pos, info->value,
73049a59dbSWei-Ning Huang 				       info->bin_attr.size);
74049a59dbSWei-Ning Huang }
75049a59dbSWei-Ning Huang 
76049a59dbSWei-Ning Huang /*
77049a59dbSWei-Ning Huang  * vpd_section_check_key_name()
78049a59dbSWei-Ning Huang  *
79049a59dbSWei-Ning Huang  * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
80049a59dbSWei-Ning Huang  * old firmware versions may have entries like "S/N" which are problematic when
81049a59dbSWei-Ning Huang  * exporting them as sysfs attributes. These keys present in old firmwares are
82049a59dbSWei-Ning Huang  * ignored.
83049a59dbSWei-Ning Huang  *
84049a59dbSWei-Ning Huang  * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
85049a59dbSWei-Ning Huang  *
86049a59dbSWei-Ning Huang  * @key: The key name to check
87049a59dbSWei-Ning Huang  * @key_len: key name length
88049a59dbSWei-Ning Huang  */
89049a59dbSWei-Ning Huang static int vpd_section_check_key_name(const u8 *key, s32 key_len)
90049a59dbSWei-Ning Huang {
91049a59dbSWei-Ning Huang 	int c;
92049a59dbSWei-Ning Huang 
93049a59dbSWei-Ning Huang 	while (key_len-- > 0) {
94049a59dbSWei-Ning Huang 		c = *key++;
95049a59dbSWei-Ning Huang 
96049a59dbSWei-Ning Huang 		if (!isalnum(c) && c != '_')
97049a59dbSWei-Ning Huang 			return VPD_FAIL;
98049a59dbSWei-Ning Huang 	}
99049a59dbSWei-Ning Huang 
100049a59dbSWei-Ning Huang 	return VPD_OK;
101049a59dbSWei-Ning Huang }
102049a59dbSWei-Ning Huang 
103049a59dbSWei-Ning Huang static int vpd_section_attrib_add(const u8 *key, s32 key_len,
104049a59dbSWei-Ning Huang 				  const u8 *value, s32 value_len,
105049a59dbSWei-Ning Huang 				  void *arg)
106049a59dbSWei-Ning Huang {
107049a59dbSWei-Ning Huang 	int ret;
108049a59dbSWei-Ning Huang 	struct vpd_section *sec = arg;
109049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info;
110049a59dbSWei-Ning Huang 
111049a59dbSWei-Ning Huang 	/*
112049a59dbSWei-Ning Huang 	 * Return VPD_OK immediately to decode next entry if the current key
113049a59dbSWei-Ning Huang 	 * name contains invalid characters.
114049a59dbSWei-Ning Huang 	 */
115049a59dbSWei-Ning Huang 	if (vpd_section_check_key_name(key, key_len) != VPD_OK)
116049a59dbSWei-Ning Huang 		return VPD_OK;
117049a59dbSWei-Ning Huang 
118049a59dbSWei-Ning Huang 	info = kzalloc(sizeof(*info), GFP_KERNEL);
1199434cec1SChristophe JAILLET 	if (!info)
120049a59dbSWei-Ning Huang 		return -ENOMEM;
1213eec6a1cSDmitry Torokhov 
1223eec6a1cSDmitry Torokhov 	info->key = kstrndup(key, key_len, GFP_KERNEL);
1239434cec1SChristophe JAILLET 	if (!info->key) {
1249434cec1SChristophe JAILLET 		ret = -ENOMEM;
1259434cec1SChristophe JAILLET 		goto free_info;
1269434cec1SChristophe JAILLET 	}
127049a59dbSWei-Ning Huang 
128049a59dbSWei-Ning Huang 	sysfs_bin_attr_init(&info->bin_attr);
129049a59dbSWei-Ning Huang 	info->bin_attr.attr.name = info->key;
130049a59dbSWei-Ning Huang 	info->bin_attr.attr.mode = 0444;
131049a59dbSWei-Ning Huang 	info->bin_attr.size = value_len;
132049a59dbSWei-Ning Huang 	info->bin_attr.read = vpd_attrib_read;
133049a59dbSWei-Ning Huang 	info->bin_attr.private = info;
134049a59dbSWei-Ning Huang 
135049a59dbSWei-Ning Huang 	info->value = value;
136049a59dbSWei-Ning Huang 
137049a59dbSWei-Ning Huang 	INIT_LIST_HEAD(&info->list);
138049a59dbSWei-Ning Huang 
139049a59dbSWei-Ning Huang 	ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
1409434cec1SChristophe JAILLET 	if (ret)
1419434cec1SChristophe JAILLET 		goto free_info_key;
142049a59dbSWei-Ning Huang 
1437f53de20SDmitry Torokhov 	list_add_tail(&info->list, &sec->attribs);
144049a59dbSWei-Ning Huang 	return 0;
1459434cec1SChristophe JAILLET 
1469434cec1SChristophe JAILLET free_info_key:
1479434cec1SChristophe JAILLET 	kfree(info->key);
1489434cec1SChristophe JAILLET free_info:
1499434cec1SChristophe JAILLET 	kfree(info);
1509434cec1SChristophe JAILLET 
1519434cec1SChristophe JAILLET 	return ret;
152049a59dbSWei-Ning Huang }
153049a59dbSWei-Ning Huang 
154049a59dbSWei-Ning Huang static void vpd_section_attrib_destroy(struct vpd_section *sec)
155049a59dbSWei-Ning Huang {
156049a59dbSWei-Ning Huang 	struct vpd_attrib_info *info;
157049a59dbSWei-Ning Huang 	struct vpd_attrib_info *temp;
158049a59dbSWei-Ning Huang 
159049a59dbSWei-Ning Huang 	list_for_each_entry_safe(info, temp, &sec->attribs, list) {
160049a59dbSWei-Ning Huang 		sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
161ce57cba3SDmitry Torokhov 		kfree(info->key);
162049a59dbSWei-Ning Huang 		kfree(info);
163049a59dbSWei-Ning Huang 	}
164049a59dbSWei-Ning Huang }
165049a59dbSWei-Ning Huang 
166049a59dbSWei-Ning Huang static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
167049a59dbSWei-Ning Huang 				struct bin_attribute *bin_attr, char *buf,
168049a59dbSWei-Ning Huang 				loff_t pos, size_t count)
169049a59dbSWei-Ning Huang {
170049a59dbSWei-Ning Huang 	struct vpd_section *sec = bin_attr->private;
171049a59dbSWei-Ning Huang 
172049a59dbSWei-Ning Huang 	return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
173049a59dbSWei-Ning Huang 				       sec->bin_attr.size);
174049a59dbSWei-Ning Huang }
175049a59dbSWei-Ning Huang 
176049a59dbSWei-Ning Huang static int vpd_section_create_attribs(struct vpd_section *sec)
177049a59dbSWei-Ning Huang {
178049a59dbSWei-Ning Huang 	s32 consumed;
179049a59dbSWei-Ning Huang 	int ret;
180049a59dbSWei-Ning Huang 
181049a59dbSWei-Ning Huang 	consumed = 0;
182049a59dbSWei-Ning Huang 	do {
183049a59dbSWei-Ning Huang 		ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
184049a59dbSWei-Ning Huang 					&consumed, vpd_section_attrib_add, sec);
185049a59dbSWei-Ning Huang 	} while (ret == VPD_OK);
186049a59dbSWei-Ning Huang 
187049a59dbSWei-Ning Huang 	return 0;
188049a59dbSWei-Ning Huang }
189049a59dbSWei-Ning Huang 
190049a59dbSWei-Ning Huang static int vpd_section_init(const char *name, struct vpd_section *sec,
191049a59dbSWei-Ning Huang 			    phys_addr_t physaddr, size_t size)
192049a59dbSWei-Ning Huang {
1939920a33eSDmitry Torokhov 	int err;
194049a59dbSWei-Ning Huang 
195049a59dbSWei-Ning Huang 	sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
196049a59dbSWei-Ning Huang 	if (!sec->baseaddr)
197049a59dbSWei-Ning Huang 		return -ENOMEM;
198049a59dbSWei-Ning Huang 
199049a59dbSWei-Ning Huang 	sec->name = name;
200049a59dbSWei-Ning Huang 
201049a59dbSWei-Ning Huang 	/* We want to export the raw partion with name ${name}_raw */
2029920a33eSDmitry Torokhov 	sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
2039920a33eSDmitry Torokhov 	if (!sec->raw_name) {
2049920a33eSDmitry Torokhov 		err = -ENOMEM;
20575e5bae6SPan Bian 		goto err_memunmap;
2069920a33eSDmitry Torokhov 	}
207049a59dbSWei-Ning Huang 
208049a59dbSWei-Ning Huang 	sysfs_bin_attr_init(&sec->bin_attr);
209049a59dbSWei-Ning Huang 	sec->bin_attr.attr.name = sec->raw_name;
210049a59dbSWei-Ning Huang 	sec->bin_attr.attr.mode = 0444;
211049a59dbSWei-Ning Huang 	sec->bin_attr.size = size;
212049a59dbSWei-Ning Huang 	sec->bin_attr.read = vpd_section_read;
213049a59dbSWei-Ning Huang 	sec->bin_attr.private = sec;
214049a59dbSWei-Ning Huang 
2159920a33eSDmitry Torokhov 	err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
2169920a33eSDmitry Torokhov 	if (err)
2179920a33eSDmitry Torokhov 		goto err_free_raw_name;
218049a59dbSWei-Ning Huang 
219049a59dbSWei-Ning Huang 	sec->kobj = kobject_create_and_add(name, vpd_kobj);
220049a59dbSWei-Ning Huang 	if (!sec->kobj) {
2219920a33eSDmitry Torokhov 		err = -EINVAL;
2229920a33eSDmitry Torokhov 		goto err_sysfs_remove;
223049a59dbSWei-Ning Huang 	}
224049a59dbSWei-Ning Huang 
225049a59dbSWei-Ning Huang 	INIT_LIST_HEAD(&sec->attribs);
226049a59dbSWei-Ning Huang 	vpd_section_create_attribs(sec);
227049a59dbSWei-Ning Huang 
228049a59dbSWei-Ning Huang 	sec->enabled = true;
229049a59dbSWei-Ning Huang 
230049a59dbSWei-Ning Huang 	return 0;
231049a59dbSWei-Ning Huang 
2329920a33eSDmitry Torokhov err_sysfs_remove:
233049a59dbSWei-Ning Huang 	sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
2349920a33eSDmitry Torokhov err_free_raw_name:
235049a59dbSWei-Ning Huang 	kfree(sec->raw_name);
23675e5bae6SPan Bian err_memunmap:
23775e5bae6SPan Bian 	memunmap(sec->baseaddr);
2389920a33eSDmitry Torokhov 	return err;
239049a59dbSWei-Ning Huang }
240049a59dbSWei-Ning Huang 
241049a59dbSWei-Ning Huang static int vpd_section_destroy(struct vpd_section *sec)
242049a59dbSWei-Ning Huang {
243049a59dbSWei-Ning Huang 	if (sec->enabled) {
244049a59dbSWei-Ning Huang 		vpd_section_attrib_destroy(sec);
2452ee1cc70SDmitry Torokhov 		kobject_put(sec->kobj);
246049a59dbSWei-Ning Huang 		sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
247049a59dbSWei-Ning Huang 		kfree(sec->raw_name);
24875e5bae6SPan Bian 		memunmap(sec->baseaddr);
249049a59dbSWei-Ning Huang 	}
250049a59dbSWei-Ning Huang 
251049a59dbSWei-Ning Huang 	return 0;
252049a59dbSWei-Ning Huang }
253049a59dbSWei-Ning Huang 
254049a59dbSWei-Ning Huang static int vpd_sections_init(phys_addr_t physaddr)
255049a59dbSWei-Ning Huang {
256049a59dbSWei-Ning Huang 	struct vpd_cbmem __iomem *temp;
257049a59dbSWei-Ning Huang 	struct vpd_cbmem header;
258049a59dbSWei-Ning Huang 	int ret = 0;
259049a59dbSWei-Ning Huang 
260049a59dbSWei-Ning Huang 	temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
261049a59dbSWei-Ning Huang 	if (!temp)
262049a59dbSWei-Ning Huang 		return -ENOMEM;
263049a59dbSWei-Ning Huang 
264049a59dbSWei-Ning Huang 	memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
26575e5bae6SPan Bian 	memunmap(temp);
266049a59dbSWei-Ning Huang 
267049a59dbSWei-Ning Huang 	if (header.magic != VPD_CBMEM_MAGIC)
268049a59dbSWei-Ning Huang 		return -ENODEV;
269049a59dbSWei-Ning Huang 
270049a59dbSWei-Ning Huang 	if (header.ro_size) {
271049a59dbSWei-Ning Huang 		ret = vpd_section_init("ro", &ro_vpd,
272049a59dbSWei-Ning Huang 				       physaddr + sizeof(struct vpd_cbmem),
273049a59dbSWei-Ning Huang 				       header.ro_size);
274049a59dbSWei-Ning Huang 		if (ret)
275049a59dbSWei-Ning Huang 			return ret;
276049a59dbSWei-Ning Huang 	}
277049a59dbSWei-Ning Huang 
278049a59dbSWei-Ning Huang 	if (header.rw_size) {
279049a59dbSWei-Ning Huang 		ret = vpd_section_init("rw", &rw_vpd,
280049a59dbSWei-Ning Huang 				       physaddr + sizeof(struct vpd_cbmem) +
281049a59dbSWei-Ning Huang 				       header.ro_size, header.rw_size);
28246505c80SDmitry Torokhov 		if (ret)
283049a59dbSWei-Ning Huang 			return ret;
284049a59dbSWei-Ning Huang 	}
285049a59dbSWei-Ning Huang 
286049a59dbSWei-Ning Huang 	return 0;
287049a59dbSWei-Ning Huang }
288049a59dbSWei-Ning Huang 
28946505c80SDmitry Torokhov static int vpd_probe(struct platform_device *pdev)
29046505c80SDmitry Torokhov {
29146505c80SDmitry Torokhov 	int ret;
29246505c80SDmitry Torokhov 	struct lb_cbmem_ref entry;
29346505c80SDmitry Torokhov 
29446505c80SDmitry Torokhov 	ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry));
29546505c80SDmitry Torokhov 	if (ret)
29646505c80SDmitry Torokhov 		return ret;
29746505c80SDmitry Torokhov 
29846505c80SDmitry Torokhov 	return vpd_sections_init(entry.cbmem_addr);
29946505c80SDmitry Torokhov }
30046505c80SDmitry Torokhov 
301*811d7e02SGuenter Roeck static int vpd_remove(struct platform_device *pdev)
302*811d7e02SGuenter Roeck {
303*811d7e02SGuenter Roeck 	vpd_section_destroy(&ro_vpd);
304*811d7e02SGuenter Roeck 	vpd_section_destroy(&rw_vpd);
305*811d7e02SGuenter Roeck 
306*811d7e02SGuenter Roeck 	return 0;
307*811d7e02SGuenter Roeck }
308*811d7e02SGuenter Roeck 
30946505c80SDmitry Torokhov static struct platform_driver vpd_driver = {
31046505c80SDmitry Torokhov 	.probe = vpd_probe,
311*811d7e02SGuenter Roeck 	.remove = vpd_remove,
31246505c80SDmitry Torokhov 	.driver = {
31346505c80SDmitry Torokhov 		.name = "vpd",
31446505c80SDmitry Torokhov 	},
31546505c80SDmitry Torokhov };
31646505c80SDmitry Torokhov 
317049a59dbSWei-Ning Huang static int __init vpd_platform_init(void)
318049a59dbSWei-Ning Huang {
31946505c80SDmitry Torokhov 	struct platform_device *pdev;
32046505c80SDmitry Torokhov 
32146505c80SDmitry Torokhov 	pdev = platform_device_register_simple("vpd", -1, NULL, 0);
32246505c80SDmitry Torokhov 	if (IS_ERR(pdev))
32346505c80SDmitry Torokhov 		return PTR_ERR(pdev);
324049a59dbSWei-Ning Huang 
325049a59dbSWei-Ning Huang 	vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
326049a59dbSWei-Ning Huang 	if (!vpd_kobj)
327049a59dbSWei-Ning Huang 		return -ENOMEM;
328049a59dbSWei-Ning Huang 
32946505c80SDmitry Torokhov 	platform_driver_register(&vpd_driver);
330049a59dbSWei-Ning Huang 
331049a59dbSWei-Ning Huang 	return 0;
332049a59dbSWei-Ning Huang }
333049a59dbSWei-Ning Huang 
334049a59dbSWei-Ning Huang static void __exit vpd_platform_exit(void)
335049a59dbSWei-Ning Huang {
3362ee1cc70SDmitry Torokhov 	kobject_put(vpd_kobj);
337049a59dbSWei-Ning Huang }
338049a59dbSWei-Ning Huang 
339049a59dbSWei-Ning Huang module_init(vpd_platform_init);
340049a59dbSWei-Ning Huang module_exit(vpd_platform_exit);
341049a59dbSWei-Ning Huang 
342049a59dbSWei-Ning Huang MODULE_AUTHOR("Google, Inc.");
343049a59dbSWei-Ning Huang MODULE_LICENSE("GPL");
344