blob: e4b40f2b46274a0871d1cb881732e8358a324112 [file] [log] [blame]
Wei-Ning Huang049a59d2017-04-12 18:56:19 +02001/*
2 * vpd.c
3 *
4 * Driver for exporting VPD content to sysfs.
5 *
6 * Copyright 2017 Google Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License v2.0 as published by
10 * the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 */
17
18#include <linux/ctype.h>
19#include <linux/init.h>
20#include <linux/io.h>
21#include <linux/kernel.h>
22#include <linux/kobject.h>
23#include <linux/list.h>
24#include <linux/module.h>
Dmitry Torokhov46505c82017-05-26 13:57:49 -070025#include <linux/of_address.h>
26#include <linux/platform_device.h>
Wei-Ning Huang049a59d2017-04-12 18:56:19 +020027#include <linux/slab.h>
28#include <linux/sysfs.h>
29
30#include "coreboot_table.h"
31#include "vpd_decode.h"
32
33#define CB_TAG_VPD 0x2c
34#define VPD_CBMEM_MAGIC 0x43524f53
35
36static struct kobject *vpd_kobj;
37
38struct vpd_cbmem {
39 u32 magic;
40 u32 version;
41 u32 ro_size;
42 u32 rw_size;
43 u8 blob[0];
44};
45
46struct vpd_section {
47 bool enabled;
48 const char *name;
49 char *raw_name; /* the string name_raw */
50 struct kobject *kobj; /* vpd/name directory */
51 char *baseaddr;
52 struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
53 struct list_head attribs; /* key/value in vpd_attrib_info list */
54};
55
56struct vpd_attrib_info {
57 char *key;
58 const char *value;
59 struct bin_attribute bin_attr;
60 struct list_head list;
61};
62
63static struct vpd_section ro_vpd;
64static struct vpd_section rw_vpd;
65
66static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
67 struct bin_attribute *bin_attr, char *buf,
68 loff_t pos, size_t count)
69{
70 struct vpd_attrib_info *info = bin_attr->private;
71
72 return memory_read_from_buffer(buf, count, &pos, info->value,
73 info->bin_attr.size);
74}
75
76/*
77 * vpd_section_check_key_name()
78 *
79 * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
80 * old firmware versions may have entries like "S/N" which are problematic when
81 * exporting them as sysfs attributes. These keys present in old firmwares are
82 * ignored.
83 *
84 * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
85 *
86 * @key: The key name to check
87 * @key_len: key name length
88 */
89static int vpd_section_check_key_name(const u8 *key, s32 key_len)
90{
91 int c;
92
93 while (key_len-- > 0) {
94 c = *key++;
95
96 if (!isalnum(c) && c != '_')
97 return VPD_FAIL;
98 }
99
100 return VPD_OK;
101}
102
103static int vpd_section_attrib_add(const u8 *key, s32 key_len,
104 const u8 *value, s32 value_len,
105 void *arg)
106{
107 int ret;
108 struct vpd_section *sec = arg;
109 struct vpd_attrib_info *info;
110
111 /*
112 * Return VPD_OK immediately to decode next entry if the current key
113 * name contains invalid characters.
114 */
115 if (vpd_section_check_key_name(key, key_len) != VPD_OK)
116 return VPD_OK;
117
118 info = kzalloc(sizeof(*info), GFP_KERNEL);
Christophe JAILLET9434cec2017-05-05 21:08:44 +0200119 if (!info)
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200120 return -ENOMEM;
Dmitry Torokhov3eec6a12017-05-23 17:07:42 -0700121
122 info->key = kstrndup(key, key_len, GFP_KERNEL);
Christophe JAILLET9434cec2017-05-05 21:08:44 +0200123 if (!info->key) {
124 ret = -ENOMEM;
125 goto free_info;
126 }
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200127
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200128 sysfs_bin_attr_init(&info->bin_attr);
129 info->bin_attr.attr.name = info->key;
130 info->bin_attr.attr.mode = 0444;
131 info->bin_attr.size = value_len;
132 info->bin_attr.read = vpd_attrib_read;
133 info->bin_attr.private = info;
134
135 info->value = value;
136
137 INIT_LIST_HEAD(&info->list);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200138
139 ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
Christophe JAILLET9434cec2017-05-05 21:08:44 +0200140 if (ret)
141 goto free_info_key;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200142
Dmitry Torokhov7f53de22017-05-23 17:07:41 -0700143 list_add_tail(&info->list, &sec->attribs);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200144 return 0;
Christophe JAILLET9434cec2017-05-05 21:08:44 +0200145
146free_info_key:
147 kfree(info->key);
148free_info:
149 kfree(info);
150
151 return ret;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200152}
153
154static void vpd_section_attrib_destroy(struct vpd_section *sec)
155{
156 struct vpd_attrib_info *info;
157 struct vpd_attrib_info *temp;
158
159 list_for_each_entry_safe(info, temp, &sec->attribs, list) {
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200160 sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
Dmitry Torokhovce57cba2017-05-23 17:07:43 -0700161 kfree(info->key);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200162 kfree(info);
163 }
164}
165
166static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
167 struct bin_attribute *bin_attr, char *buf,
168 loff_t pos, size_t count)
169{
170 struct vpd_section *sec = bin_attr->private;
171
172 return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
173 sec->bin_attr.size);
174}
175
176static int vpd_section_create_attribs(struct vpd_section *sec)
177{
178 s32 consumed;
179 int ret;
180
181 consumed = 0;
182 do {
183 ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
184 &consumed, vpd_section_attrib_add, sec);
185 } while (ret == VPD_OK);
186
187 return 0;
188}
189
190static int vpd_section_init(const char *name, struct vpd_section *sec,
191 phys_addr_t physaddr, size_t size)
192{
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700193 int err;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200194
195 sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
196 if (!sec->baseaddr)
197 return -ENOMEM;
198
199 sec->name = name;
200
201 /* We want to export the raw partion with name ${name}_raw */
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700202 sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
203 if (!sec->raw_name) {
204 err = -ENOMEM;
Pan Bian75e5bae62017-08-11 07:13:13 +0800205 goto err_memunmap;
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700206 }
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200207
208 sysfs_bin_attr_init(&sec->bin_attr);
209 sec->bin_attr.attr.name = sec->raw_name;
210 sec->bin_attr.attr.mode = 0444;
211 sec->bin_attr.size = size;
212 sec->bin_attr.read = vpd_section_read;
213 sec->bin_attr.private = sec;
214
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700215 err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
216 if (err)
217 goto err_free_raw_name;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200218
219 sec->kobj = kobject_create_and_add(name, vpd_kobj);
220 if (!sec->kobj) {
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700221 err = -EINVAL;
222 goto err_sysfs_remove;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200223 }
224
225 INIT_LIST_HEAD(&sec->attribs);
226 vpd_section_create_attribs(sec);
227
228 sec->enabled = true;
229
230 return 0;
231
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700232err_sysfs_remove:
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200233 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700234err_free_raw_name:
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200235 kfree(sec->raw_name);
Pan Bian75e5bae62017-08-11 07:13:13 +0800236err_memunmap:
237 memunmap(sec->baseaddr);
Dmitry Torokhov9920a332017-05-23 17:07:45 -0700238 return err;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200239}
240
241static int vpd_section_destroy(struct vpd_section *sec)
242{
243 if (sec->enabled) {
244 vpd_section_attrib_destroy(sec);
Dmitry Torokhov2ee1cc72017-05-23 17:07:44 -0700245 kobject_put(sec->kobj);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200246 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
247 kfree(sec->raw_name);
Pan Bian75e5bae62017-08-11 07:13:13 +0800248 memunmap(sec->baseaddr);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200249 }
250
251 return 0;
252}
253
254static int vpd_sections_init(phys_addr_t physaddr)
255{
256 struct vpd_cbmem __iomem *temp;
257 struct vpd_cbmem header;
258 int ret = 0;
259
260 temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
261 if (!temp)
262 return -ENOMEM;
263
264 memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
Pan Bian75e5bae62017-08-11 07:13:13 +0800265 memunmap(temp);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200266
267 if (header.magic != VPD_CBMEM_MAGIC)
268 return -ENODEV;
269
270 if (header.ro_size) {
271 ret = vpd_section_init("ro", &ro_vpd,
272 physaddr + sizeof(struct vpd_cbmem),
273 header.ro_size);
274 if (ret)
275 return ret;
276 }
277
278 if (header.rw_size) {
279 ret = vpd_section_init("rw", &rw_vpd,
280 physaddr + sizeof(struct vpd_cbmem) +
281 header.ro_size, header.rw_size);
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700282 if (ret)
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200283 return ret;
284 }
285
286 return 0;
287}
288
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700289static int vpd_probe(struct platform_device *pdev)
290{
291 int ret;
292 struct lb_cbmem_ref entry;
293
294 ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry));
295 if (ret)
296 return ret;
297
Guenter Roecke4b28b32017-11-15 13:00:44 -0800298 vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
299 if (!vpd_kobj)
300 return -ENOMEM;
301
302 ret = vpd_sections_init(entry.cbmem_addr);
303 if (ret) {
304 kobject_put(vpd_kobj);
305 return ret;
306 }
307
308 return 0;
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700309}
310
Guenter Roeck811d7e02017-11-15 13:00:43 -0800311static int vpd_remove(struct platform_device *pdev)
312{
313 vpd_section_destroy(&ro_vpd);
314 vpd_section_destroy(&rw_vpd);
315
Guenter Roecke4b28b32017-11-15 13:00:44 -0800316 kobject_put(vpd_kobj);
317
Guenter Roeck811d7e02017-11-15 13:00:43 -0800318 return 0;
319}
320
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700321static struct platform_driver vpd_driver = {
322 .probe = vpd_probe,
Guenter Roeck811d7e02017-11-15 13:00:43 -0800323 .remove = vpd_remove,
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700324 .driver = {
325 .name = "vpd",
326 },
327};
328
Guenter Roeck0631fb82017-11-15 13:00:45 -0800329static struct platform_device *vpd_pdev;
330
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200331static int __init vpd_platform_init(void)
332{
Guenter Roeck0631fb82017-11-15 13:00:45 -0800333 int ret;
Dmitry Torokhov46505c82017-05-26 13:57:49 -0700334
Guenter Roeck0631fb82017-11-15 13:00:45 -0800335 ret = platform_driver_register(&vpd_driver);
336 if (ret)
337 return ret;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200338
Guenter Roeck0631fb82017-11-15 13:00:45 -0800339 vpd_pdev = platform_device_register_simple("vpd", -1, NULL, 0);
340 if (IS_ERR(vpd_pdev)) {
341 platform_driver_unregister(&vpd_driver);
342 return PTR_ERR(vpd_pdev);
343 }
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200344
345 return 0;
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200346}
347
348static void __exit vpd_platform_exit(void)
349{
Guenter Roeck0631fb82017-11-15 13:00:45 -0800350 platform_device_unregister(vpd_pdev);
351 platform_driver_unregister(&vpd_driver);
Wei-Ning Huang049a59d2017-04-12 18:56:19 +0200352}
353
354module_init(vpd_platform_init);
355module_exit(vpd_platform_exit);
356
357MODULE_AUTHOR("Google, Inc.");
358MODULE_LICENSE("GPL");