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