1d9523678SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2d384d6f4SThierry Escande /*
3d384d6f4SThierry Escande * coreboot_table.c
4d384d6f4SThierry Escande *
5d384d6f4SThierry Escande * Module providing coreboot table access.
6d384d6f4SThierry Escande *
7d384d6f4SThierry Escande * Copyright 2017 Google Inc.
8570d30c2SSamuel Holland * Copyright 2017 Samuel Holland <samuel@sholland.org>
9d384d6f4SThierry Escande */
10d384d6f4SThierry Escande
11a28aad66SStephen Boyd #include <linux/acpi.h>
12570d30c2SSamuel Holland #include <linux/device.h>
13d384d6f4SThierry Escande #include <linux/err.h>
14d384d6f4SThierry Escande #include <linux/init.h>
15d384d6f4SThierry Escande #include <linux/io.h>
16d384d6f4SThierry Escande #include <linux/kernel.h>
17d384d6f4SThierry Escande #include <linux/module.h>
18a28aad66SStephen Boyd #include <linux/of.h>
19a28aad66SStephen Boyd #include <linux/platform_device.h>
20570d30c2SSamuel Holland #include <linux/slab.h>
21d384d6f4SThierry Escande
22d384d6f4SThierry Escande #include "coreboot_table.h"
23d384d6f4SThierry Escande
24570d30c2SSamuel Holland #define CB_DEV(d) container_of(d, struct coreboot_device, dev)
25570d30c2SSamuel Holland #define CB_DRV(d) container_of(d, struct coreboot_driver, drv)
26d384d6f4SThierry Escande
coreboot_bus_match(struct device * dev,struct device_driver * drv)27570d30c2SSamuel Holland static int coreboot_bus_match(struct device *dev, struct device_driver *drv)
28570d30c2SSamuel Holland {
29570d30c2SSamuel Holland struct coreboot_device *device = CB_DEV(dev);
30570d30c2SSamuel Holland struct coreboot_driver *driver = CB_DRV(drv);
31570d30c2SSamuel Holland
32570d30c2SSamuel Holland return device->entry.tag == driver->tag;
33570d30c2SSamuel Holland }
34570d30c2SSamuel Holland
coreboot_bus_probe(struct device * dev)35570d30c2SSamuel Holland static int coreboot_bus_probe(struct device *dev)
36570d30c2SSamuel Holland {
37570d30c2SSamuel Holland int ret = -ENODEV;
38570d30c2SSamuel Holland struct coreboot_device *device = CB_DEV(dev);
39570d30c2SSamuel Holland struct coreboot_driver *driver = CB_DRV(dev->driver);
40570d30c2SSamuel Holland
41570d30c2SSamuel Holland if (driver->probe)
42570d30c2SSamuel Holland ret = driver->probe(device);
43570d30c2SSamuel Holland
44570d30c2SSamuel Holland return ret;
45570d30c2SSamuel Holland }
46570d30c2SSamuel Holland
coreboot_bus_remove(struct device * dev)47fc7a6209SUwe Kleine-König static void coreboot_bus_remove(struct device *dev)
48570d30c2SSamuel Holland {
49570d30c2SSamuel Holland struct coreboot_device *device = CB_DEV(dev);
50570d30c2SSamuel Holland struct coreboot_driver *driver = CB_DRV(dev->driver);
51570d30c2SSamuel Holland
52570d30c2SSamuel Holland if (driver->remove)
535f680532SUwe Kleine-König driver->remove(device);
54570d30c2SSamuel Holland }
55570d30c2SSamuel Holland
56570d30c2SSamuel Holland static struct bus_type coreboot_bus_type = {
57570d30c2SSamuel Holland .name = "coreboot",
58570d30c2SSamuel Holland .match = coreboot_bus_match,
59570d30c2SSamuel Holland .probe = coreboot_bus_probe,
60570d30c2SSamuel Holland .remove = coreboot_bus_remove,
61570d30c2SSamuel Holland };
62570d30c2SSamuel Holland
coreboot_device_release(struct device * dev)63570d30c2SSamuel Holland static void coreboot_device_release(struct device *dev)
64570d30c2SSamuel Holland {
65570d30c2SSamuel Holland struct coreboot_device *device = CB_DEV(dev);
66570d30c2SSamuel Holland
67570d30c2SSamuel Holland kfree(device);
68570d30c2SSamuel Holland }
69570d30c2SSamuel Holland
coreboot_driver_register(struct coreboot_driver * driver)70570d30c2SSamuel Holland int coreboot_driver_register(struct coreboot_driver *driver)
71570d30c2SSamuel Holland {
72570d30c2SSamuel Holland driver->drv.bus = &coreboot_bus_type;
73570d30c2SSamuel Holland
74570d30c2SSamuel Holland return driver_register(&driver->drv);
75570d30c2SSamuel Holland }
76570d30c2SSamuel Holland EXPORT_SYMBOL(coreboot_driver_register);
77570d30c2SSamuel Holland
coreboot_driver_unregister(struct coreboot_driver * driver)78570d30c2SSamuel Holland void coreboot_driver_unregister(struct coreboot_driver *driver)
79570d30c2SSamuel Holland {
80570d30c2SSamuel Holland driver_unregister(&driver->drv);
81570d30c2SSamuel Holland }
82570d30c2SSamuel Holland EXPORT_SYMBOL(coreboot_driver_unregister);
83570d30c2SSamuel Holland
coreboot_table_populate(struct device * dev,void * ptr)847adb05bbSStephen Boyd static int coreboot_table_populate(struct device *dev, void *ptr)
85d384d6f4SThierry Escande {
86570d30c2SSamuel Holland int i, ret;
87570d30c2SSamuel Holland void *ptr_entry;
88570d30c2SSamuel Holland struct coreboot_device *device;
89a7d9b5f0SStephen Boyd struct coreboot_table_entry *entry;
907adb05bbSStephen Boyd struct coreboot_table_header *header = ptr;
91d384d6f4SThierry Escande
927adb05bbSStephen Boyd ptr_entry = ptr + header->header_bytes;
93a7d9b5f0SStephen Boyd for (i = 0; i < header->table_entries; i++) {
94a7d9b5f0SStephen Boyd entry = ptr_entry;
95570d30c2SSamuel Holland
96*3b293487SKees Cook if (entry->size < sizeof(*entry)) {
97*3b293487SKees Cook dev_warn(dev, "coreboot table entry too small!\n");
98*3b293487SKees Cook return -EINVAL;
99*3b293487SKees Cook }
100*3b293487SKees Cook
101*3b293487SKees Cook device = kzalloc(sizeof(device->dev) + entry->size, GFP_KERNEL);
1027adb05bbSStephen Boyd if (!device)
1037adb05bbSStephen Boyd return -ENOMEM;
104570d30c2SSamuel Holland
105570d30c2SSamuel Holland device->dev.parent = dev;
106570d30c2SSamuel Holland device->dev.bus = &coreboot_bus_type;
107570d30c2SSamuel Holland device->dev.release = coreboot_device_release;
108*3b293487SKees Cook memcpy(device->raw, ptr_entry, entry->size);
109570d30c2SSamuel Holland
11019d54020SJack Rosenthal switch (device->entry.tag) {
11119d54020SJack Rosenthal case LB_TAG_CBMEM_ENTRY:
11219d54020SJack Rosenthal dev_set_name(&device->dev, "cbmem-%08x",
11319d54020SJack Rosenthal device->cbmem_entry.id);
11419d54020SJack Rosenthal break;
11519d54020SJack Rosenthal default:
11619d54020SJack Rosenthal dev_set_name(&device->dev, "coreboot%d", i);
11719d54020SJack Rosenthal break;
11819d54020SJack Rosenthal }
11919d54020SJack Rosenthal
120570d30c2SSamuel Holland ret = device_register(&device->dev);
121570d30c2SSamuel Holland if (ret) {
122570d30c2SSamuel Holland put_device(&device->dev);
1237adb05bbSStephen Boyd return ret;
124570d30c2SSamuel Holland }
125570d30c2SSamuel Holland
126a7d9b5f0SStephen Boyd ptr_entry += entry->size;
127570d30c2SSamuel Holland }
128b81e3140SStephen Boyd
1297adb05bbSStephen Boyd return 0;
130d384d6f4SThierry Escande }
131d384d6f4SThierry Escande
coreboot_table_probe(struct platform_device * pdev)132a28aad66SStephen Boyd static int coreboot_table_probe(struct platform_device *pdev)
133a28aad66SStephen Boyd {
134a28aad66SStephen Boyd resource_size_t len;
135a7d9b5f0SStephen Boyd struct coreboot_table_header *header;
136a28aad66SStephen Boyd struct resource *res;
1377adb05bbSStephen Boyd struct device *dev = &pdev->dev;
138a7d9b5f0SStephen Boyd void *ptr;
1397adb05bbSStephen Boyd int ret;
140a28aad66SStephen Boyd
141a28aad66SStephen Boyd res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
142a28aad66SStephen Boyd if (!res)
143a28aad66SStephen Boyd return -EINVAL;
144a28aad66SStephen Boyd
145a28aad66SStephen Boyd len = resource_size(res);
146a28aad66SStephen Boyd if (!res->start || !len)
147a28aad66SStephen Boyd return -EINVAL;
148a28aad66SStephen Boyd
1497adb05bbSStephen Boyd /* Check just the header first to make sure things are sane */
150a7d9b5f0SStephen Boyd header = memremap(res->start, sizeof(*header), MEMREMAP_WB);
1517adb05bbSStephen Boyd if (!header)
152a28aad66SStephen Boyd return -ENOMEM;
153a28aad66SStephen Boyd
1547adb05bbSStephen Boyd len = header->header_bytes + header->table_bytes;
1557adb05bbSStephen Boyd ret = strncmp(header->signature, "LBIO", sizeof(header->signature));
156a7d9b5f0SStephen Boyd memunmap(header);
1577adb05bbSStephen Boyd if (ret) {
1587adb05bbSStephen Boyd dev_warn(dev, "coreboot table missing or corrupt!\n");
1597adb05bbSStephen Boyd return -ENODEV;
1607adb05bbSStephen Boyd }
1617adb05bbSStephen Boyd
1627adb05bbSStephen Boyd ptr = memremap(res->start, len, MEMREMAP_WB);
163a28aad66SStephen Boyd if (!ptr)
164a28aad66SStephen Boyd return -ENOMEM;
165a28aad66SStephen Boyd
1667adb05bbSStephen Boyd ret = coreboot_table_populate(dev, ptr);
16765946690SBrian Norris
1687adb05bbSStephen Boyd memunmap(ptr);
1697adb05bbSStephen Boyd
1707adb05bbSStephen Boyd return ret;
171a28aad66SStephen Boyd }
172a28aad66SStephen Boyd
__cb_dev_unregister(struct device * dev,void * dummy)173cae0970eSPatrick Rudolph static int __cb_dev_unregister(struct device *dev, void *dummy)
174cae0970eSPatrick Rudolph {
175cae0970eSPatrick Rudolph device_unregister(dev);
176cae0970eSPatrick Rudolph return 0;
177cae0970eSPatrick Rudolph }
178cae0970eSPatrick Rudolph
coreboot_table_remove(struct platform_device * pdev)179a28aad66SStephen Boyd static int coreboot_table_remove(struct platform_device *pdev)
180d384d6f4SThierry Escande {
181cae0970eSPatrick Rudolph bus_for_each_dev(&coreboot_bus_type, NULL, NULL, __cb_dev_unregister);
182d384d6f4SThierry Escande return 0;
183d384d6f4SThierry Escande }
184d384d6f4SThierry Escande
185a28aad66SStephen Boyd #ifdef CONFIG_ACPI
186a28aad66SStephen Boyd static const struct acpi_device_id cros_coreboot_acpi_match[] = {
187a28aad66SStephen Boyd { "GOOGCB00", 0 },
188a28aad66SStephen Boyd { "BOOT0000", 0 },
189a28aad66SStephen Boyd { }
190a28aad66SStephen Boyd };
191a28aad66SStephen Boyd MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
192a28aad66SStephen Boyd #endif
193a28aad66SStephen Boyd
194a28aad66SStephen Boyd #ifdef CONFIG_OF
195a28aad66SStephen Boyd static const struct of_device_id coreboot_of_match[] = {
196a28aad66SStephen Boyd { .compatible = "coreboot" },
197a28aad66SStephen Boyd {}
198a28aad66SStephen Boyd };
199a28aad66SStephen Boyd MODULE_DEVICE_TABLE(of, coreboot_of_match);
200a28aad66SStephen Boyd #endif
201a28aad66SStephen Boyd
202a28aad66SStephen Boyd static struct platform_driver coreboot_table_driver = {
203a28aad66SStephen Boyd .probe = coreboot_table_probe,
204a28aad66SStephen Boyd .remove = coreboot_table_remove,
205a28aad66SStephen Boyd .driver = {
206a28aad66SStephen Boyd .name = "coreboot_table",
207a28aad66SStephen Boyd .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
208a28aad66SStephen Boyd .of_match_table = of_match_ptr(coreboot_of_match),
209a28aad66SStephen Boyd },
210a28aad66SStephen Boyd };
21165946690SBrian Norris
coreboot_table_driver_init(void)21265946690SBrian Norris static int __init coreboot_table_driver_init(void)
21365946690SBrian Norris {
21465946690SBrian Norris int ret;
21565946690SBrian Norris
21665946690SBrian Norris ret = bus_register(&coreboot_bus_type);
21765946690SBrian Norris if (ret)
21865946690SBrian Norris return ret;
21965946690SBrian Norris
22065946690SBrian Norris ret = platform_driver_register(&coreboot_table_driver);
22165946690SBrian Norris if (ret) {
22265946690SBrian Norris bus_unregister(&coreboot_bus_type);
22365946690SBrian Norris return ret;
22465946690SBrian Norris }
22565946690SBrian Norris
22665946690SBrian Norris return 0;
22765946690SBrian Norris }
22865946690SBrian Norris
coreboot_table_driver_exit(void)22965946690SBrian Norris static void __exit coreboot_table_driver_exit(void)
23065946690SBrian Norris {
23165946690SBrian Norris platform_driver_unregister(&coreboot_table_driver);
23265946690SBrian Norris bus_unregister(&coreboot_bus_type);
23365946690SBrian Norris }
23465946690SBrian Norris
23565946690SBrian Norris module_init(coreboot_table_driver_init);
23665946690SBrian Norris module_exit(coreboot_table_driver_exit);
23765946690SBrian Norris
238d384d6f4SThierry Escande MODULE_AUTHOR("Google, Inc.");
239d384d6f4SThierry Escande MODULE_LICENSE("GPL");
240