1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * coreboot_table.c |
4 | * |
5 | * Module providing coreboot table access. |
6 | * |
7 | * Copyright 2017 Google Inc. |
8 | * Copyright 2017 Samuel Holland <samuel@sholland.org> |
9 | */ |
10 | |
11 | #include <linux/acpi.h> |
12 | #include <linux/device.h> |
13 | #include <linux/err.h> |
14 | #include <linux/init.h> |
15 | #include <linux/io.h> |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> |
18 | #include <linux/of.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/slab.h> |
21 | |
22 | #include "coreboot_table.h" |
23 | |
24 | #define CB_DEV(d) container_of(d, struct coreboot_device, dev) |
25 | #define CB_DRV(d) container_of(d, struct coreboot_driver, drv) |
26 | |
27 | static int coreboot_bus_match(struct device *dev, struct device_driver *drv) |
28 | { |
29 | struct coreboot_device *device = CB_DEV(dev); |
30 | struct coreboot_driver *driver = CB_DRV(drv); |
31 | const struct coreboot_device_id *id; |
32 | |
33 | if (!driver->id_table) |
34 | return 0; |
35 | |
36 | for (id = driver->id_table; id->tag; id++) { |
37 | if (device->entry.tag == id->tag) |
38 | return 1; |
39 | } |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | static int coreboot_bus_probe(struct device *dev) |
45 | { |
46 | int ret = -ENODEV; |
47 | struct coreboot_device *device = CB_DEV(dev); |
48 | struct coreboot_driver *driver = CB_DRV(dev->driver); |
49 | |
50 | if (driver->probe) |
51 | ret = driver->probe(device); |
52 | |
53 | return ret; |
54 | } |
55 | |
56 | static void coreboot_bus_remove(struct device *dev) |
57 | { |
58 | struct coreboot_device *device = CB_DEV(dev); |
59 | struct coreboot_driver *driver = CB_DRV(dev->driver); |
60 | |
61 | if (driver->remove) |
62 | driver->remove(device); |
63 | } |
64 | |
65 | static int coreboot_bus_uevent(const struct device *dev, struct kobj_uevent_env *env) |
66 | { |
67 | struct coreboot_device *device = CB_DEV(dev); |
68 | u32 tag = device->entry.tag; |
69 | |
70 | return add_uevent_var(env, format: "MODALIAS=coreboot:t%08X" , tag); |
71 | } |
72 | |
73 | static const struct bus_type coreboot_bus_type = { |
74 | .name = "coreboot" , |
75 | .match = coreboot_bus_match, |
76 | .probe = coreboot_bus_probe, |
77 | .remove = coreboot_bus_remove, |
78 | .uevent = coreboot_bus_uevent, |
79 | }; |
80 | |
81 | static void coreboot_device_release(struct device *dev) |
82 | { |
83 | struct coreboot_device *device = CB_DEV(dev); |
84 | |
85 | kfree(objp: device); |
86 | } |
87 | |
88 | int coreboot_driver_register(struct coreboot_driver *driver) |
89 | { |
90 | driver->drv.bus = &coreboot_bus_type; |
91 | |
92 | return driver_register(drv: &driver->drv); |
93 | } |
94 | EXPORT_SYMBOL(coreboot_driver_register); |
95 | |
96 | void coreboot_driver_unregister(struct coreboot_driver *driver) |
97 | { |
98 | driver_unregister(drv: &driver->drv); |
99 | } |
100 | EXPORT_SYMBOL(coreboot_driver_unregister); |
101 | |
102 | static int coreboot_table_populate(struct device *dev, void *ptr) |
103 | { |
104 | int i, ret; |
105 | void *ptr_entry; |
106 | struct coreboot_device *device; |
107 | struct coreboot_table_entry *entry; |
108 | struct coreboot_table_header * = ptr; |
109 | |
110 | ptr_entry = ptr + header->header_bytes; |
111 | for (i = 0; i < header->table_entries; i++) { |
112 | entry = ptr_entry; |
113 | |
114 | if (entry->size < sizeof(*entry)) { |
115 | dev_warn(dev, "coreboot table entry too small!\n" ); |
116 | return -EINVAL; |
117 | } |
118 | |
119 | device = kzalloc(size: sizeof(device->dev) + entry->size, GFP_KERNEL); |
120 | if (!device) |
121 | return -ENOMEM; |
122 | |
123 | device->dev.parent = dev; |
124 | device->dev.bus = &coreboot_bus_type; |
125 | device->dev.release = coreboot_device_release; |
126 | memcpy(device->raw, ptr_entry, entry->size); |
127 | |
128 | switch (device->entry.tag) { |
129 | case LB_TAG_CBMEM_ENTRY: |
130 | dev_set_name(dev: &device->dev, name: "cbmem-%08x" , |
131 | device->cbmem_entry.id); |
132 | break; |
133 | default: |
134 | dev_set_name(dev: &device->dev, name: "coreboot%d" , i); |
135 | break; |
136 | } |
137 | |
138 | ret = device_register(dev: &device->dev); |
139 | if (ret) { |
140 | put_device(dev: &device->dev); |
141 | return ret; |
142 | } |
143 | |
144 | ptr_entry += entry->size; |
145 | } |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int coreboot_table_probe(struct platform_device *pdev) |
151 | { |
152 | resource_size_t len; |
153 | struct coreboot_table_header *; |
154 | struct resource *res; |
155 | struct device *dev = &pdev->dev; |
156 | void *ptr; |
157 | int ret; |
158 | |
159 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
160 | if (!res) |
161 | return -EINVAL; |
162 | |
163 | len = resource_size(res); |
164 | if (!res->start || !len) |
165 | return -EINVAL; |
166 | |
167 | /* Check just the header first to make sure things are sane */ |
168 | header = memremap(offset: res->start, size: sizeof(*header), flags: MEMREMAP_WB); |
169 | if (!header) |
170 | return -ENOMEM; |
171 | |
172 | len = header->header_bytes + header->table_bytes; |
173 | ret = strncmp(header->signature, "LBIO" , sizeof(header->signature)); |
174 | memunmap(addr: header); |
175 | if (ret) { |
176 | dev_warn(dev, "coreboot table missing or corrupt!\n" ); |
177 | return -ENODEV; |
178 | } |
179 | |
180 | ptr = memremap(offset: res->start, size: len, flags: MEMREMAP_WB); |
181 | if (!ptr) |
182 | return -ENOMEM; |
183 | |
184 | ret = coreboot_table_populate(dev, ptr); |
185 | |
186 | memunmap(addr: ptr); |
187 | |
188 | return ret; |
189 | } |
190 | |
191 | static int __cb_dev_unregister(struct device *dev, void *dummy) |
192 | { |
193 | device_unregister(dev); |
194 | return 0; |
195 | } |
196 | |
197 | static void coreboot_table_remove(struct platform_device *pdev) |
198 | { |
199 | bus_for_each_dev(bus: &coreboot_bus_type, NULL, NULL, fn: __cb_dev_unregister); |
200 | } |
201 | |
202 | #ifdef CONFIG_ACPI |
203 | static const struct acpi_device_id cros_coreboot_acpi_match[] = { |
204 | { "GOOGCB00" , 0 }, |
205 | { "BOOT0000" , 0 }, |
206 | { } |
207 | }; |
208 | MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); |
209 | #endif |
210 | |
211 | #ifdef CONFIG_OF |
212 | static const struct of_device_id coreboot_of_match[] = { |
213 | { .compatible = "coreboot" }, |
214 | {} |
215 | }; |
216 | MODULE_DEVICE_TABLE(of, coreboot_of_match); |
217 | #endif |
218 | |
219 | static struct platform_driver coreboot_table_driver = { |
220 | .probe = coreboot_table_probe, |
221 | .remove_new = coreboot_table_remove, |
222 | .driver = { |
223 | .name = "coreboot_table" , |
224 | .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match), |
225 | .of_match_table = of_match_ptr(coreboot_of_match), |
226 | }, |
227 | }; |
228 | |
229 | static int __init coreboot_table_driver_init(void) |
230 | { |
231 | int ret; |
232 | |
233 | ret = bus_register(bus: &coreboot_bus_type); |
234 | if (ret) |
235 | return ret; |
236 | |
237 | ret = platform_driver_register(&coreboot_table_driver); |
238 | if (ret) { |
239 | bus_unregister(bus: &coreboot_bus_type); |
240 | return ret; |
241 | } |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static void __exit coreboot_table_driver_exit(void) |
247 | { |
248 | platform_driver_unregister(&coreboot_table_driver); |
249 | bus_unregister(bus: &coreboot_bus_type); |
250 | } |
251 | |
252 | module_init(coreboot_table_driver_init); |
253 | module_exit(coreboot_table_driver_exit); |
254 | |
255 | MODULE_AUTHOR("Google, Inc." ); |
256 | MODULE_LICENSE("GPL" ); |
257 | |