1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * vpd.c
4 *
5 * Driver for exporting VPD content to sysfs.
6 *
7 * Copyright 2017 Google Inc.
8 */
9
10#include <linux/ctype.h>
11#include <linux/init.h>
12#include <linux/io.h>
13#include <linux/kernel.h>
14#include <linux/kobject.h>
15#include <linux/list.h>
16#include <linux/module.h>
17#include <linux/of_address.h>
18#include <linux/platform_device.h>
19#include <linux/slab.h>
20#include <linux/sysfs.h>
21
22#include "coreboot_table.h"
23#include "vpd_decode.h"
24
25#define CB_TAG_VPD      0x2c
26#define VPD_CBMEM_MAGIC 0x43524f53
27
28static struct kobject *vpd_kobj;
29
30struct vpd_cbmem {
31	u32 magic;
32	u32 version;
33	u32 ro_size;
34	u32 rw_size;
35	u8  blob[];
36};
37
38struct vpd_section {
39	bool enabled;
40	const char *name;
41	char *raw_name;                /* the string name_raw */
42	struct kobject *kobj;          /* vpd/name directory */
43	char *baseaddr;
44	struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
45	struct list_head attribs;      /* key/value in vpd_attrib_info list */
46};
47
48struct vpd_attrib_info {
49	char *key;
50	const char *value;
51	struct bin_attribute bin_attr;
52	struct list_head list;
53};
54
55static struct vpd_section ro_vpd;
56static struct vpd_section rw_vpd;
57
58static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
59			       struct bin_attribute *bin_attr, char *buf,
60			       loff_t pos, size_t count)
61{
62	struct vpd_attrib_info *info = bin_attr->private;
63
64	return memory_read_from_buffer(buf, count, &pos, info->value,
65				       info->bin_attr.size);
66}
67
68/*
69 * vpd_section_check_key_name()
70 *
71 * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
72 * old firmware versions may have entries like "S/N" which are problematic when
73 * exporting them as sysfs attributes. These keys present in old firmwares are
74 * ignored.
75 *
76 * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
77 *
78 * @key: The key name to check
79 * @key_len: key name length
80 */
81static int vpd_section_check_key_name(const u8 *key, s32 key_len)
82{
83	int c;
84
85	while (key_len-- > 0) {
86		c = *key++;
87
88		if (!isalnum(c) && c != '_')
89			return VPD_FAIL;
90	}
91
92	return VPD_OK;
93}
94
95static int vpd_section_attrib_add(const u8 *key, u32 key_len,
96				  const u8 *value, u32 value_len,
97				  void *arg)
98{
99	int ret;
100	struct vpd_section *sec = arg;
101	struct vpd_attrib_info *info;
102
103	/*
104	 * Return VPD_OK immediately to decode next entry if the current key
105	 * name contains invalid characters.
106	 */
107	if (vpd_section_check_key_name(key, key_len) != VPD_OK)
108		return VPD_OK;
109
110	info = kzalloc(sizeof(*info), GFP_KERNEL);
111	if (!info)
112		return -ENOMEM;
113
114	info->key = kstrndup(key, key_len, GFP_KERNEL);
115	if (!info->key) {
116		ret = -ENOMEM;
117		goto free_info;
118	}
119
120	sysfs_bin_attr_init(&info->bin_attr);
121	info->bin_attr.attr.name = info->key;
122	info->bin_attr.attr.mode = 0444;
123	info->bin_attr.size = value_len;
124	info->bin_attr.read = vpd_attrib_read;
125	info->bin_attr.private = info;
126
127	info->value = value;
128
129	INIT_LIST_HEAD(&info->list);
130
131	ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
132	if (ret)
133		goto free_info_key;
134
135	list_add_tail(&info->list, &sec->attribs);
136	return 0;
137
138free_info_key:
139	kfree(info->key);
140free_info:
141	kfree(info);
142
143	return ret;
144}
145
146static void vpd_section_attrib_destroy(struct vpd_section *sec)
147{
148	struct vpd_attrib_info *info;
149	struct vpd_attrib_info *temp;
150
151	list_for_each_entry_safe(info, temp, &sec->attribs, list) {
152		sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
153		kfree(info->key);
154		kfree(info);
155	}
156}
157
158static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
159				struct bin_attribute *bin_attr, char *buf,
160				loff_t pos, size_t count)
161{
162	struct vpd_section *sec = bin_attr->private;
163
164	return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
165				       sec->bin_attr.size);
166}
167
168static int vpd_section_create_attribs(struct vpd_section *sec)
169{
170	s32 consumed;
171	int ret;
172
173	consumed = 0;
174	do {
175		ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
176					&consumed, vpd_section_attrib_add, sec);
177	} while (ret == VPD_OK);
178
179	return 0;
180}
181
182static int vpd_section_init(const char *name, struct vpd_section *sec,
183			    phys_addr_t physaddr, size_t size)
184{
185	int err;
186
187	sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
188	if (!sec->baseaddr)
189		return -ENOMEM;
190
191	sec->name = name;
192
193	/* We want to export the raw partition with name ${name}_raw */
194	sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
195	if (!sec->raw_name) {
196		err = -ENOMEM;
197		goto err_memunmap;
198	}
199
200	sysfs_bin_attr_init(&sec->bin_attr);
201	sec->bin_attr.attr.name = sec->raw_name;
202	sec->bin_attr.attr.mode = 0444;
203	sec->bin_attr.size = size;
204	sec->bin_attr.read = vpd_section_read;
205	sec->bin_attr.private = sec;
206
207	err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
208	if (err)
209		goto err_free_raw_name;
210
211	sec->kobj = kobject_create_and_add(name, vpd_kobj);
212	if (!sec->kobj) {
213		err = -EINVAL;
214		goto err_sysfs_remove;
215	}
216
217	INIT_LIST_HEAD(&sec->attribs);
218	vpd_section_create_attribs(sec);
219
220	sec->enabled = true;
221
222	return 0;
223
224err_sysfs_remove:
225	sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
226err_free_raw_name:
227	kfree(sec->raw_name);
228err_memunmap:
229	memunmap(sec->baseaddr);
230	return err;
231}
232
233static int vpd_section_destroy(struct vpd_section *sec)
234{
235	if (sec->enabled) {
236		vpd_section_attrib_destroy(sec);
237		kobject_put(sec->kobj);
238		sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
239		kfree(sec->raw_name);
240		memunmap(sec->baseaddr);
241		sec->enabled = false;
242	}
243
244	return 0;
245}
246
247static int vpd_sections_init(phys_addr_t physaddr)
248{
249	struct vpd_cbmem *temp;
250	struct vpd_cbmem header;
251	int ret = 0;
252
253	temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
254	if (!temp)
255		return -ENOMEM;
256
257	memcpy(&header, temp, sizeof(struct vpd_cbmem));
258	memunmap(temp);
259
260	if (header.magic != VPD_CBMEM_MAGIC)
261		return -ENODEV;
262
263	if (header.ro_size) {
264		ret = vpd_section_init("ro", &ro_vpd,
265				       physaddr + sizeof(struct vpd_cbmem),
266				       header.ro_size);
267		if (ret)
268			return ret;
269	}
270
271	if (header.rw_size) {
272		ret = vpd_section_init("rw", &rw_vpd,
273				       physaddr + sizeof(struct vpd_cbmem) +
274				       header.ro_size, header.rw_size);
275		if (ret) {
276			vpd_section_destroy(&ro_vpd);
277			return ret;
278		}
279	}
280
281	return 0;
282}
283
284static int vpd_probe(struct coreboot_device *dev)
285{
286	int ret;
287
288	vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
289	if (!vpd_kobj)
290		return -ENOMEM;
291
292	ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);
293	if (ret) {
294		kobject_put(vpd_kobj);
295		return ret;
296	}
297
298	return 0;
299}
300
301static void vpd_remove(struct coreboot_device *dev)
302{
303	vpd_section_destroy(&ro_vpd);
304	vpd_section_destroy(&rw_vpd);
305
306	kobject_put(vpd_kobj);
307}
308
309static const struct coreboot_device_id vpd_ids[] = {
310	{ .tag = CB_TAG_VPD },
311	{ /* sentinel */ }
312};
313MODULE_DEVICE_TABLE(coreboot, vpd_ids);
314
315static struct coreboot_driver vpd_driver = {
316	.probe = vpd_probe,
317	.remove = vpd_remove,
318	.drv = {
319		.name = "vpd",
320	},
321	.id_table = vpd_ids,
322};
323module_coreboot_driver(vpd_driver);
324
325MODULE_AUTHOR("Google, Inc.");
326MODULE_LICENSE("GPL");
327