From 106b4a2a78b2a2f14885604891957b84bba502db Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Fri, 11 Sep 2026 17:13:14 +0200 Subject: drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validation amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and uses the first encoding that validates. YCbCr 4:4:4 is listed before RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the "color format" property is left at AUTO, even though RGB fits the same link. That contradicts the documented AUTO behaviour for HDMI in enum drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when the bandwidth is not available or the mode is 4:2:0-only), which the amdgpu implementation of the property also describes. It also leaves the "Broadcast RGB" property without effect on such sinks, since the quantization range it selects only applies to RGB output. Try RGB first. The mask still holds every encoding the sink supports, so a mode that cannot carry RGB falls back exactly as before. For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI sink that advertised it. Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation") Suggested-by: Adolfo Rodrigues Assisted-by: Claude Code:claude-fable-5-1 Signed-off-by: Adrian Betschart Reviewed-by: Fangzhi Zuo Tested-by: Adolfo Rodrigues Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7) --- drivers/firmware/google/Kconfig | 85 ++ drivers/firmware/google/Makefile | 14 + drivers/firmware/google/cbmem.c | 136 +++ drivers/firmware/google/coreboot_table.c | 306 +++++++ drivers/firmware/google/coreboot_table.h | 62 ++ drivers/firmware/google/framebuffer-coreboot.c | 187 ++++ drivers/firmware/google/gsmi.c | 1096 +++++++++++++++++++++++ drivers/firmware/google/memconsole-coreboot.c | 118 +++ drivers/firmware/google/memconsole-x86-legacy.c | 158 ++++ drivers/firmware/google/memconsole.c | 54 ++ drivers/firmware/google/memconsole.h | 36 + drivers/firmware/google/vpd.c | 328 +++++++ drivers/firmware/google/vpd_decode.c | 98 ++ drivers/firmware/google/vpd_decode.h | 50 ++ 14 files changed, 2728 insertions(+) create mode 100644 drivers/firmware/google/Kconfig create mode 100644 drivers/firmware/google/Makefile create mode 100644 drivers/firmware/google/cbmem.c create mode 100644 drivers/firmware/google/coreboot_table.c create mode 100644 drivers/firmware/google/coreboot_table.h create mode 100644 drivers/firmware/google/framebuffer-coreboot.c create mode 100644 drivers/firmware/google/gsmi.c create mode 100644 drivers/firmware/google/memconsole-coreboot.c create mode 100644 drivers/firmware/google/memconsole-x86-legacy.c create mode 100644 drivers/firmware/google/memconsole.c create mode 100644 drivers/firmware/google/memconsole.h create mode 100644 drivers/firmware/google/vpd.c create mode 100644 drivers/firmware/google/vpd_decode.c create mode 100644 drivers/firmware/google/vpd_decode.h (limited to 'drivers/firmware/google') diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig new file mode 100644 index 000000000..b78c644fa --- /dev/null +++ b/drivers/firmware/google/Kconfig @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig GOOGLE_FIRMWARE + bool "Google Firmware Drivers" + default n + help + These firmware drivers are used by Google servers, + Chromebooks and other devices using coreboot firmware. + If in doubt, say "N". + +if GOOGLE_FIRMWARE + +config GOOGLE_SMI + tristate "SMI interface for Google platforms" + depends on X86 && ACPI && DMI + help + Say Y here if you want to enable SMI callbacks for Google + platforms. This provides an interface for writing to and + clearing the event log. If CONFIG_EFI is also enabled this + driver provides an interface for reading and writing NVRAM + variables. + +config GOOGLE_CBMEM + tristate "CBMEM entries in sysfs" + depends on GOOGLE_COREBOOT_TABLE + help + CBMEM is a downwards-growing memory region created by the + Coreboot BIOS containing tagged data structures from the + BIOS. These data structures expose things like the verified + boot firmware variables, flash layout, firmware event log, + and more. + + This option enables the cbmem module, which causes the + kernel to search for Coreboot CBMEM entries, and expose the + memory for each entry in sysfs under + /sys/bus/coreboot/devices/cbmem-. + +config GOOGLE_COREBOOT_TABLE + tristate "Coreboot Table Access" + depends on HAS_IOMEM && (ACPI || OF) + help + This option enables the coreboot_table module, which provides other + firmware modules access to the coreboot table. The coreboot table + pointer is accessed through the ACPI "GOOGCB00" object or the + device tree node /firmware/coreboot. + If unsure say N. + +config GOOGLE_MEMCONSOLE + tristate + depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT + +config GOOGLE_MEMCONSOLE_X86_LEGACY + tristate "Firmware Memory Console - X86 Legacy support" + depends on X86 && ACPI && DMI + select GOOGLE_MEMCONSOLE + help + This option enables the kernel to search for a firmware log in + 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 GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to search for a framebuffer in + the coreboot table. If found, it is registered with a platform + device of type coreboot-framebuffer. Using the old device of + type simple-framebuffer is deprecated. + +config GOOGLE_MEMCONSOLE_COREBOOT + tristate "Firmware Memory Console" + depends on GOOGLE_COREBOOT_TABLE + select GOOGLE_MEMCONSOLE + help + This option enables the kernel to search for a firmware log in + the coreboot table. If found, this log is exported to userland + in the file /sys/firmware/log. + +config GOOGLE_VPD + tristate "Vital Product Data" + depends on GOOGLE_COREBOOT_TABLE + help + This option enables the kernel to expose the content of Google VPD + under /sys/firmware/vpd. + +endif # GOOGLE_FIRMWARE diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile new file mode 100644 index 000000000..8151e323c --- /dev/null +++ b/drivers/firmware/google/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_GOOGLE_SMI) += gsmi.o +obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.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 + +# Must come after coreboot_table.o, as this driver depends on that bus type. +obj-$(CONFIG_GOOGLE_CBMEM) += cbmem.o + +vpd-sysfs-y := vpd.o vpd_decode.o +obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o diff --git a/drivers/firmware/google/cbmem.c b/drivers/firmware/google/cbmem.c new file mode 100644 index 000000000..4d20477ed --- /dev/null +++ b/drivers/firmware/google/cbmem.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cbmem.c + * + * Driver for exporting cbmem entries in sysfs. + * + * Copyright 2022 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" + +struct cbmem_entry { + char *mem_file_buf; + u32 size; +}; + +static struct cbmem_entry *to_cbmem_entry(struct kobject *kobj) +{ + return dev_get_drvdata(kobj_to_dev(kobj)); +} + +static ssize_t mem_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, + size_t count) +{ + struct cbmem_entry *entry = to_cbmem_entry(kobj); + + return memory_read_from_buffer(buf, count, &pos, entry->mem_file_buf, + entry->size); +} + +static ssize_t mem_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, + size_t count) +{ + struct cbmem_entry *entry = to_cbmem_entry(kobj); + + if (pos < 0 || pos >= entry->size) + return -EINVAL; + if (count > entry->size - pos) + count = entry->size - pos; + + memcpy(entry->mem_file_buf + pos, buf, count); + return count; +} +static const BIN_ATTR_ADMIN_RW(mem, 0); + +static ssize_t address_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct coreboot_device *cbdev = dev_to_coreboot_device(dev); + + return sysfs_emit(buf, "0x%llx\n", cbdev->cbmem_entry.address); +} +static DEVICE_ATTR_RO(address); + +static ssize_t size_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct coreboot_device *cbdev = dev_to_coreboot_device(dev); + + return sysfs_emit(buf, "0x%x\n", cbdev->cbmem_entry.entry_size); +} +static DEVICE_ATTR_RO(size); + +static struct attribute *attrs[] = { + &dev_attr_address.attr, + &dev_attr_size.attr, + NULL, +}; + +static const struct bin_attribute *const bin_attrs[] = { + &bin_attr_mem, + NULL, +}; + +static const struct attribute_group cbmem_entry_group = { + .attrs = attrs, + .bin_attrs = bin_attrs, +}; + +static const struct attribute_group *dev_groups[] = { + &cbmem_entry_group, + NULL, +}; + +static int cbmem_entry_probe(struct coreboot_device *dev) +{ + struct cbmem_entry *entry; + + entry = devm_kzalloc(&dev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + dev_set_drvdata(&dev->dev, entry); + entry->mem_file_buf = devm_memremap(&dev->dev, dev->cbmem_entry.address, + dev->cbmem_entry.entry_size, + MEMREMAP_WB); + if (IS_ERR(entry->mem_file_buf)) + return PTR_ERR(entry->mem_file_buf); + + entry->size = dev->cbmem_entry.entry_size; + + return 0; +} + +static const struct coreboot_device_id cbmem_ids[] = { + { .tag = LB_TAG_CBMEM_ENTRY }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(coreboot, cbmem_ids); + +static struct coreboot_driver cbmem_entry_driver = { + .probe = cbmem_entry_probe, + .drv = { + .name = "cbmem", + .dev_groups = dev_groups, + }, + .id_table = cbmem_ids, +}; +module_coreboot_driver(cbmem_entry_driver); + +MODULE_AUTHOR("Jack Rosenthal "); +MODULE_DESCRIPTION("Driver for exporting CBMEM entries in sysfs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c new file mode 100644 index 000000000..25ee8cf53 --- /dev/null +++ b/drivers/firmware/google/coreboot_table.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * coreboot_table.c + * + * Module providing coreboot table access. + * + * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" + +/* Coreboot table header structure */ +struct coreboot_table_header { + char signature[4]; + u32 header_bytes; + u32 header_checksum; + u32 table_bytes; + u32 table_checksum; + u32 table_entries; +}; + +#define CB_DEV(d) container_of(d, struct coreboot_device, dev) +#define CB_DRV(d) container_of_const(d, struct coreboot_driver, drv) + +static int coreboot_bus_match(struct device *dev, const struct device_driver *drv) +{ + struct coreboot_device *device = CB_DEV(dev); + const struct coreboot_driver *driver = CB_DRV(drv); + const struct coreboot_device_id *id; + + if (!driver->id_table) + return 0; + + for (id = driver->id_table; id->tag; id++) { + if (device->entry.tag == id->tag) + return 1; + } + + return 0; +} + +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 void coreboot_bus_remove(struct device *dev) +{ + struct coreboot_device *device = CB_DEV(dev); + struct coreboot_driver *driver = CB_DRV(dev->driver); + + if (driver->remove) + driver->remove(device); +} + +static int coreboot_bus_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + struct coreboot_device *device = CB_DEV(dev); + u32 tag = device->entry.tag; + + return add_uevent_var(env, "MODALIAS=coreboot:t%08X", tag); +} + +static const struct bus_type coreboot_bus_type = { + .name = "coreboot", + .match = coreboot_bus_match, + .probe = coreboot_bus_probe, + .remove = coreboot_bus_remove, + .uevent = coreboot_bus_uevent, +}; + +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, + struct module *owner) +{ + driver->drv.bus = &coreboot_bus_type; + driver->drv.owner = owner; + + 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); + +static int coreboot_table_populate(struct device *dev, void *ptr, resource_size_t len) +{ + int i, ret; + void *ptr_entry; + struct coreboot_device *device; + struct coreboot_table_entry *entry; + struct coreboot_table_header *header = ptr; + void *ptr_end; + + ptr_end = ptr + len; + ptr_entry = ptr + header->header_bytes; + for (i = 0; i < header->table_entries; i++, ptr_entry += entry->size) { + if (ptr_entry + sizeof(*entry) > ptr_end) + return -EINVAL; + entry = ptr_entry; + + if (entry->size < sizeof(*entry)) { + dev_warn(dev, "coreboot table entry too small!\n"); + return -EINVAL; + } + + if (ptr_entry + entry->size > ptr_end) + return -EINVAL; + + device = kzalloc(sizeof(device->dev) + entry->size, GFP_KERNEL); + if (!device) + return -ENOMEM; + + device->dev.parent = dev; + device->dev.bus = &coreboot_bus_type; + device->dev.release = coreboot_device_release; + memcpy(device->raw, ptr_entry, entry->size); + + switch (device->entry.tag) { + case LB_TAG_CBMEM_ENTRY: + /* + * Skip entries that are not exclusively System RAM or + * Reserved memory. + * On ARM64, no-map regions are filtered out as they are + * IORESOURCE_MEM (see request_standard_resources() in + * arch/arm64/kernel/setup.c). + * On x86, CBMEM often resides in standard reserved regions + * (IORES_DESC_RESERVED). + */ + if (region_intersects(device->cbmem_entry.address, + device->cbmem_entry.entry_size, + IORESOURCE_SYSTEM_RAM, + IORES_DESC_NONE) != REGION_INTERSECTS && + region_intersects(device->cbmem_entry.address, + device->cbmem_entry.entry_size, + IORESOURCE_MEM, + IORES_DESC_RESERVED) != REGION_INTERSECTS) { + kfree(device); + continue; + } + dev_set_name(&device->dev, "cbmem-%08x", + device->cbmem_entry.id); + break; + default: + dev_set_name(&device->dev, "coreboot%d", i); + break; + } + + ret = device_register(&device->dev); + if (ret) { + dev_warn(dev, "failed to register coreboot device: %d\n", ret); + put_device(&device->dev); + } + } + + return 0; +} + +static int coreboot_table_probe(struct platform_device *pdev) +{ + resource_size_t len; + resource_size_t table_span; + struct coreboot_table_header *header; + struct resource *res; + struct device *dev = &pdev->dev; + void *ptr; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + len = resource_size(res); + if (!res->start || len < sizeof(*header)) + return -EINVAL; + + /* Check just the header first to make sure things are sane */ + header = memremap(res->start, sizeof(*header), MEMREMAP_WB); + if (!header) + return -ENOMEM; + + ret = strncmp(header->signature, "LBIO", sizeof(header->signature)); + + if (!ret && + (header->header_bytes < sizeof(*header) || + check_add_overflow((resource_size_t)header->header_bytes, + (resource_size_t)header->table_bytes, + &table_span) || + table_span > len)) + ret = -EINVAL; + + memunmap(header); + if (ret) { + dev_warn(dev, "coreboot table missing or corrupt!\n"); + return -ENODEV; + } + + ptr = memremap(res->start, table_span, MEMREMAP_WB); + if (!ptr) + return -ENOMEM; + + ret = coreboot_table_populate(dev, ptr, table_span); + + memunmap(ptr); + + return ret; +} + +static int __cb_dev_unregister(struct device *dev, void *dummy) +{ + device_unregister(dev); + return 0; +} + +static void coreboot_table_remove(struct platform_device *pdev) +{ + bus_for_each_dev(&coreboot_bus_type, NULL, NULL, __cb_dev_unregister); +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cros_coreboot_acpi_match[] = { + { "GOOGCB00", 0 }, + { "BOOT0000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id coreboot_of_match[] = { + { .compatible = "coreboot" }, + {} +}; +MODULE_DEVICE_TABLE(of, coreboot_of_match); +#endif + +static struct platform_driver coreboot_table_driver = { + .probe = coreboot_table_probe, + .remove = coreboot_table_remove, + .driver = { + .name = "coreboot_table", + .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match), + .of_match_table = of_match_ptr(coreboot_of_match), + }, +}; + +static int __init coreboot_table_driver_init(void) +{ + int ret; + + ret = bus_register(&coreboot_bus_type); + if (ret) + return ret; + + ret = platform_driver_register(&coreboot_table_driver); + if (ret) { + bus_unregister(&coreboot_bus_type); + return ret; + } + + return 0; +} + +static void __exit coreboot_table_driver_exit(void) +{ + platform_driver_unregister(&coreboot_table_driver); + bus_unregister(&coreboot_bus_type); +} + +subsys_initcall(coreboot_table_driver_init); +module_exit(coreboot_table_driver_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Module providing coreboot table access"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h new file mode 100644 index 000000000..616ca3903 --- /dev/null +++ b/drivers/firmware/google/coreboot_table.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * coreboot_table.h + * + * Internal header for coreboot table access. + * + * Copyright 2014 Gerd Hoffmann + * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland + */ + +#ifndef __COREBOOT_TABLE_H +#define __COREBOOT_TABLE_H + +#include +#include + +/* 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_cbmem_entry cbmem_entry; + struct lb_framebuffer framebuffer; + DECLARE_FLEX_ARRAY(u8, raw); + }; +}; + +static inline struct coreboot_device *dev_to_coreboot_device(struct device *dev) +{ + return container_of(dev, struct coreboot_device, dev); +} + +/* A driver for handling devices described in coreboot tables. */ +struct coreboot_driver { + int (*probe)(struct coreboot_device *); + void (*remove)(struct coreboot_device *); + struct device_driver drv; + const struct coreboot_device_id *id_table; +}; + +/* use a macro to avoid include chaining to get THIS_MODULE */ +#define coreboot_driver_register(driver) \ + __coreboot_driver_register(driver, THIS_MODULE) +/* Register a driver that uses the data from a coreboot table. */ +int __coreboot_driver_register(struct coreboot_driver *driver, + struct module *owner); + +/* Unregister a driver that uses the data from a coreboot table. */ +void coreboot_driver_unregister(struct coreboot_driver *driver); + +/* module_coreboot_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_coreboot_driver(__coreboot_driver) \ + module_driver(__coreboot_driver, coreboot_driver_register, \ + coreboot_driver_unregister) + +#endif /* __COREBOOT_TABLE_H */ diff --git a/drivers/firmware/google/framebuffer-coreboot.c b/drivers/firmware/google/framebuffer-coreboot.c new file mode 100644 index 000000000..1a6d4ac6d --- /dev/null +++ b/drivers/firmware/google/framebuffer-coreboot.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * framebuffer-coreboot.c + * + * Memory based framebuffer accessed through coreboot table. + * + * Copyright 2012-2013 David Herrmann + * Copyright 2017 Google Inc. + * Copyright 2017 Samuel Holland + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" + +#if defined(CONFIG_PCI) +static bool framebuffer_pci_dev_is_enabled(struct pci_dev *pdev) +{ + /* + * TODO: Try to integrate this code into the PCI subsystem + */ + int ret; + u16 command; + + ret = pci_read_config_word(pdev, PCI_COMMAND, &command); + if (ret != PCIBIOS_SUCCESSFUL) + return false; + if (!(command & PCI_COMMAND_MEMORY)) + return false; + return true; +} + +static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) +{ + struct pci_dev *pdev = NULL; + const struct resource *r = NULL; + + while (!r && (pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) + r = pci_find_resource(pdev, res); + + if (!r || !pdev) + return NULL; /* not found; not an error */ + + if (!framebuffer_pci_dev_is_enabled(pdev)) { + pci_dev_put(pdev); + return ERR_PTR(-ENODEV); + } + + return pdev; +} +#else +static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) +{ + return NULL; +} +#endif + +static struct device *framebuffer_parent_dev(struct resource *res) +{ + struct pci_dev *pdev; + + pdev = framebuffer_parent_pci_dev(res); + if (IS_ERR(pdev)) + return ERR_CAST(pdev); + else if (pdev) + return &pdev->dev; + + return NULL; +} + +static int framebuffer_probe(struct coreboot_device *dev) +{ + struct lb_framebuffer *fb = &dev->framebuffer; + struct device *parent; + struct platform_device *pdev; + struct resource res; + int ret; +#if !IS_ENABLED(CONFIG_DRM_COREBOOTDRM) + struct simplefb_platform_data pdata = { + .width = fb->x_resolution, + .height = fb->y_resolution, + .stride = fb->bytes_per_line, + .format = NULL, + }; + int i; + static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; +#endif + + /* + * On coreboot systems, the advertised LB_TAG_FRAMEBUFFER entry + * in the coreboot table should only be used if the payload did + * not pass a framebuffer information to the Linux kernel. + * + * If the global screen_info data has been filled, the Generic + * System Framebuffers (sysfb) will already register a platform + * device and pass that screen_info as platform_data to a driver + * that can scan-out using the system provided framebuffer. + */ + if (sysfb_handles_screen_info()) + return -ENODEV; + + if (!fb->physical_address) + return -ENODEV; + + res = DEFINE_RES_MEM(fb->physical_address, + PAGE_ALIGN(fb->y_resolution * fb->bytes_per_line)); + if (res.end <= res.start) + return -EINVAL; + + parent = framebuffer_parent_dev(&res); + if (IS_ERR(parent)) + return PTR_ERR(parent); + +#if IS_ENABLED(CONFIG_DRM_COREBOOTDRM) + pdev = platform_device_register_resndata(parent, "coreboot-framebuffer", 0, + &res, 1, fb, fb->size); + if (IS_ERR(pdev)) { + pr_warn("coreboot: could not register framebuffer\n"); + ret = PTR_ERR(pdev); + goto out_put_device_parent; + } +#else + /* + * FIXME: Coreboot systems should use a driver that binds to + * coreboot-framebuffer devices. Remove support for + * simple-framebuffer at some point. + */ + 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) + pdata.format = formats[i].name; + } + if (!pdata.format) { + ret = -ENODEV; + goto out_put_device_parent; + } + + pdev = platform_device_register_resndata(parent, + "simple-framebuffer", 0, + &res, 1, &pdata, + sizeof(pdata)); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + pr_warn("coreboot: could not register framebuffer\n"); + goto out_put_device_parent; + } +#endif + + ret = 0; + +out_put_device_parent: + if (parent) + put_device(parent); + return ret; +} + +static const struct coreboot_device_id framebuffer_ids[] = { + { .tag = CB_TAG_FRAMEBUFFER }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(coreboot, framebuffer_ids); + +static struct coreboot_driver framebuffer_driver = { + .probe = framebuffer_probe, + .drv = { + .name = "framebuffer", + }, + .id_table = framebuffer_ids, +}; +module_coreboot_driver(framebuffer_driver); + +MODULE_AUTHOR("Samuel Holland "); +MODULE_DESCRIPTION("Memory based framebuffer accessed through coreboot table"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c new file mode 100644 index 000000000..e21e7408c --- /dev/null +++ b/drivers/firmware/google/gsmi.c @@ -0,0 +1,1096 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2010 Google Inc. All Rights Reserved. + * Author: dlaurie@google.com (Duncan Laurie) + * + * Re-worked to expose sysfs APIs by mikew@google.com (Mike Waychison) + * + * EFI SMI interface for Google platforms + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GSMI_SHUTDOWN_CLEAN 0 /* Clean Shutdown */ +/* TODO(mikew@google.com): Tie in HARDLOCKUP_DETECTOR with NMIWDT */ +#define GSMI_SHUTDOWN_NMIWDT 1 /* NMI Watchdog */ +#define GSMI_SHUTDOWN_PANIC 2 /* Panic */ +#define GSMI_SHUTDOWN_OOPS 3 /* Oops */ +#define GSMI_SHUTDOWN_DIE 4 /* Die -- No longer meaningful */ +#define GSMI_SHUTDOWN_MCE 5 /* Machine Check */ +#define GSMI_SHUTDOWN_SOFTWDT 6 /* Software Watchdog */ +#define GSMI_SHUTDOWN_MBE 7 /* Uncorrected ECC */ +#define GSMI_SHUTDOWN_TRIPLE 8 /* Triple Fault */ + +#define DRIVER_VERSION "1.0" +#define GSMI_GUID_SIZE 16 +#define GSMI_BUF_SIZE 1024 +#define GSMI_BUF_ALIGN sizeof(u64) +#define GSMI_CALLBACK 0xef + +/* SMI return codes */ +#define GSMI_SUCCESS 0x00 +#define GSMI_UNSUPPORTED2 0x03 +#define GSMI_LOG_FULL 0x0b +#define GSMI_VAR_NOT_FOUND 0x0e +#define GSMI_HANDSHAKE_SPIN 0x7d +#define GSMI_HANDSHAKE_CF 0x7e +#define GSMI_HANDSHAKE_NONE 0x7f +#define GSMI_INVALID_PARAMETER 0x82 +#define GSMI_UNSUPPORTED 0x83 +#define GSMI_BUFFER_TOO_SMALL 0x85 +#define GSMI_NOT_READY 0x86 +#define GSMI_DEVICE_ERROR 0x87 +#define GSMI_NOT_FOUND 0x8e + +#define QUIRKY_BOARD_HASH 0x78a30a50 + +/* Internally used commands passed to the firmware */ +#define GSMI_CMD_GET_NVRAM_VAR 0x01 +#define GSMI_CMD_GET_NEXT_VAR 0x02 +#define GSMI_CMD_SET_NVRAM_VAR 0x03 +#define GSMI_CMD_SET_EVENT_LOG 0x08 +#define GSMI_CMD_CLEAR_EVENT_LOG 0x09 +#define GSMI_CMD_LOG_S0IX_SUSPEND 0x0a +#define GSMI_CMD_LOG_S0IX_RESUME 0x0b +#define GSMI_CMD_CLEAR_CONFIG 0x20 +#define GSMI_CMD_HANDSHAKE_TYPE 0xC1 +#define GSMI_CMD_RESERVED 0xff + +/* Magic entry type for kernel events */ +#define GSMI_LOG_ENTRY_TYPE_KERNEL 0xDEAD + +/* SMI buffers must be in 32bit physical address space */ +struct gsmi_buf { + u8 *start; /* start of buffer */ + size_t length; /* length of buffer */ + u32 address; /* physical address of buffer */ +}; + +static struct gsmi_device { + struct platform_device *pdev; /* platform device */ + struct gsmi_buf *name_buf; /* variable name buffer */ + struct gsmi_buf *data_buf; /* generic data buffer */ + struct gsmi_buf *param_buf; /* parameter buffer */ + spinlock_t lock; /* serialize access to SMIs */ + u16 smi_cmd; /* SMI command port */ + int handshake_type; /* firmware handler interlock type */ + struct kmem_cache *mem_pool; /* kmem cache for gsmi_buf allocations */ +} gsmi_dev; + +/* Packed structures for communicating with the firmware */ +struct gsmi_nvram_var_param { + efi_guid_t guid; + u32 name_ptr; + u32 attributes; + u32 data_len; + u32 data_ptr; +} __packed; + +struct gsmi_get_next_var_param { + u8 guid[GSMI_GUID_SIZE]; + u32 name_ptr; + u32 name_len; +} __packed; + +struct gsmi_set_eventlog_param { + u32 data_ptr; + u32 data_len; + u32 type; +} __packed; + +/* Event log formats */ +struct gsmi_log_entry_type_1 { + u16 type; + u32 instance; +} __packed; + +/* + * Some platforms don't have explicit SMI handshake + * and need to wait for SMI to complete. + */ +#define GSMI_DEFAULT_SPINCOUNT 0x10000 +static unsigned int spincount = GSMI_DEFAULT_SPINCOUNT; +module_param(spincount, uint, 0600); +MODULE_PARM_DESC(spincount, + "The number of loop iterations to use when using the spin handshake."); + +/* + * Some older platforms with Apollo Lake chipsets do not support S0ix logging + * in their GSMI handlers, and behaved poorly when resuming via power button + * press if the logging was attempted. Updated firmware with proper behavior + * has long since shipped, removing the need for this opt-in parameter. It + * now exists as an opt-out parameter for folks defiantly running old + * firmware, or unforeseen circumstances. After the change from opt-in to + * opt-out has baked sufficiently, this parameter should probably be removed + * entirely. + */ +static bool s0ix_logging_enable = true; +module_param(s0ix_logging_enable, bool, 0600); + +static struct gsmi_buf *gsmi_buf_alloc(void) +{ + struct gsmi_buf *smibuf; + + smibuf = kzalloc_obj(*smibuf); + if (!smibuf) { + printk(KERN_ERR "gsmi: out of memory\n"); + return NULL; + } + + /* allocate buffer in 32bit address space */ + smibuf->start = kmem_cache_alloc(gsmi_dev.mem_pool, GFP_KERNEL); + if (!smibuf->start) { + printk(KERN_ERR "gsmi: failed to allocate name buffer\n"); + kfree(smibuf); + return NULL; + } + + /* fill in the buffer handle */ + smibuf->length = GSMI_BUF_SIZE; + smibuf->address = (u32)virt_to_phys(smibuf->start); + + return smibuf; +} + +static void gsmi_buf_free(struct gsmi_buf *smibuf) +{ + if (smibuf) { + if (smibuf->start) + kmem_cache_free(gsmi_dev.mem_pool, smibuf->start); + kfree(smibuf); + } +} + +/* + * Make a call to gsmi func(sub). GSMI error codes are translated to + * in-kernel errnos (0 on success, -ERRNO on error). + */ +static int gsmi_exec(u8 func, u8 sub) +{ + u16 cmd = (sub << 8) | func; + u16 result = 0; + int rc = 0; + + /* + * AH : Subfunction number + * AL : Function number + * EBX : Parameter block address + * DX : SMI command port + * + * Three protocols here. See also the comment in gsmi_init(). + */ + if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_CF) { + /* + * If handshake_type == HANDSHAKE_CF then set CF on the + * way in and wait for the handler to clear it; this avoids + * corrupting register state on those chipsets which have + * a delay between writing the SMI trigger register and + * entering SMM. + */ + asm volatile ( + "stc\n" + "outb %%al, %%dx\n" + "1: jc 1b\n" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address) + : "memory", "cc" + ); + } else if (gsmi_dev.handshake_type == GSMI_HANDSHAKE_SPIN) { + /* + * If handshake_type == HANDSHAKE_SPIN we spin a + * hundred-ish usecs to ensure the SMI has triggered. + */ + asm volatile ( + "outb %%al, %%dx\n" + "1: loop 1b\n" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address), + "c" (spincount) + : "memory", "cc" + ); + } else { + /* + * If handshake_type == HANDSHAKE_NONE we do nothing; + * either we don't need to or it's legacy firmware that + * doesn't understand the CF protocol. + */ + asm volatile ( + "outb %%al, %%dx\n\t" + : "=a" (result) + : "0" (cmd), + "d" (gsmi_dev.smi_cmd), + "b" (gsmi_dev.param_buf->address) + : "memory", "cc" + ); + } + + /* check return code from SMI handler */ + switch (result) { + case GSMI_SUCCESS: + break; + case GSMI_VAR_NOT_FOUND: + /* not really an error, but let the caller know */ + rc = 1; + break; + case GSMI_INVALID_PARAMETER: + printk(KERN_ERR "gsmi: exec 0x%04x: Invalid parameter\n", cmd); + rc = -EINVAL; + break; + case GSMI_BUFFER_TOO_SMALL: + printk(KERN_ERR "gsmi: exec 0x%04x: Buffer too small\n", cmd); + rc = -ENOMEM; + break; + case GSMI_UNSUPPORTED: + case GSMI_UNSUPPORTED2: + if (sub != GSMI_CMD_HANDSHAKE_TYPE) + printk(KERN_ERR "gsmi: exec 0x%04x: Not supported\n", + cmd); + rc = -ENOSYS; + break; + case GSMI_NOT_READY: + printk(KERN_ERR "gsmi: exec 0x%04x: Not ready\n", cmd); + rc = -EBUSY; + break; + case GSMI_DEVICE_ERROR: + printk(KERN_ERR "gsmi: exec 0x%04x: Device error\n", cmd); + rc = -EFAULT; + break; + case GSMI_NOT_FOUND: + printk(KERN_ERR "gsmi: exec 0x%04x: Data not found\n", cmd); + rc = -ENOENT; + break; + case GSMI_LOG_FULL: + printk(KERN_ERR "gsmi: exec 0x%04x: Log full\n", cmd); + rc = -ENOSPC; + break; + case GSMI_HANDSHAKE_CF: + case GSMI_HANDSHAKE_SPIN: + case GSMI_HANDSHAKE_NONE: + rc = result; + break; + default: + printk(KERN_ERR "gsmi: exec 0x%04x: Unknown error 0x%04x\n", + cmd, result); + rc = -ENXIO; + } + + return rc; +} + +#ifdef CONFIG_EFI + +static struct efivars efivars; + +static efi_status_t gsmi_get_variable(efi_char16_t *name, + efi_guid_t *vendor, u32 *attr, + unsigned long *data_size, + void *data) +{ + struct gsmi_nvram_var_param param = { + .name_ptr = gsmi_dev.name_buf->address, + .data_ptr = gsmi_dev.data_buf->address, + .data_len = (u32)*data_size, + }; + efi_status_t ret = EFI_SUCCESS; + unsigned long flags; + size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2); + int rc; + + if (name_len >= GSMI_BUF_SIZE / 2) + return EFI_BAD_BUFFER_SIZE; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* Vendor guid */ + memcpy(¶m.guid, vendor, sizeof(param.guid)); + + /* variable name, already in UTF-16 */ + memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length); + memcpy(gsmi_dev.name_buf->start, name, name_len * 2); + + /* data pointer */ + memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length); + + /* parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NVRAM_VAR); + if (rc < 0) { + printk(KERN_ERR "gsmi: Get Variable failed\n"); + ret = EFI_LOAD_ERROR; + } else if (rc == 1) { + /* variable was not found */ + ret = EFI_NOT_FOUND; + } else { + /* Get the arguments back */ + memcpy(¶m, gsmi_dev.param_buf->start, sizeof(param)); + + /* The size reported is the min of all of our buffers */ + *data_size = min_t(unsigned long, *data_size, + gsmi_dev.data_buf->length); + *data_size = min_t(unsigned long, *data_size, param.data_len); + + /* Copy data back to return buffer. */ + memcpy(data, gsmi_dev.data_buf->start, *data_size); + + /* All variables are have the following attributes */ + if (attr) + *attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + } + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + return ret; +} + +static efi_status_t gsmi_get_next_variable(unsigned long *name_size, + efi_char16_t *name, + efi_guid_t *vendor) +{ + struct gsmi_get_next_var_param param = { + .name_ptr = gsmi_dev.name_buf->address, + .name_len = gsmi_dev.name_buf->length, + }; + efi_status_t ret = EFI_SUCCESS; + int rc; + unsigned long flags; + + /* For the moment, only support buffers that exactly match in size */ + if (*name_size != GSMI_BUF_SIZE) + return EFI_BAD_BUFFER_SIZE; + + /* Let's make sure the thing is at least null-terminated */ + if (ucs2_strnlen(name, GSMI_BUF_SIZE / 2) == GSMI_BUF_SIZE / 2) + return EFI_INVALID_PARAMETER; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* guid */ + memcpy(¶m.guid, vendor, sizeof(param.guid)); + + /* variable name, already in UTF-16 */ + memcpy(gsmi_dev.name_buf->start, name, *name_size); + + /* parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_GET_NEXT_VAR); + if (rc < 0) { + printk(KERN_ERR "gsmi: Get Next Variable Name failed\n"); + ret = EFI_LOAD_ERROR; + } else if (rc == 1) { + /* variable not found -- end of list */ + ret = EFI_NOT_FOUND; + } else { + /* copy variable data back to return buffer */ + memcpy(¶m, gsmi_dev.param_buf->start, sizeof(param)); + + /* Copy the name back */ + memcpy(name, gsmi_dev.name_buf->start, GSMI_BUF_SIZE); + *name_size = ucs2_strnlen(name, GSMI_BUF_SIZE / 2) * 2; + + /* copy guid to return buffer */ + memcpy(vendor, ¶m.guid, sizeof(param.guid)); + ret = EFI_SUCCESS; + } + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + return ret; +} + +static efi_status_t gsmi_set_variable(efi_char16_t *name, + efi_guid_t *vendor, + u32 attr, + unsigned long data_size, + void *data) +{ + struct gsmi_nvram_var_param param = { + .name_ptr = gsmi_dev.name_buf->address, + .data_ptr = gsmi_dev.data_buf->address, + .data_len = (u32)data_size, + .attributes = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + }; + size_t name_len = ucs2_strnlen(name, GSMI_BUF_SIZE / 2); + efi_status_t ret = EFI_SUCCESS; + int rc; + unsigned long flags; + + if (name_len >= GSMI_BUF_SIZE / 2) + return EFI_BAD_BUFFER_SIZE; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* guid */ + memcpy(¶m.guid, vendor, sizeof(param.guid)); + + /* variable name, already in UTF-16 */ + memset(gsmi_dev.name_buf->start, 0, gsmi_dev.name_buf->length); + memcpy(gsmi_dev.name_buf->start, name, name_len * 2); + + /* data pointer */ + memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length); + memcpy(gsmi_dev.data_buf->start, data, data_size); + + /* parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_NVRAM_VAR); + if (rc < 0) { + printk(KERN_ERR "gsmi: Set Variable failed\n"); + ret = EFI_INVALID_PARAMETER; + } + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + return ret; +} + +static const struct efivar_operations efivar_ops = { + .get_variable = gsmi_get_variable, + .set_variable = gsmi_set_variable, + .get_next_variable = gsmi_get_next_variable, +}; + +#endif /* CONFIG_EFI */ + +static ssize_t eventlog_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + struct gsmi_set_eventlog_param param = { + .data_ptr = gsmi_dev.data_buf->address, + }; + int rc = 0; + unsigned long flags; + + /* Pull the type out */ + if (count < sizeof(u32)) + return -EINVAL; + param.type = *(u32 *)buf; + buf += sizeof(u32); + + /* The remaining buffer is the data payload */ + if ((count - sizeof(u32)) > gsmi_dev.data_buf->length) + return -EINVAL; + param.data_len = count - sizeof(u32); + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* data pointer */ + memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length); + memcpy(gsmi_dev.data_buf->start, buf, param.data_len); + + /* parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG); + if (rc < 0) + printk(KERN_ERR "gsmi: Set Event Log failed\n"); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + return (rc == 0) ? count : rc; + +} + +static const struct bin_attribute eventlog_bin_attr = { + .attr = {.name = "append_to_eventlog", .mode = 0200}, + .write = eventlog_write, +}; + +static ssize_t gsmi_clear_eventlog_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int rc; + unsigned long flags; + unsigned long val; + struct { + u32 percentage; + u32 data_type; + } param; + + rc = kstrtoul(buf, 0, &val); + if (rc) + return rc; + + /* + * Value entered is a percentage, 0 through 100, anything else + * is invalid. + */ + if (val > 100) + return -EINVAL; + + /* data_type here selects the smbios event log. */ + param.percentage = val; + param.data_type = 0; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_EVENT_LOG); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + if (rc) + return rc; + return count; +} + +static struct kobj_attribute gsmi_clear_eventlog_attr = { + .attr = {.name = "clear_eventlog", .mode = 0200}, + .store = gsmi_clear_eventlog_store, +}; + +static ssize_t gsmi_clear_config_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int rc; + unsigned long flags; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + /* clear parameter buffer */ + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_CLEAR_CONFIG); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + if (rc) + return rc; + return count; +} + +static struct kobj_attribute gsmi_clear_config_attr = { + .attr = {.name = "clear_config", .mode = 0200}, + .store = gsmi_clear_config_store, +}; + +static const struct attribute *gsmi_attrs[] = { + &gsmi_clear_config_attr.attr, + &gsmi_clear_eventlog_attr.attr, + NULL, +}; + +static int gsmi_shutdown_reason(int reason) +{ + struct gsmi_log_entry_type_1 entry = { + .type = GSMI_LOG_ENTRY_TYPE_KERNEL, + .instance = reason, + }; + struct gsmi_set_eventlog_param param = { + .data_len = sizeof(entry), + .type = 1, + }; + static int saved_reason; + int rc = 0; + unsigned long flags; + + /* avoid duplicate entries in the log */ + if (saved_reason & (1 << reason)) + return 0; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + saved_reason |= (1 << reason); + + /* data pointer */ + memset(gsmi_dev.data_buf->start, 0, gsmi_dev.data_buf->length); + memcpy(gsmi_dev.data_buf->start, &entry, sizeof(entry)); + + /* parameter buffer */ + param.data_ptr = gsmi_dev.data_buf->address; + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + memcpy(gsmi_dev.param_buf->start, ¶m, sizeof(param)); + + rc = gsmi_exec(GSMI_CALLBACK, GSMI_CMD_SET_EVENT_LOG); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + if (rc < 0) + printk(KERN_ERR "gsmi: Log Shutdown Reason failed\n"); + else + printk(KERN_EMERG "gsmi: Log Shutdown Reason 0x%02x\n", + reason); + + return rc; +} + +static int gsmi_reboot_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + gsmi_shutdown_reason(GSMI_SHUTDOWN_CLEAN); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_reboot_notifier = { + .notifier_call = gsmi_reboot_callback +}; + +static int gsmi_die_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + if (reason == DIE_OOPS) + gsmi_shutdown_reason(GSMI_SHUTDOWN_OOPS); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_die_notifier = { + .notifier_call = gsmi_die_callback +}; + +static int gsmi_panic_callback(struct notifier_block *nb, + unsigned long reason, void *arg) +{ + + /* + * Panic callbacks are executed with all other CPUs stopped, + * so we must not attempt to spin waiting for gsmi_dev.lock + * to be released. + */ + if (spin_is_locked(&gsmi_dev.lock)) + return NOTIFY_DONE; + + gsmi_shutdown_reason(GSMI_SHUTDOWN_PANIC); + return NOTIFY_DONE; +} + +static struct notifier_block gsmi_panic_notifier = { + .notifier_call = gsmi_panic_callback, +}; + +/* + * This hash function was blatantly copied from include/linux/hash.h. + * It is used by this driver to obfuscate a board name that requires a + * quirk within this driver. + * + * Please do not remove this copy of the function as any changes to the + * global utility hash_64() function would break this driver's ability + * to identify a board and provide the appropriate quirk -- mikew@google.com + */ +static u64 __init local_hash_64(u64 val, unsigned bits) +{ + u64 hash = val; + + /* Sigh, gcc can't optimise this alone like it does for 32 bits. */ + u64 n = hash; + n <<= 18; + hash -= n; + n <<= 33; + hash -= n; + n <<= 3; + hash += n; + n <<= 3; + hash -= n; + n <<= 4; + hash += n; + n <<= 2; + hash += n; + + /* High bits are more random, so use them. */ + return hash >> (64 - bits); +} + +static u32 __init hash_oem_table_id(char s[8]) +{ + u64 input; + memcpy(&input, s, 8); + return local_hash_64(input, 32); +} + +static const struct dmi_system_id gsmi_dmi_table[] __initconst = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + { + .ident = "Coreboot Firmware", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, gsmi_dmi_table); + +static __init int gsmi_system_valid(void) +{ + u32 hash; + u16 cmd, result; + + if (!dmi_check_system(gsmi_dmi_table)) + return -ENODEV; + + /* + * Only newer firmware supports the gsmi interface. All older + * firmware that didn't support this interface used to plug the + * table name in the first four bytes of the oem_table_id field. + * Newer firmware doesn't do that though, so use that as the + * discriminant factor. We have to do this in order to + * whitewash our board names out of the public driver. + */ + if (!strncmp(acpi_gbl_FADT.header.oem_table_id, "FACP", 4)) { + printk(KERN_INFO "gsmi: Board is too old\n"); + return -ENODEV; + } + + /* Disable on board with 1.0 BIOS due to Google bug 2602657 */ + hash = hash_oem_table_id(acpi_gbl_FADT.header.oem_table_id); + if (hash == QUIRKY_BOARD_HASH) { + const char *bios_ver = dmi_get_system_info(DMI_BIOS_VERSION); + if (strncmp(bios_ver, "1.0", 3) == 0) { + pr_info("gsmi: disabled on this board's BIOS %s\n", + bios_ver); + return -ENODEV; + } + } + + /* check for valid SMI command port in ACPI FADT */ + if (acpi_gbl_FADT.smi_command == 0) { + pr_info("gsmi: missing smi_command\n"); + return -ENODEV; + } + + /* Test the smihandler with a bogus command. If it leaves the + * calling argument in %ax untouched, there is no handler for + * GSMI commands. + */ + cmd = GSMI_CALLBACK | GSMI_CMD_RESERVED << 8; + asm volatile ( + "outb %%al, %%dx\n\t" + : "=a" (result) + : "0" (cmd), + "d" (acpi_gbl_FADT.smi_command) + : "memory", "cc" + ); + if (cmd == result) { + pr_info("gsmi: no gsmi handler in firmware\n"); + return -ENODEV; + } + + /* Found */ + return 0; +} + +static struct kobject *gsmi_kobj; + +static const struct platform_device_info gsmi_dev_info = { + .name = "gsmi", + .id = -1, + /* SMI callbacks require 32bit addresses */ + .dma_mask = DMA_BIT_MASK(32), +}; + +#ifdef CONFIG_PM +static void gsmi_log_s0ix_info(u8 cmd) +{ + unsigned long flags; + + /* + * If platform has not enabled S0ix logging, then no action is + * necessary. + */ + if (!s0ix_logging_enable) + return; + + spin_lock_irqsave(&gsmi_dev.lock, flags); + + memset(gsmi_dev.param_buf->start, 0, gsmi_dev.param_buf->length); + + gsmi_exec(GSMI_CALLBACK, cmd); + + spin_unlock_irqrestore(&gsmi_dev.lock, flags); +} + +static int gsmi_log_s0ix_suspend(struct device *dev) +{ + /* + * If system is not suspending via firmware using the standard ACPI Sx + * types, then make a GSMI call to log the suspend info. + */ + if (!pm_suspend_via_firmware()) + gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_SUSPEND); + + /* + * Always return success, since we do not want suspend + * to fail just because of logging failure. + */ + return 0; +} + +static int gsmi_log_s0ix_resume(struct device *dev) +{ + /* + * If system did not resume via firmware, then make a GSMI call to log + * the resume info and wake source. + */ + if (!pm_resume_via_firmware()) + gsmi_log_s0ix_info(GSMI_CMD_LOG_S0IX_RESUME); + + /* + * Always return success, since we do not want resume + * to fail just because of logging failure. + */ + return 0; +} + +static const struct dev_pm_ops gsmi_pm_ops = { + .suspend_noirq = gsmi_log_s0ix_suspend, + .resume_noirq = gsmi_log_s0ix_resume, +}; + +static int gsmi_platform_driver_probe(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver gsmi_driver_info = { + .driver = { + .name = "gsmi", + .pm = &gsmi_pm_ops, + }, + .probe = gsmi_platform_driver_probe, +}; +#endif + +static __init int gsmi_init(void) +{ + unsigned long flags; + int ret; + + ret = gsmi_system_valid(); + if (ret) + return ret; + + gsmi_dev.smi_cmd = acpi_gbl_FADT.smi_command; + +#ifdef CONFIG_PM + ret = platform_driver_register(&gsmi_driver_info); + if (unlikely(ret)) { + printk(KERN_ERR "gsmi: unable to register platform driver\n"); + return ret; + } +#endif + + /* register device */ + gsmi_dev.pdev = platform_device_register_full(&gsmi_dev_info); + if (IS_ERR(gsmi_dev.pdev)) { + printk(KERN_ERR "gsmi: unable to register platform device\n"); + ret = PTR_ERR(gsmi_dev.pdev); + goto out_unregister; + } + + /* SMI access needs to be serialized */ + spin_lock_init(&gsmi_dev.lock); + + ret = -ENOMEM; + + /* + * SLAB cache is created using SLAB_CACHE_DMA32 to ensure that the + * allocations for gsmi_buf come from the DMA32 memory zone. These + * buffers have nothing to do with DMA. They are required for + * communication with firmware executing in SMI mode which can only + * access the bottom 4GiB of physical memory. Since DMA32 memory zone + * guarantees allocation under the 4GiB boundary, this driver creates + * a SLAB cache with SLAB_CACHE_DMA32 flag. + */ + gsmi_dev.mem_pool = kmem_cache_create("gsmi", GSMI_BUF_SIZE, + GSMI_BUF_ALIGN, + SLAB_CACHE_DMA32, NULL); + if (!gsmi_dev.mem_pool) + goto out_err; + + /* + * pre-allocate buffers because sometimes we are called when + * this is not feasible: oops, panic, die, mce, etc + */ + gsmi_dev.name_buf = gsmi_buf_alloc(); + if (!gsmi_dev.name_buf) { + printk(KERN_ERR "gsmi: failed to allocate name buffer\n"); + goto out_err; + } + + gsmi_dev.data_buf = gsmi_buf_alloc(); + if (!gsmi_dev.data_buf) { + printk(KERN_ERR "gsmi: failed to allocate data buffer\n"); + goto out_err; + } + + gsmi_dev.param_buf = gsmi_buf_alloc(); + if (!gsmi_dev.param_buf) { + printk(KERN_ERR "gsmi: failed to allocate param buffer\n"); + goto out_err; + } + + /* + * Determine type of handshake used to serialize the SMI + * entry. See also gsmi_exec(). + * + * There's a "behavior" present on some chipsets where writing the + * SMI trigger register in the southbridge doesn't result in an + * immediate SMI. Rather, the processor can execute "a few" more + * instructions before the SMI takes effect. To ensure synchronous + * behavior, implement a handshake between the kernel driver and the + * firmware handler to spin until released. This ioctl determines + * the type of handshake. + * + * NONE: The firmware handler does not implement any + * handshake. Either it doesn't need to, or it's legacy firmware + * that doesn't know it needs to and never will. + * + * CF: The firmware handler will clear the CF in the saved + * state before returning. The driver may set the CF and test for + * it to clear before proceeding. + * + * SPIN: The firmware handler does not implement any handshake + * but the driver should spin for a hundred or so microseconds + * to ensure the SMI has triggered. + * + * Finally, the handler will return -ENOSYS if + * GSMI_CMD_HANDSHAKE_TYPE is unimplemented, which implies + * HANDSHAKE_NONE. + */ + spin_lock_irqsave(&gsmi_dev.lock, flags); + gsmi_dev.handshake_type = GSMI_HANDSHAKE_SPIN; + gsmi_dev.handshake_type = + gsmi_exec(GSMI_CALLBACK, GSMI_CMD_HANDSHAKE_TYPE); + if (gsmi_dev.handshake_type == -ENOSYS) + gsmi_dev.handshake_type = GSMI_HANDSHAKE_NONE; + spin_unlock_irqrestore(&gsmi_dev.lock, flags); + + /* Remove and clean up gsmi if the handshake could not complete. */ + if (gsmi_dev.handshake_type == -ENXIO) { + printk(KERN_INFO "gsmi version " DRIVER_VERSION + " failed to load\n"); + ret = -ENODEV; + goto out_err; + } + + /* Register in the firmware directory */ + ret = -ENOMEM; + gsmi_kobj = kobject_create_and_add("gsmi", firmware_kobj); + if (!gsmi_kobj) { + printk(KERN_INFO "gsmi: Failed to create firmware kobj\n"); + goto out_err; + } + + /* Setup eventlog access */ + ret = sysfs_create_bin_file(gsmi_kobj, &eventlog_bin_attr); + if (ret) { + printk(KERN_INFO "gsmi: Failed to setup eventlog"); + goto out_err; + } + + /* Other attributes */ + ret = sysfs_create_files(gsmi_kobj, gsmi_attrs); + if (ret) { + printk(KERN_INFO "gsmi: Failed to add attrs"); + goto out_remove_bin_file; + } + +#ifdef CONFIG_EFI + ret = efivars_register(&efivars, &efivar_ops); + if (ret) { + printk(KERN_INFO "gsmi: Failed to register efivars\n"); + sysfs_remove_files(gsmi_kobj, gsmi_attrs); + goto out_remove_bin_file; + } +#endif + + register_reboot_notifier(&gsmi_reboot_notifier); + register_die_notifier(&gsmi_die_notifier); + atomic_notifier_chain_register(&panic_notifier_list, + &gsmi_panic_notifier); + + printk(KERN_INFO "gsmi version " DRIVER_VERSION " loaded\n"); + + return 0; + +out_remove_bin_file: + sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr); +out_err: + kobject_put(gsmi_kobj); + gsmi_buf_free(gsmi_dev.param_buf); + gsmi_buf_free(gsmi_dev.data_buf); + gsmi_buf_free(gsmi_dev.name_buf); + kmem_cache_destroy(gsmi_dev.mem_pool); + platform_device_unregister(gsmi_dev.pdev); +out_unregister: +#ifdef CONFIG_PM + platform_driver_unregister(&gsmi_driver_info); +#endif + pr_info("gsmi: failed to load: %d\n", ret); + return ret; +} + +static void __exit gsmi_exit(void) +{ + unregister_reboot_notifier(&gsmi_reboot_notifier); + unregister_die_notifier(&gsmi_die_notifier); + atomic_notifier_chain_unregister(&panic_notifier_list, + &gsmi_panic_notifier); +#ifdef CONFIG_EFI + efivars_unregister(&efivars); +#endif + + sysfs_remove_files(gsmi_kobj, gsmi_attrs); + sysfs_remove_bin_file(gsmi_kobj, &eventlog_bin_attr); + kobject_put(gsmi_kobj); + gsmi_buf_free(gsmi_dev.param_buf); + gsmi_buf_free(gsmi_dev.data_buf); + gsmi_buf_free(gsmi_dev.name_buf); + kmem_cache_destroy(gsmi_dev.mem_pool); + platform_device_unregister(gsmi_dev.pdev); +#ifdef CONFIG_PM + platform_driver_unregister(&gsmi_driver_info); +#endif +} + +module_init(gsmi_init); +module_exit(gsmi_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("EFI SMI interface for Google platforms"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c new file mode 100644 index 000000000..75e372732 --- /dev/null +++ b/drivers/firmware/google/memconsole-coreboot.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * memconsole-coreboot.c + * + * Memory based BIOS console accessed through coreboot table. + * + * Copyright 2017 Google Inc. + */ + +#include +#include +#include +#include +#include + +#include "memconsole.h" +#include "coreboot_table.h" + +#define CB_TAG_CBMEM_CONSOLE 0x17 + +/* CBMEM firmware console log descriptor. */ +struct cbmem_cons { + u32 size_dont_access_after_boot; + u32 cursor; + u8 body[]; +} __packed; + +#define CURSOR_MASK ((1 << 28) - 1) +#define OVERFLOW (1 << 31) + +static struct cbmem_cons *cbmem_console; +static u32 cbmem_console_size; + +/* + * The cbmem_console structure is read again on every access because it may + * change at any time if runtime firmware logs new messages. This may rarely + * lead to race conditions where the firmware overwrites the beginning of the + * ring buffer with more lines after we have already read |cursor|. It should be + * rare and harmless enough that we don't spend extra effort working around it. + */ +static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count) +{ + u32 cursor = cbmem_console->cursor & CURSOR_MASK; + u32 flags = cbmem_console->cursor & ~CURSOR_MASK; + u32 size = cbmem_console_size; + struct seg { /* describes ring buffer segments in logical order */ + u32 phys; /* physical offset from start of mem buffer */ + u32 len; /* length of segment */ + } seg[2] = { {0}, {0} }; + size_t done = 0; + int i; + + if (flags & OVERFLOW) { + if (cursor > size) /* Shouldn't really happen, but... */ + cursor = 0; + seg[0] = (struct seg){.phys = cursor, .len = size - cursor}; + seg[1] = (struct seg){.phys = 0, .len = cursor}; + } else { + seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)}; + } + + for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) { + done += memory_read_from_buffer(buf + done, count - done, &pos, + cbmem_console->body + seg[i].phys, seg[i].len); + pos -= seg[i].len; + } + return done; +} + +static int memconsole_probe(struct coreboot_device *dev) +{ + struct cbmem_cons *tmp_cbmc; + + 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 = devm_memremap(&dev->dev, dev->cbmem_ref.cbmem_addr, + cbmem_console_size + sizeof(*cbmem_console), + MEMREMAP_WB); + memunmap(tmp_cbmc); + + if (IS_ERR(cbmem_console)) + return PTR_ERR(cbmem_console); + + memconsole_setup(memconsole_coreboot_read); + + return memconsole_sysfs_init(); +} + +static void memconsole_remove(struct coreboot_device *dev) +{ + memconsole_exit(); +} + +static const struct coreboot_device_id memconsole_ids[] = { + { .tag = CB_TAG_CBMEM_CONSOLE }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(coreboot, memconsole_ids); + +static struct coreboot_driver memconsole_driver = { + .probe = memconsole_probe, + .remove = memconsole_remove, + .drv = { + .name = "memconsole", + }, + .id_table = memconsole_ids, +}; +module_coreboot_driver(memconsole_driver); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Memory based BIOS console accessed through coreboot table"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c new file mode 100644 index 000000000..a0974c376 --- /dev/null +++ b/drivers/firmware/google/memconsole-x86-legacy.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * memconsole-x86-legacy.c + * + * EBDA specific parts of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "memconsole.h" + +#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE +#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24)) + +struct biosmemcon_ebda { + u32 signature; + union { + struct { + u8 enabled; + u32 buffer_addr; + u16 start; + u16 end; + u16 num_chars; + u8 wrapped; + } __packed v1; + struct { + u32 buffer_addr; + /* Misdocumented as number of pages! */ + u16 num_bytes; + u16 start; + u16 end; + } __packed v2; + }; +} __packed; + +static char *memconsole_baseaddr; +static size_t memconsole_length; + +static ssize_t memconsole_read(char *buf, loff_t pos, size_t count) +{ + return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr, + memconsole_length); +} + +static void found_v1_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n", + hdr->v1.buffer_addr, hdr->v1.start, + hdr->v1.end, hdr->v1.num_chars); + + memconsole_baseaddr = phys_to_virt(hdr->v1.buffer_addr); + memconsole_length = hdr->v1.num_chars; + memconsole_setup(memconsole_read); +} + +static void found_v2_header(struct biosmemcon_ebda *hdr) +{ + pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n", + hdr); + pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n", + hdr->v2.buffer_addr, hdr->v2.start, + hdr->v2.end, hdr->v2.num_bytes); + + memconsole_baseaddr = phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start); + memconsole_length = hdr->v2.end - hdr->v2.start; + memconsole_setup(memconsole_read); +} + +/* + * Search through the EBDA for the BIOS Memory Console, and + * set the global variables to point to it. Return true if found. + */ +static bool memconsole_ebda_init(void) +{ + unsigned int address; + size_t length, cur; + + address = get_bios_ebda(); + if (!address) { + pr_info("memconsole: BIOS EBDA non-existent.\n"); + return false; + } + + /* EBDA length is byte 0 of EBDA (in KB) */ + length = *(u8 *)phys_to_virt(address); + length <<= 10; /* convert to bytes */ + + /* + * Search through EBDA for BIOS memory console structure + * note: signature is not necessarily dword-aligned + */ + for (cur = 0; cur < length; cur++) { + struct biosmemcon_ebda *hdr = phys_to_virt(address + cur); + + /* memconsole v1 */ + if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) { + found_v1_header(hdr); + return true; + } + + /* memconsole v2 */ + if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) { + found_v2_header(hdr); + return true; + } + } + + pr_info("memconsole: BIOS console EBDA structure not found!\n"); + return false; +} + +static const struct dmi_system_id memconsole_dmi_table[] __initconst = { + { + .ident = "Google Board", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table); + +static bool __init memconsole_find(void) +{ + if (!dmi_check_system(memconsole_dmi_table)) + return false; + + return memconsole_ebda_init(); +} + +static int __init memconsole_x86_init(void) +{ + if (!memconsole_find()) + return -ENODEV; + + return memconsole_sysfs_init(); +} + +static void __exit memconsole_x86_exit(void) +{ + memconsole_exit(); +} + +module_init(memconsole_x86_init); +module_exit(memconsole_x86_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("EBDA specific parts of the memory based BIOS console."); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c new file mode 100644 index 000000000..6138a1653 --- /dev/null +++ b/drivers/firmware/google/memconsole.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * memconsole.c + * + * Architecture-independent parts of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + */ + +#include +#include +#include + +#include "memconsole.h" + +static ssize_t memconsole_read(struct file *filp, struct kobject *kobp, + const struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + ssize_t (*memconsole_read_func)(char *, loff_t, size_t); + + memconsole_read_func = bin_attr->private; + if (WARN_ON_ONCE(!memconsole_read_func)) + return -EIO; + + return memconsole_read_func(buf, pos, count); +} + +static struct bin_attribute memconsole_bin_attr = { + .attr = {.name = "log", .mode = 0444}, + .read = memconsole_read, +}; + +void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t)) +{ + memconsole_bin_attr.private = read_func; +} +EXPORT_SYMBOL(memconsole_setup); + +int memconsole_sysfs_init(void) +{ + return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr); +} +EXPORT_SYMBOL(memconsole_sysfs_init); + +void memconsole_exit(void) +{ + sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr); +} +EXPORT_SYMBOL(memconsole_exit); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Architecture-independent parts of the memory based BIOS console"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h new file mode 100644 index 000000000..aaff2b72b --- /dev/null +++ b/drivers/firmware/google/memconsole.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * memconsole.h + * + * Internal headers of the memory based BIOS console. + * + * Copyright 2017 Google Inc. + */ + +#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H +#define __FIRMWARE_GOOGLE_MEMCONSOLE_H + +#include + +/* + * memconsole_setup + * + * Initialize the memory console, passing the function to handle read accesses. + */ +void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t)); + +/* + * memconsole_sysfs_init + * + * Update memory console length and create binary file + * for firmware object. + */ +int memconsole_sysfs_init(void); + +/* memconsole_exit + * + * Unmap the console buffer. + */ +void memconsole_exit(void); + +#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */ diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c new file mode 100644 index 000000000..fbb5a8d7c --- /dev/null +++ b/drivers/firmware/google/vpd.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vpd.c + * + * Driver for exporting VPD content to sysfs. + * + * Copyright 2017 Google Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" +#include "vpd_decode.h" + +#define CB_TAG_VPD 0x2c +#define VPD_CBMEM_MAGIC 0x43524f53 + +static struct kobject *vpd_kobj; + +struct vpd_cbmem { + u32 magic; + u32 version; + u32 ro_size; + u32 rw_size; + u8 blob[]; +}; + +struct vpd_section { + bool enabled; + const char *name; + char *raw_name; /* the string name_raw */ + struct kobject *kobj; /* vpd/name directory */ + char *baseaddr; + struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */ + struct list_head attribs; /* key/value in vpd_attrib_info list */ +}; + +struct vpd_attrib_info { + char *key; + const char *value; + struct bin_attribute bin_attr; + struct list_head list; +}; + +static struct vpd_section ro_vpd; +static struct vpd_section rw_vpd; + +static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp, + const struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_attrib_info *info = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, info->value, + info->bin_attr.size); +} + +/* + * vpd_section_check_key_name() + * + * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but + * old firmware versions may have entries like "S/N" which are problematic when + * exporting them as sysfs attributes. These keys present in old firmwares are + * ignored. + * + * Returns VPD_OK for a valid key name, VPD_FAIL otherwise. + * + * @key: The key name to check + * @key_len: key name length + */ +static int vpd_section_check_key_name(const u8 *key, s32 key_len) +{ + int c; + + while (key_len-- > 0) { + c = *key++; + + if (!isalnum(c) && c != '_') + return VPD_FAIL; + } + + return VPD_OK; +} + +static int vpd_section_attrib_add(const u8 *key, u32 key_len, + const u8 *value, u32 value_len, + void *arg) +{ + int ret; + struct vpd_section *sec = arg; + struct vpd_attrib_info *info; + + /* + * Return VPD_OK immediately to decode next entry if the current key + * name contains invalid characters. + */ + if (vpd_section_check_key_name(key, key_len) != VPD_OK) + return VPD_OK; + + info = kzalloc_obj(*info); + if (!info) + return -ENOMEM; + + info->key = kstrndup(key, key_len, GFP_KERNEL); + if (!info->key) { + ret = -ENOMEM; + goto free_info; + } + + sysfs_bin_attr_init(&info->bin_attr); + info->bin_attr.attr.name = info->key; + info->bin_attr.attr.mode = 0444; + info->bin_attr.size = value_len; + info->bin_attr.read = vpd_attrib_read; + info->bin_attr.private = info; + + info->value = value; + + INIT_LIST_HEAD(&info->list); + + ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr); + if (ret) + goto free_info_key; + + list_add_tail(&info->list, &sec->attribs); + return 0; + +free_info_key: + kfree(info->key); +free_info: + kfree(info); + + return ret; +} + +static void vpd_section_attrib_destroy(struct vpd_section *sec) +{ + struct vpd_attrib_info *info; + struct vpd_attrib_info *temp; + + list_for_each_entry_safe(info, temp, &sec->attribs, list) { + sysfs_remove_bin_file(sec->kobj, &info->bin_attr); + kfree(info->key); + kfree(info); + } +} + +static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp, + const struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct vpd_section *sec = bin_attr->private; + + return memory_read_from_buffer(buf, count, &pos, sec->baseaddr, + sec->bin_attr.size); +} + +static int vpd_section_create_attribs(struct vpd_section *sec) +{ + s32 consumed; + int ret; + + consumed = 0; + do { + ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr, + &consumed, vpd_section_attrib_add, sec); + } while (ret == VPD_OK); + + return 0; +} + +static int vpd_section_init(const char *name, struct vpd_section *sec, + phys_addr_t physaddr, size_t size) +{ + int err; + + sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); + if (!sec->baseaddr) + return -ENOMEM; + + sec->name = name; + + /* We want to export the raw partition with name ${name}_raw */ + sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name); + if (!sec->raw_name) { + err = -ENOMEM; + goto err_memunmap; + } + + sysfs_bin_attr_init(&sec->bin_attr); + sec->bin_attr.attr.name = sec->raw_name; + sec->bin_attr.attr.mode = 0444; + sec->bin_attr.size = size; + sec->bin_attr.read = vpd_section_read; + sec->bin_attr.private = sec; + + err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); + if (err) + goto err_free_raw_name; + + sec->kobj = kobject_create_and_add(name, vpd_kobj); + if (!sec->kobj) { + err = -EINVAL; + goto err_sysfs_remove; + } + + INIT_LIST_HEAD(&sec->attribs); + vpd_section_create_attribs(sec); + + sec->enabled = true; + + return 0; + +err_sysfs_remove: + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); +err_free_raw_name: + kfree(sec->raw_name); +err_memunmap: + memunmap(sec->baseaddr); + return err; +} + +static int vpd_section_destroy(struct vpd_section *sec) +{ + if (sec->enabled) { + vpd_section_attrib_destroy(sec); + kobject_put(sec->kobj); + sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); + kfree(sec->raw_name); + memunmap(sec->baseaddr); + sec->enabled = false; + } + + return 0; +} + +static int vpd_sections_init(phys_addr_t physaddr) +{ + struct vpd_cbmem *temp; + struct vpd_cbmem header; + int ret = 0; + + temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB); + if (!temp) + return -ENOMEM; + + memcpy(&header, temp, sizeof(struct vpd_cbmem)); + memunmap(temp); + + if (header.magic != VPD_CBMEM_MAGIC) + return -ENODEV; + + if (header.ro_size) { + ret = vpd_section_init("ro", &ro_vpd, + physaddr + sizeof(struct vpd_cbmem), + header.ro_size); + if (ret) + return ret; + } + + if (header.rw_size) { + ret = vpd_section_init("rw", &rw_vpd, + physaddr + sizeof(struct vpd_cbmem) + + header.ro_size, header.rw_size); + if (ret) { + vpd_section_destroy(&ro_vpd); + return ret; + } + } + + return 0; +} + +static int vpd_probe(struct coreboot_device *dev) +{ + int ret; + + vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); + if (!vpd_kobj) + return -ENOMEM; + + ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr); + if (ret) { + kobject_put(vpd_kobj); + return ret; + } + + return 0; +} + +static void vpd_remove(struct coreboot_device *dev) +{ + vpd_section_destroy(&ro_vpd); + vpd_section_destroy(&rw_vpd); + + kobject_put(vpd_kobj); +} + +static const struct coreboot_device_id vpd_ids[] = { + { .tag = CB_TAG_VPD }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(coreboot, vpd_ids); + +static struct coreboot_driver vpd_driver = { + .probe = vpd_probe, + .remove = vpd_remove, + .drv = { + .name = "vpd", + }, + .id_table = vpd_ids, +}; +module_coreboot_driver(vpd_driver); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Driver for exporting Vital Product Data content to sysfs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c new file mode 100644 index 000000000..5c6f2a74f --- /dev/null +++ b/drivers/firmware/google/vpd_decode.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vpd_decode.c + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + */ + +#include "vpd_decode.h" + +static int vpd_decode_len(const u32 max_len, const u8 *in, + u32 *length, u32 *decoded_len) +{ + u8 more; + int i = 0; + + if (!length || !decoded_len) + return VPD_FAIL; + + *length = 0; + do { + if (i >= max_len) + return VPD_FAIL; + + more = in[i] & 0x80; + *length <<= 7; + *length |= in[i] & 0x7f; + ++i; + } while (more); + + *decoded_len = i; + return VPD_OK; +} + +static int vpd_decode_entry(const u32 max_len, const u8 *input_buf, + u32 *_consumed, const u8 **entry, u32 *entry_len) +{ + u32 decoded_len; + u32 consumed = *_consumed; + + if (vpd_decode_len(max_len - consumed, &input_buf[consumed], + entry_len, &decoded_len) != VPD_OK) + return VPD_FAIL; + if (max_len - consumed < decoded_len) + return VPD_FAIL; + + consumed += decoded_len; + *entry = input_buf + consumed; + + /* entry_len is untrusted data and must be checked again. */ + if (max_len - consumed < *entry_len) + return VPD_FAIL; + + consumed += *entry_len; + *_consumed = consumed; + return VPD_OK; +} + +int vpd_decode_string(const u32 max_len, const u8 *input_buf, u32 *consumed, + vpd_decode_callback callback, void *callback_arg) +{ + int type; + u32 key_len; + u32 value_len; + const u8 *key; + const u8 *value; + + /* type */ + if (*consumed >= max_len) + return VPD_FAIL; + + type = input_buf[*consumed]; + + switch (type) { + case VPD_TYPE_INFO: + case VPD_TYPE_STRING: + (*consumed)++; + + if (vpd_decode_entry(max_len, input_buf, consumed, &key, + &key_len) != VPD_OK) + return VPD_FAIL; + + if (vpd_decode_entry(max_len, input_buf, consumed, &value, + &value_len) != VPD_OK) + return VPD_FAIL; + + if (type == VPD_TYPE_STRING) + return callback(key, key_len, value, value_len, + callback_arg); + break; + + default: + return VPD_FAIL; + } + + return VPD_OK; +} diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h new file mode 100644 index 000000000..8dbe41cac --- /dev/null +++ b/drivers/firmware/google/vpd_decode.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * vpd_decode.h + * + * Google VPD decoding routines. + * + * Copyright 2017 Google Inc. + */ + +#ifndef __VPD_DECODE_H +#define __VPD_DECODE_H + +#include + +enum { + VPD_OK = 0, + VPD_FAIL, +}; + +enum { + VPD_TYPE_TERMINATOR = 0, + VPD_TYPE_STRING, + VPD_TYPE_INFO = 0xfe, + VPD_TYPE_IMPLICIT_TERMINATOR = 0xff, +}; + +/* Callback for vpd_decode_string to invoke. */ +typedef int vpd_decode_callback(const u8 *key, u32 key_len, + const u8 *value, u32 value_len, + void *arg); + +/* + * vpd_decode_string + * + * Given the encoded string, this function invokes callback with extracted + * (key, value). The *consumed will be plused the number of bytes consumed in + * this function. + * + * The input_buf points to the first byte of the input buffer. + * + * The *consumed starts from 0, which is actually the next byte to be decoded. + * It can be non-zero to be used in multiple calls. + * + * If one entry is successfully decoded, sends it to callback and returns the + * result. + */ +int vpd_decode_string(const u32 max_len, const u8 *input_buf, u32 *consumed, + vpd_decode_callback callback, void *callback_arg); + +#endif /* __VPD_DECODE_H */ -- cgit v1.3.1