]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blame - drivers/firmware/google/vpd.c
Merge remote-tracking branches 'asoc/topic/msm8916', 'asoc/topic/mtk', 'asoc/topic...
[mirror_ubuntu-jammy-kernel.git] / drivers / firmware / google / vpd.c
CommitLineData
049a59db
WNH
1/*
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>
25#include <linux/of_address.h>
26#include <linux/platform_device.h>
27#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);
9434cec1 119 if (!info)
049a59db 120 return -ENOMEM;
9434cec1
CJ
121 info->key = kzalloc(key_len + 1, GFP_KERNEL);
122 if (!info->key) {
123 ret = -ENOMEM;
124 goto free_info;
125 }
049a59db
WNH
126
127 memcpy(info->key, key, key_len);
128
129 sysfs_bin_attr_init(&info->bin_attr);
130 info->bin_attr.attr.name = info->key;
131 info->bin_attr.attr.mode = 0444;
132 info->bin_attr.size = value_len;
133 info->bin_attr.read = vpd_attrib_read;
134 info->bin_attr.private = info;
135
136 info->value = value;
137
138 INIT_LIST_HEAD(&info->list);
049a59db
WNH
139
140 ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
9434cec1
CJ
141 if (ret)
142 goto free_info_key;
049a59db 143
7f53de20 144 list_add_tail(&info->list, &sec->attribs);
049a59db 145 return 0;
9434cec1
CJ
146
147free_info_key:
148 kfree(info->key);
149free_info:
150 kfree(info);
151
152 return ret;
049a59db
WNH
153}
154
155static void vpd_section_attrib_destroy(struct vpd_section *sec)
156{
157 struct vpd_attrib_info *info;
158 struct vpd_attrib_info *temp;
159
160 list_for_each_entry_safe(info, temp, &sec->attribs, list) {
049a59db 161 sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
ce57cba3 162 kfree(info->key);
049a59db
WNH
163 kfree(info);
164 }
165}
166
167static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
168 struct bin_attribute *bin_attr, char *buf,
169 loff_t pos, size_t count)
170{
171 struct vpd_section *sec = bin_attr->private;
172
173 return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
174 sec->bin_attr.size);
175}
176
177static int vpd_section_create_attribs(struct vpd_section *sec)
178{
179 s32 consumed;
180 int ret;
181
182 consumed = 0;
183 do {
184 ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
185 &consumed, vpd_section_attrib_add, sec);
186 } while (ret == VPD_OK);
187
188 return 0;
189}
190
191static int vpd_section_init(const char *name, struct vpd_section *sec,
192 phys_addr_t physaddr, size_t size)
193{
194 int ret;
195 int raw_len;
196
197 sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
198 if (!sec->baseaddr)
199 return -ENOMEM;
200
201 sec->name = name;
202
203 /* We want to export the raw partion with name ${name}_raw */
204 raw_len = strlen(name) + 5;
205 sec->raw_name = kzalloc(raw_len, GFP_KERNEL);
206 strncpy(sec->raw_name, name, raw_len);
207 strncat(sec->raw_name, "_raw", raw_len);
208
209 sysfs_bin_attr_init(&sec->bin_attr);
210 sec->bin_attr.attr.name = sec->raw_name;
211 sec->bin_attr.attr.mode = 0444;
212 sec->bin_attr.size = size;
213 sec->bin_attr.read = vpd_section_read;
214 sec->bin_attr.private = sec;
215
216 ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
217 if (ret)
218 goto free_sec;
219
220 sec->kobj = kobject_create_and_add(name, vpd_kobj);
221 if (!sec->kobj) {
222 ret = -EINVAL;
223 goto sysfs_remove;
224 }
225
226 INIT_LIST_HEAD(&sec->attribs);
227 vpd_section_create_attribs(sec);
228
229 sec->enabled = true;
230
231 return 0;
232
233sysfs_remove:
234 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
235
236free_sec:
237 kfree(sec->raw_name);
238 iounmap(sec->baseaddr);
239
240 return ret;
241}
242
243static int vpd_section_destroy(struct vpd_section *sec)
244{
245 if (sec->enabled) {
246 vpd_section_attrib_destroy(sec);
2ee1cc70 247 kobject_put(sec->kobj);
049a59db
WNH
248 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
249 kfree(sec->raw_name);
250 iounmap(sec->baseaddr);
251 }
252
253 return 0;
254}
255
256static int vpd_sections_init(phys_addr_t physaddr)
257{
258 struct vpd_cbmem __iomem *temp;
259 struct vpd_cbmem header;
260 int ret = 0;
261
262 temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
263 if (!temp)
264 return -ENOMEM;
265
266 memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
267 iounmap(temp);
268
269 if (header.magic != VPD_CBMEM_MAGIC)
270 return -ENODEV;
271
272 if (header.ro_size) {
273 ret = vpd_section_init("ro", &ro_vpd,
274 physaddr + sizeof(struct vpd_cbmem),
275 header.ro_size);
276 if (ret)
277 return ret;
278 }
279
280 if (header.rw_size) {
281 ret = vpd_section_init("rw", &rw_vpd,
282 physaddr + sizeof(struct vpd_cbmem) +
283 header.ro_size, header.rw_size);
284 if (ret)
285 return ret;
286 }
287
288 return 0;
289}
290
291static int vpd_probe(struct platform_device *pdev)
292{
293 int ret;
294 struct lb_cbmem_ref entry;
295
296 ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry));
297 if (ret)
298 return ret;
299
300 return vpd_sections_init(entry.cbmem_addr);
301}
302
303static struct platform_driver vpd_driver = {
304 .probe = vpd_probe,
305 .driver = {
306 .name = "vpd",
307 },
308};
309
310static int __init vpd_platform_init(void)
311{
312 struct platform_device *pdev;
313
314 pdev = platform_device_register_simple("vpd", -1, NULL, 0);
856c634d
WY
315 if (IS_ERR(pdev))
316 return PTR_ERR(pdev);
049a59db
WNH
317
318 vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
319 if (!vpd_kobj)
320 return -ENOMEM;
321
322 memset(&ro_vpd, 0, sizeof(ro_vpd));
323 memset(&rw_vpd, 0, sizeof(rw_vpd));
324
325 platform_driver_register(&vpd_driver);
326
327 return 0;
328}
329
330static void __exit vpd_platform_exit(void)
331{
332 vpd_section_destroy(&ro_vpd);
333 vpd_section_destroy(&rw_vpd);
2ee1cc70 334 kobject_put(vpd_kobj);
049a59db
WNH
335}
336
337module_init(vpd_platform_init);
338module_exit(vpd_platform_exit);
339
340MODULE_AUTHOR("Google, Inc.");
341MODULE_LICENSE("GPL");