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 | |
28 | static struct kobject *vpd_kobj; |
29 | |
30 | struct vpd_cbmem { |
31 | u32 magic; |
32 | u32 version; |
33 | u32 ro_size; |
34 | u32 rw_size; |
35 | u8 blob[]; |
36 | }; |
37 | |
38 | struct 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 | |
48 | struct vpd_attrib_info { |
49 | char *key; |
50 | const char *value; |
51 | struct bin_attribute bin_attr; |
52 | struct list_head list; |
53 | }; |
54 | |
55 | static struct vpd_section ro_vpd; |
56 | static struct vpd_section rw_vpd; |
57 | |
58 | static 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(to: buf, count, ppos: &pos, from: info->value, |
65 | available: 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 | */ |
81 | static 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 | |
95 | static 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(size: sizeof(*info), GFP_KERNEL); |
111 | if (!info) |
112 | return -ENOMEM; |
113 | |
114 | info->key = kstrndup(s: key, len: 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(list: &info->list); |
130 | |
131 | ret = sysfs_create_bin_file(kobj: sec->kobj, attr: &info->bin_attr); |
132 | if (ret) |
133 | goto free_info_key; |
134 | |
135 | list_add_tail(new: &info->list, head: &sec->attribs); |
136 | return 0; |
137 | |
138 | free_info_key: |
139 | kfree(objp: info->key); |
140 | free_info: |
141 | kfree(objp: info); |
142 | |
143 | return ret; |
144 | } |
145 | |
146 | static 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(kobj: sec->kobj, attr: &info->bin_attr); |
153 | kfree(objp: info->key); |
154 | kfree(objp: info); |
155 | } |
156 | } |
157 | |
158 | static 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(to: buf, count, ppos: &pos, from: sec->baseaddr, |
165 | available: sec->bin_attr.size); |
166 | } |
167 | |
168 | static 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(max_len: sec->bin_attr.size, input_buf: sec->baseaddr, |
176 | consumed: &consumed, callback: vpd_section_attrib_add, callback_arg: sec); |
177 | } while (ret == VPD_OK); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | static 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(offset: physaddr, size, flags: 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, fmt: "%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(kobj: vpd_kobj, attr: &sec->bin_attr); |
208 | if (err) |
209 | goto err_free_raw_name; |
210 | |
211 | sec->kobj = kobject_create_and_add(name, parent: vpd_kobj); |
212 | if (!sec->kobj) { |
213 | err = -EINVAL; |
214 | goto err_sysfs_remove; |
215 | } |
216 | |
217 | INIT_LIST_HEAD(list: &sec->attribs); |
218 | vpd_section_create_attribs(sec); |
219 | |
220 | sec->enabled = true; |
221 | |
222 | return 0; |
223 | |
224 | err_sysfs_remove: |
225 | sysfs_remove_bin_file(kobj: vpd_kobj, attr: &sec->bin_attr); |
226 | err_free_raw_name: |
227 | kfree(objp: sec->raw_name); |
228 | err_memunmap: |
229 | memunmap(addr: sec->baseaddr); |
230 | return err; |
231 | } |
232 | |
233 | static int vpd_section_destroy(struct vpd_section *sec) |
234 | { |
235 | if (sec->enabled) { |
236 | vpd_section_attrib_destroy(sec); |
237 | kobject_put(kobj: sec->kobj); |
238 | sysfs_remove_bin_file(kobj: vpd_kobj, attr: &sec->bin_attr); |
239 | kfree(objp: sec->raw_name); |
240 | memunmap(addr: sec->baseaddr); |
241 | sec->enabled = false; |
242 | } |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static int vpd_sections_init(phys_addr_t physaddr) |
248 | { |
249 | struct vpd_cbmem *temp; |
250 | struct vpd_cbmem ; |
251 | int ret = 0; |
252 | |
253 | temp = memremap(offset: physaddr, size: sizeof(struct vpd_cbmem), flags: MEMREMAP_WB); |
254 | if (!temp) |
255 | return -ENOMEM; |
256 | |
257 | memcpy(&header, temp, sizeof(struct vpd_cbmem)); |
258 | memunmap(addr: temp); |
259 | |
260 | if (header.magic != VPD_CBMEM_MAGIC) |
261 | return -ENODEV; |
262 | |
263 | if (header.ro_size) { |
264 | ret = vpd_section_init(name: "ro" , sec: &ro_vpd, |
265 | physaddr: physaddr + sizeof(struct vpd_cbmem), |
266 | size: header.ro_size); |
267 | if (ret) |
268 | return ret; |
269 | } |
270 | |
271 | if (header.rw_size) { |
272 | ret = vpd_section_init(name: "rw" , sec: &rw_vpd, |
273 | physaddr: physaddr + sizeof(struct vpd_cbmem) + |
274 | header.ro_size, size: header.rw_size); |
275 | if (ret) { |
276 | vpd_section_destroy(sec: &ro_vpd); |
277 | return ret; |
278 | } |
279 | } |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | static int vpd_probe(struct coreboot_device *dev) |
285 | { |
286 | int ret; |
287 | |
288 | vpd_kobj = kobject_create_and_add(name: "vpd" , parent: firmware_kobj); |
289 | if (!vpd_kobj) |
290 | return -ENOMEM; |
291 | |
292 | ret = vpd_sections_init(physaddr: dev->cbmem_ref.cbmem_addr); |
293 | if (ret) { |
294 | kobject_put(kobj: vpd_kobj); |
295 | return ret; |
296 | } |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static void vpd_remove(struct coreboot_device *dev) |
302 | { |
303 | vpd_section_destroy(sec: &ro_vpd); |
304 | vpd_section_destroy(sec: &rw_vpd); |
305 | |
306 | kobject_put(kobj: vpd_kobj); |
307 | } |
308 | |
309 | static const struct coreboot_device_id vpd_ids[] = { |
310 | { .tag = CB_TAG_VPD }, |
311 | { /* sentinel */ } |
312 | }; |
313 | MODULE_DEVICE_TABLE(coreboot, vpd_ids); |
314 | |
315 | static 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 | }; |
323 | module_coreboot_driver(vpd_driver); |
324 | |
325 | MODULE_AUTHOR("Google, Inc." ); |
326 | MODULE_LICENSE("GPL" ); |
327 | |