On many systems, coreboot[1] firmware can initialize graphics hardware and set up a high-resolution linear framebuffer. It exports information about this framebuffer, along with various other information, in a table discoverable via ACPI or a device tree.
coreboot also supports booting Linux directly from flash as a "payload". Projects such as Heads[2], u-root[3], and petitboot[4] provide a minimal userland that can then be used to chainload (via kexec) into a full Linux system loaded from disk or over the network.
Fitting even a minimal Linux system on an SPI flash chip is challenging. Reusing the framebuffer setup from coreboot provides an enormous benefit to these projects by allowing them to omit full graphics drivers from their kernel builds. It also speeds up boot times by avoiding duplicated effort, and because coreboot's graphics initialization is often much faster than the Linux driver.
Patch 1 of this series expands coreboot table support into an enumerable bus that devices can hang off of. Patches 2-3 convert the existing drivers to use the new bus structure instead of ad-hoc platform devices, and patch 4 removes the old coreboot_table_find function.
Finally, patch 5 adds a new driver for the coreboot-initialized framebuffer. It improves on earlier work[5] by being architecture- independent and not needing to scan through low memory.
This patchset has been tested on a Lenovo ThinkPad X220, and earlier versions of these patches have been tested by various members of the coreboot community on other hardware.
[1]: https://www.coreboot.org/Welcome_to_coreboot [2]: https://github.com/osresearch/heads [3]: https://github.com/u-root/u-root [4]: https://www.kernel.org/pub/linux/kernel/people/geoff/petitboot/petitboot.htm... [5]: https://mail.coreboot.org/pipermail/coreboot/2014-September/078551.html
Samuel Holland (5): firmware: coreboot: Expose the coreboot table as a bus firmware: memconsole: Probe via coreboot bus firmware: vpd: Probe via coreboot bus firmware: coreboot: Remove unused coreboot_table_find firmware: coreboot: Add coreboot framebuffer driver
drivers/firmware/google/Kconfig | 8 ++ drivers/firmware/google/Makefile | 1 + drivers/firmware/google/coreboot_table-acpi.c | 2 +- drivers/firmware/google/coreboot_table-of.c | 2 +- drivers/firmware/google/coreboot_table.c | 130 ++++++++++++++++++------- drivers/firmware/google/coreboot_table.h | 72 +++++++++++--- drivers/firmware/google/framebuffer-coreboot.c | 115 ++++++++++++++++++++++ drivers/firmware/google/memconsole-coreboot.c | 49 ++++------ drivers/firmware/google/vpd.c | 43 +++----- 9 files changed, 313 insertions(+), 109 deletions(-) create mode 100644 drivers/firmware/google/framebuffer-coreboot.c
This simplifies creating device drivers for hardware or information described in the coreboot table. It also avoids needing to search through the table every time a driver is loaded.
Signed-off-by: Samuel Holland samuel@sholland.org --- drivers/firmware/google/coreboot_table-acpi.c | 2 +- drivers/firmware/google/coreboot_table-of.c | 2 +- drivers/firmware/google/coreboot_table.c | 121 ++++++++++++++++++++++++-- drivers/firmware/google/coreboot_table.h | 49 +++++++++-- 4 files changed, 156 insertions(+), 18 deletions(-)
diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c index fb98db2d20e2..77197fe3d42f 100644 --- a/drivers/firmware/google/coreboot_table-acpi.c +++ b/drivers/firmware/google/coreboot_table-acpi.c @@ -53,7 +53,7 @@ static int coreboot_table_acpi_probe(struct platform_device *pdev) if (!ptr) return -ENOMEM;
- return coreboot_table_init(ptr); + return coreboot_table_init(&pdev->dev, ptr); }
static int coreboot_table_acpi_remove(struct platform_device *pdev) diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c index 727acdc83e83..f15bf404c579 100644 --- a/drivers/firmware/google/coreboot_table-of.c +++ b/drivers/firmware/google/coreboot_table-of.c @@ -34,7 +34,7 @@ static int coreboot_table_of_probe(struct platform_device *pdev) if (!ptr) return -ENOMEM;
- return coreboot_table_init(ptr); + return coreboot_table_init(&pdev->dev, ptr); }
static int coreboot_table_of_remove(struct platform_device *pdev) diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c index 0019d3ec18dd..04fc08e81744 100644 --- a/drivers/firmware/google/coreboot_table.c +++ b/drivers/firmware/google/coreboot_table.c @@ -4,6 +4,7 @@ * Module providing coreboot table access. * * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland samuel@sholland.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License v2.0 as published by @@ -15,21 +16,87 @@ * GNU General Public License for more details. */
+#include <linux/device.h> #include <linux/err.h> #include <linux/init.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/slab.h>
#include "coreboot_table.h"
-struct coreboot_table_entry { - u32 tag; - u32 size; -}; +#define CB_DEV(d) container_of(d, struct coreboot_device, dev) +#define CB_DRV(d) container_of(d, struct coreboot_driver, drv)
static struct coreboot_table_header __iomem *ptr_header;
+static int coreboot_bus_match(struct device *dev, struct device_driver *drv) +{ + struct coreboot_device *device = CB_DEV(dev); + struct coreboot_driver *driver = CB_DRV(drv); + + return device->entry.tag == driver->tag; +} + +static int coreboot_bus_probe(struct device *dev) +{ + int ret = -ENODEV; + struct coreboot_device *device = CB_DEV(dev); + struct coreboot_driver *driver = CB_DRV(dev->driver); + + if (driver->probe) + ret = driver->probe(device); + + return ret; +} + +static int coreboot_bus_remove(struct device *dev) +{ + int ret = 0; + struct coreboot_device *device = CB_DEV(dev); + struct coreboot_driver *driver = CB_DRV(dev->driver); + + if (driver->remove) + ret = driver->remove(device); + + return ret; +} + +static struct bus_type coreboot_bus_type = { + .name = "coreboot", + .match = coreboot_bus_match, + .probe = coreboot_bus_probe, + .remove = coreboot_bus_remove, +}; + +static int __init coreboot_bus_init(void) +{ + return bus_register(&coreboot_bus_type); +} +module_init(coreboot_bus_init); + +static void coreboot_device_release(struct device *dev) +{ + struct coreboot_device *device = CB_DEV(dev); + + kfree(device); +} + +int coreboot_driver_register(struct coreboot_driver *driver) +{ + driver->drv.bus = &coreboot_bus_type; + + return driver_register(&driver->drv); +} +EXPORT_SYMBOL(coreboot_driver_register); + +void coreboot_driver_unregister(struct coreboot_driver *driver) +{ + driver_unregister(&driver->drv); +} +EXPORT_SYMBOL(coreboot_driver_unregister); + /* * This function parses the coreboot table for an entry that contains the base * address of the given entry tag. The coreboot table consists of a header @@ -73,18 +140,58 @@ int coreboot_table_find(int tag, void *data, size_t data_size) } EXPORT_SYMBOL(coreboot_table_find);
-int coreboot_table_init(void __iomem *ptr) +int coreboot_table_init(struct device *dev, void __iomem *ptr) { + int i, ret; + void *ptr_entry; + struct coreboot_device *device; + struct coreboot_table_entry entry; + struct coreboot_table_header header; + ptr_header = ptr; + memcpy_fromio(&header, ptr_header, sizeof(header));
- return 0; + if (strncmp(header.signature, "LBIO", sizeof(header.signature))) { + pr_warn("coreboot_table: coreboot table missing or corrupt!\n"); + return -ENODEV; + } + + ptr_entry = (void *)ptr_header + header.header_bytes; + for (i = 0; i < header.table_entries; i++) { + memcpy_fromio(&entry, ptr_entry, sizeof(entry)); + + device = kzalloc(sizeof(struct device) + entry.size, GFP_KERNEL); + if (!device) { + ret = -ENOMEM; + break; + } + + dev_set_name(&device->dev, "coreboot%d", i); + device->dev.parent = dev; + device->dev.bus = &coreboot_bus_type; + device->dev.release = coreboot_device_release; + memcpy_fromio(&device->entry, ptr_entry, entry.size); + + ret = device_register(&device->dev); + if (ret) { + put_device(&device->dev); + break; + } + + ptr_entry += entry.size; + } + + return ret; } EXPORT_SYMBOL(coreboot_table_init);
int coreboot_table_exit(void) { - if (ptr_header) + if (ptr_header) { + bus_unregister(&coreboot_bus_type); iounmap(ptr_header); + ptr_header = NULL; + }
return 0; } diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h index 6eff1ae0c5d3..88e6a1c06028 100644 --- a/drivers/firmware/google/coreboot_table.h +++ b/drivers/firmware/google/coreboot_table.h @@ -4,6 +4,7 @@ * Internal header for coreboot table access. * * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland samuel@sholland.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License v2.0 as published by @@ -20,14 +21,6 @@
#include <linux/io.h>
-/* List of coreboot entry structures that is used */ -struct lb_cbmem_ref { - uint32_t tag; - uint32_t size; - - uint64_t cbmem_addr; -}; - /* Coreboot table header structure */ struct coreboot_table_header { char signature[4]; @@ -38,11 +31,49 @@ struct coreboot_table_header { u32 table_entries; };
+/* List of coreboot entry structures that is used */ +/* Generic */ +struct coreboot_table_entry { + u32 tag; + u32 size; +}; + +/* Points to a CBMEM entry */ +struct lb_cbmem_ref { + u32 tag; + u32 size; + + u64 cbmem_addr; +}; + +/* A device, additionally with information from coreboot. */ +struct coreboot_device { + struct device dev; + union { + struct coreboot_table_entry entry; + struct lb_cbmem_ref cbmem_ref; + }; +}; + +/* A driver for handling devices described in coreboot tables. */ +struct coreboot_driver { + int (*probe)(struct coreboot_device *); + int (*remove)(struct coreboot_device *); + struct device_driver drv; + u32 tag; +}; + +/* Register a driver that uses the data from a coreboot table. */ +int coreboot_driver_register(struct coreboot_driver *driver); + +/* Unregister a driver that uses the data from a coreboot table. */ +void coreboot_driver_unregister(struct coreboot_driver *driver); + /* Retrieve coreboot table entry with tag *tag* and copy it to data */ int coreboot_table_find(int tag, void *data, size_t data_size);
/* Initialize coreboot table module given a pointer to iomem */ -int coreboot_table_init(void __iomem *ptr); +int coreboot_table_init(struct device *dev, void __iomem *ptr);
/* Cleanup coreboot table module */ int coreboot_table_exit(void);
Remove the ad-hoc coreboot table search. Now the driver will only be probed when the necessary coreboot table entry has already been found.
Signed-off-by: Samuel Holland samuel@sholland.org --- drivers/firmware/google/memconsole-coreboot.c | 49 ++++++++++----------------- 1 file changed, 17 insertions(+), 32 deletions(-)
diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c index 52738887735c..b29e10757bfb 100644 --- a/drivers/firmware/google/memconsole-coreboot.c +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -15,9 +15,9 @@ * GNU General Public License for more details. */
+#include <linux/device.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/platform_device.h>
#include "memconsole.h" #include "coreboot_table.h" @@ -73,18 +73,19 @@ static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count) return done; }
-static int memconsole_coreboot_init(phys_addr_t physaddr) +static int memconsole_probe(struct coreboot_device *dev) { struct cbmem_cons __iomem *tmp_cbmc;
- tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB); + tmp_cbmc = memremap(dev->cbmem_ref.cbmem_addr, + sizeof(*tmp_cbmc), MEMREMAP_WB);
if (!tmp_cbmc) return -ENOMEM;
/* Read size only once to prevent overrun attack through /dev/mem. */ cbmem_console_size = tmp_cbmc->size_dont_access_after_boot; - cbmem_console = memremap(physaddr, + cbmem_console = memremap(dev->cbmem_ref.cbmem_addr, cbmem_console_size + sizeof(*cbmem_console), MEMREMAP_WB); memunmap(tmp_cbmc); @@ -93,26 +94,11 @@ static int memconsole_coreboot_init(phys_addr_t physaddr) return -ENOMEM;
memconsole_setup(memconsole_coreboot_read); - return 0; -} - -static int memconsole_probe(struct platform_device *pdev) -{ - int ret; - struct lb_cbmem_ref entry; - - ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry)); - if (ret) - return ret; - - ret = memconsole_coreboot_init(entry.cbmem_addr); - if (ret) - return ret;
return memconsole_sysfs_init(); }
-static int memconsole_remove(struct platform_device *pdev) +static int memconsole_remove(struct coreboot_device *dev) { memconsole_exit();
@@ -122,28 +108,27 @@ static int memconsole_remove(struct platform_device *pdev) return 0; }
-static struct platform_driver memconsole_driver = { +static struct coreboot_driver memconsole_driver = { .probe = memconsole_probe, .remove = memconsole_remove, - .driver = { + .drv = { .name = "memconsole", }, + .tag = CB_TAG_CBMEM_CONSOLE, };
-static int __init platform_memconsole_init(void) +static void coreboot_memconsole_exit(void) { - struct platform_device *pdev; - - pdev = platform_device_register_simple("memconsole", -1, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - platform_driver_register(&memconsole_driver); + coreboot_driver_unregister(&memconsole_driver); +}
- return 0; +static int __init coreboot_memconsole_init(void) +{ + return coreboot_driver_register(&memconsole_driver); }
-module_init(platform_memconsole_init); +module_exit(coreboot_memconsole_exit); +module_init(coreboot_memconsole_init);
MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL");
Remove the ad-hoc coreboot table search. Now the driver will only be probed when the necessary coreboot table entry has already been found. Furthermore, since the coreboot bus takes care of creating the device, a separate platform device is no longer needed.
Signed-off-by: Samuel Holland samuel@sholland.org --- drivers/firmware/google/vpd.c | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-)
diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index e4b40f2b4627..e9db895916c3 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -286,20 +286,15 @@ static int vpd_sections_init(phys_addr_t physaddr) return 0; }
-static int vpd_probe(struct platform_device *pdev) +static int vpd_probe(struct coreboot_device *dev) { int ret; - struct lb_cbmem_ref entry; - - ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry)); - if (ret) - return ret;
vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); if (!vpd_kobj) return -ENOMEM;
- ret = vpd_sections_init(entry.cbmem_addr); + ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr); if (ret) { kobject_put(vpd_kobj); return ret; @@ -308,7 +303,7 @@ static int vpd_probe(struct platform_device *pdev) return 0; }
-static int vpd_remove(struct platform_device *pdev) +static int vpd_remove(struct coreboot_device *dev) { vpd_section_destroy(&ro_vpd); vpd_section_destroy(&rw_vpd); @@ -318,41 +313,27 @@ static int vpd_remove(struct platform_device *pdev) return 0; }
-static struct platform_driver vpd_driver = { +static struct coreboot_driver vpd_driver = { .probe = vpd_probe, .remove = vpd_remove, - .driver = { + .drv = { .name = "vpd", }, + .tag = CB_TAG_VPD, };
-static struct platform_device *vpd_pdev; - -static int __init vpd_platform_init(void) +static int __init coreboot_vpd_init(void) { - int ret; - - ret = platform_driver_register(&vpd_driver); - if (ret) - return ret; - - vpd_pdev = platform_device_register_simple("vpd", -1, NULL, 0); - if (IS_ERR(vpd_pdev)) { - platform_driver_unregister(&vpd_driver); - return PTR_ERR(vpd_pdev); - } - - return 0; + return coreboot_driver_register(&vpd_driver); }
-static void __exit vpd_platform_exit(void) +static void __exit coreboot_vpd_exit(void) { - platform_device_unregister(vpd_pdev); - platform_driver_unregister(&vpd_driver); + coreboot_driver_unregister(&vpd_driver); }
-module_init(vpd_platform_init); -module_exit(vpd_platform_exit); +module_init(coreboot_vpd_init); +module_exit(coreboot_vpd_exit);
MODULE_AUTHOR("Google, Inc."); MODULE_LICENSE("GPL");
Now that all users of the coreboot_table_find function have been updated to hang off the coreboot table bus instead, remove it.
Signed-off-by: Samuel Holland samuel@sholland.org --- drivers/firmware/google/coreboot_table.c | 43 -------------------------------- drivers/firmware/google/coreboot_table.h | 3 --- 2 files changed, 46 deletions(-)
diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c index 04fc08e81744..19db5709ae28 100644 --- a/drivers/firmware/google/coreboot_table.c +++ b/drivers/firmware/google/coreboot_table.c @@ -97,49 +97,6 @@ void coreboot_driver_unregister(struct coreboot_driver *driver) } EXPORT_SYMBOL(coreboot_driver_unregister);
-/* - * This function parses the coreboot table for an entry that contains the base - * address of the given entry tag. The coreboot table consists of a header - * directly followed by a number of small, variable-sized entries, which each - * contain an identifying tag and their length as the first two fields. - */ -int coreboot_table_find(int tag, void *data, size_t data_size) -{ - struct coreboot_table_header header; - struct coreboot_table_entry entry; - void *ptr_entry; - int i; - - if (!ptr_header) - return -EPROBE_DEFER; - - memcpy_fromio(&header, ptr_header, sizeof(header)); - - if (strncmp(header.signature, "LBIO", sizeof(header.signature))) { - pr_warn("coreboot_table: coreboot table missing or corrupt!\n"); - return -ENODEV; - } - - ptr_entry = (void *)ptr_header + header.header_bytes; - - for (i = 0; i < header.table_entries; i++) { - memcpy_fromio(&entry, ptr_entry, sizeof(entry)); - if (entry.tag == tag) { - if (data_size < entry.size) - return -EINVAL; - - memcpy_fromio(data, ptr_entry, entry.size); - - return 0; - } - - ptr_entry += entry.size; - } - - return -ENOENT; -} -EXPORT_SYMBOL(coreboot_table_find); - int coreboot_table_init(struct device *dev, void __iomem *ptr) { int i, ret; diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h index 88e6a1c06028..26a3f3f3ac9c 100644 --- a/drivers/firmware/google/coreboot_table.h +++ b/drivers/firmware/google/coreboot_table.h @@ -69,9 +69,6 @@ int coreboot_driver_register(struct coreboot_driver *driver); /* Unregister a driver that uses the data from a coreboot table. */ void coreboot_driver_unregister(struct coreboot_driver *driver);
-/* Retrieve coreboot table entry with tag *tag* and copy it to data */ -int coreboot_table_find(int tag, void *data, size_t data_size); - /* Initialize coreboot table module given a pointer to iomem */ int coreboot_table_init(struct device *dev, void __iomem *ptr);
Register a simplefb framebuffer when the coreboot table contains a framebuffer entry.
Signed-off-by: Samuel Holland samuel@sholland.org --- drivers/firmware/google/Kconfig | 8 ++ drivers/firmware/google/Makefile | 1 + drivers/firmware/google/coreboot_table.h | 22 +++++ drivers/firmware/google/framebuffer-coreboot.c | 115 +++++++++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 drivers/firmware/google/framebuffer-coreboot.c
diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig index f16b381a569c..a456a000048b 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/google/Kconfig @@ -55,6 +55,14 @@ config GOOGLE_MEMCONSOLE_X86_LEGACY the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log.
+config GOOGLE_FRAMEBUFFER_COREBOOT + tristate "Coreboot Framebuffer" + depends on FB_SIMPLE + depends on GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to search for a framebuffer in + the coreboot table. If found, it is registered with simplefb. + config GOOGLE_MEMCONSOLE_COREBOOT tristate "Firmware Memory Console" depends on GOOGLE_COREBOOT_TABLE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile index dcd3675efcfc..d0b3fba96194 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/google/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o +obj-$(CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT) += framebuffer-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h index 26a3f3f3ac9c..8ad95a94481b 100644 --- a/drivers/firmware/google/coreboot_table.h +++ b/drivers/firmware/google/coreboot_table.h @@ -3,6 +3,7 @@ * * Internal header for coreboot table access. * + * Copyright 2014 Gerd Hoffmann kraxel@redhat.com * Copyright 2017 Google Inc. * Copyright 2017 Samuel Holland samuel@sholland.org * @@ -46,12 +47,33 @@ struct lb_cbmem_ref { u64 cbmem_addr; };
+/* Describes framebuffer setup by coreboot */ +struct lb_framebuffer { + u32 tag; + u32 size; + + u64 physical_address; + u32 x_resolution; + u32 y_resolution; + u32 bytes_per_line; + u8 bits_per_pixel; + u8 red_mask_pos; + u8 red_mask_size; + u8 green_mask_pos; + u8 green_mask_size; + u8 blue_mask_pos; + u8 blue_mask_size; + u8 reserved_mask_pos; + u8 reserved_mask_size; +}; + /* A device, additionally with information from coreboot. */ struct coreboot_device { struct device dev; union { struct coreboot_table_entry entry; struct lb_cbmem_ref cbmem_ref; + struct lb_framebuffer framebuffer; }; };
diff --git a/drivers/firmware/google/framebuffer-coreboot.c b/drivers/firmware/google/framebuffer-coreboot.c new file mode 100644 index 000000000000..b8b49c067157 --- /dev/null +++ b/drivers/firmware/google/framebuffer-coreboot.c @@ -0,0 +1,115 @@ +/* + * framebuffer-coreboot.c + * + * Memory based framebuffer accessed through coreboot table. + * + * Copyright 2012-2013 David Herrmann dh.herrmann@gmail.com + * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland samuel@sholland.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License v2.0 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_data/simplefb.h> +#include <linux/platform_device.h> + +#include "coreboot_table.h" + +#define CB_TAG_FRAMEBUFFER 0x12 + +static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; + +static int framebuffer_probe(struct coreboot_device *dev) +{ + int i; + u32 length; + struct lb_framebuffer *fb = &dev->framebuffer; + struct platform_device *pdev; + struct resource res; + struct simplefb_platform_data pdata = { + .width = fb->x_resolution, + .height = fb->y_resolution, + .stride = fb->bytes_per_line, + .format = NULL, + }; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (fb->bits_per_pixel == formats[i].bits_per_pixel && + fb->red_mask_pos == formats[i].red.offset && + fb->red_mask_size == formats[i].red.length && + fb->green_mask_pos == formats[i].green.offset && + fb->green_mask_size == formats[i].green.length && + fb->blue_mask_pos == formats[i].blue.offset && + fb->blue_mask_size == formats[i].blue.length && + fb->reserved_mask_pos == formats[i].transp.offset && + fb->reserved_mask_size == formats[i].transp.length) + pdata.format = formats[i].name; + } + if (!pdata.format) + return -ENODEV; + + memset(&res, 0, sizeof(res)); + res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + res.name = "Coreboot Framebuffer"; + res.start = fb->physical_address; + length = PAGE_ALIGN(fb->y_resolution * fb->bytes_per_line); + res.end = res.start + length - 1; + if (res.end <= res.start) + return -EINVAL; + + pdev = platform_device_register_resndata(&dev->dev, + "simple-framebuffer", 0, + &res, 1, &pdata, + sizeof(pdata)); + if (IS_ERR(pdev)) + pr_warn("coreboot: could not register framebuffer\n"); + else + dev_set_drvdata(&dev->dev, pdev); + + return PTR_ERR_OR_ZERO(pdev); +} + +static int framebuffer_remove(struct coreboot_device *dev) +{ + struct platform_device *pdev = dev_get_drvdata(&dev->dev); + + platform_device_unregister(pdev); + + return 0; +} + +static struct coreboot_driver framebuffer_driver = { + .probe = framebuffer_probe, + .remove = framebuffer_remove, + .drv = { + .name = "framebuffer", + }, + .tag = CB_TAG_FRAMEBUFFER, +}; + +static int __init coreboot_framebuffer_init(void) +{ + return coreboot_driver_register(&framebuffer_driver); +} + +static void coreboot_framebuffer_exit(void) +{ + coreboot_driver_unregister(&framebuffer_driver); +} + +module_init(coreboot_framebuffer_init); +module_exit(coreboot_framebuffer_exit); + +MODULE_AUTHOR("Samuel Holland samuel@sholland.org"); +MODULE_LICENSE("GPL");
On Wed, Jan 24, 2018 at 07:41:15PM -0600, Samuel Holland wrote:
On many systems, coreboot[1] firmware can initialize graphics hardware and set up a high-resolution linear framebuffer. It exports information about this framebuffer, along with various other information, in a table discoverable via ACPI or a device tree.
coreboot also supports booting Linux directly from flash as a "payload". Projects such as Heads[2], u-root[3], and petitboot[4] provide a minimal userland that can then be used to chainload (via kexec) into a full Linux system loaded from disk or over the network.
Fitting even a minimal Linux system on an SPI flash chip is challenging. Reusing the framebuffer setup from coreboot provides an enormous benefit to these projects by allowing them to omit full graphics drivers from their kernel builds. It also speeds up boot times by avoiding duplicated effort, and because coreboot's graphics initialization is often much faster than the Linux driver.
Patch 1 of this series expands coreboot table support into an enumerable bus that devices can hang off of. Patches 2-3 convert the existing drivers to use the new bus structure instead of ad-hoc platform devices, and patch 4 removes the old coreboot_table_find function.
Finally, patch 5 adds a new driver for the coreboot-initialized framebuffer. It improves on earlier work[5] by being architecture- independent and not needing to scan through low memory.
This patchset has been tested on a Lenovo ThinkPad X220, and earlier versions of these patches have been tested by various members of the coreboot community on other hardware.
It would be great to get some of the google developers to ack these, as this touches their code...
thanks,
greg k-h
It would be great to get some of the google developers to ack these, as this touches their code...
From the coreboot point of view I guess we're fine with it since it claims
to maintain all of the existing functionality. It's just changing the kernel-level plumbing for these drivers and I don't really have the expertise to comment on whether this is better or worse than the old code (maybe Dmitry or Guenter will?). It seems a little odd to me to call this a "bus", but if we think that's the most fitting abstraction the kernel has for it, I'm okay with that. All I care about is that it will work (in all combinations... e.g. regardless of probe order and even if some parts are compiled as modules and loaded/unloaded at runtime).