diff options
| author | Abdelkader Boudih <freebsd@seuros.com> | 2026-07-29 19:11:55 +0000 |
|---|---|---|
| committer | Adrian Chadd <adrian@FreeBSD.org> | 2026-07-29 19:12:41 +0000 |
| commit | 5f74217c05a99f38a31a6e4950220964595d4ac8 (patch) | |
| tree | 8537db3db384b086738047ff277033b92963a1ff | |
| parent | ea44c37d0bd15e796c85f84b57c211ac220c550c (diff) | |
coreboot: Add coreboot firmware table driver
Nexus-attached driver that discovers and parses coreboot's LBIO tables
from physical memory. Exposes firmware metadata (version, build info,
mainboard, serial config, TSC frequency, CBMEM entries) via sysctl
hw.coreboot.*, the firmware console ring buffer via /dev/coreboot_console,
and structured CBMEM entry access via /dev/cbmem ioctl interface.
Tested on:
- Qotom Q535G6 (Kabylake)
- Intel NUC D54250WYK (Haswell)
- Intel NUC D33217GKE (Ivy Bridge)
- Dell 3100 2-in-1 (Gabbiter)
- Dell 3100 (Fleex)
- Lenovo IdeaPad 320s
- Lenovo ThinkPad T480
- HP Chromebook 11 G4
- HP Chromebook 11 G5
- HP Chromebook 11 G6 EE
- HP Chromebook 14 G4
- HP Chromebook 14 G5
- HP Chromebook x360 11 G1 EE
- HP Chromebook x360 11 G2 EE
- HP Chromebook x360 14 G1
- Acer C720
- Acer Chromebook 11
- Lenovo N22
Reviewed by: ngie, kib, adrian
Differential Revision: https://reviews.freebsd.org/D55649
| -rw-r--r-- | etc/mtree/BSD.include.dist | 2 | ||||
| -rw-r--r-- | include/Makefile | 11 | ||||
| -rw-r--r-- | share/man/man4/Makefile | 1 | ||||
| -rw-r--r-- | share/man/man4/coreboot.4 | 355 | ||||
| -rw-r--r-- | sys/conf/files | 4 | ||||
| -rw-r--r-- | sys/conf/files.x86 | 4 | ||||
| -rw-r--r-- | sys/dev/coreboot/coreboot.c | 1502 | ||||
| -rw-r--r-- | sys/dev/coreboot/coreboot.h | 632 | ||||
| -rw-r--r-- | sys/dev/coreboot/coreboot_cbmem.c | 151 | ||||
| -rw-r--r-- | sys/dev/coreboot/coreboot_console.c | 192 | ||||
| -rw-r--r-- | sys/dev/coreboot/coreboot_timestamps.c | 214 | ||||
| -rw-r--r-- | sys/dev/coreboot/corebootio.h | 123 | ||||
| -rw-r--r-- | sys/modules/Makefile | 2 | ||||
| -rw-r--r-- | sys/modules/coreboot/Makefile | 7 |
14 files changed, 3198 insertions, 2 deletions
diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist index 81ea0842035c..05e739bcf5dd 100644 --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -207,6 +207,8 @@ .. ciss .. + coreboot + .. evdev .. filemon diff --git a/include/Makefile b/include/Makefile index 3d83556f5238..60c2ca5819dc 100644 --- a/include/Makefile +++ b/include/Makefile @@ -151,6 +151,10 @@ CAMSCSI= scsi_all.h \ smp_all.h CAMSCSIDIR= ${INCLUDEDIR}/cam/scsi +.PATH: ${SRCTOP}/sys/dev/coreboot +COREBOOT= corebootio.h +COREBOOTDIR= ${INCLUDEDIR}/dev/coreboot + .PATH: ${SRCTOP}/sys/fs/cd9660 FS9660= cd9660_mount.h \ cd9660_node.h \ @@ -254,6 +258,7 @@ INCSGROUPS= INCS \ CAMMMC \ CAMNVME \ CAMSCSI \ + COREBOOT \ CRYPTO \ EVDEV \ FS9660 \ @@ -381,7 +386,7 @@ copies: .PHONY .META cd ${SDESTDIR}${INCLUDEDIR}; find ${LDIRS} ${LSUBDIRS} ${LSUBSUBDIRS} crypto \ machine machine/pc ${_MARCHS} -maxdepth 1 -mindepth 1 -type l \ -name "*.h" -print -delete || true -.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec:Ndev/wg} ${LSUBSUBDIRS} +.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/coreboot:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec:Ndev/wg} ${LSUBSUBDIRS} cd ${SRCTOP}/sys; \ ${INSTALL} -C ${TAG_ARGS:D${TAG_ARGS},dev} -o ${BINOWN} -g ${BINGRP} -m 444 $i/*.h \ ${SDESTDIR}${INCLUDEDIR}/$i @@ -417,7 +422,7 @@ symlinks: .PHONY .META cd ${SRCTOP}; ${INSTALL_SYMLINK} ${TAG_ARGS:D${TAG_ARGS},dev} \ $$(printf '../../../%s ' sys/$i/*.h) ${SDESTDIR}${INCLUDEDIR}/$i .endfor -.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec:Ndev/wg} +.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/coreboot:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec:Ndev/wg} cd ${SRCTOP}; ${INSTALL_SYMLINK} ${TAG_ARGS:D${TAG_ARGS},dev} \ $$(printf '../../../../%s ' sys/$i/*.h) ${SDESTDIR}${INCLUDEDIR}/$i .endfor @@ -438,6 +443,8 @@ symlinks: .PHONY .META ${SDESTDIR}${INCLUDEDIR}/dev/hyperv; \ ${INSTALL_SYMLINK} ${TAG_ARGS:D${TAG_ARGS},dev} ../../../../sys/dev/pci/pcireg.h \ ${SDESTDIR}${INCLUDEDIR}/dev/pci; \ + ${INSTALL_SYMLINK} ${TAG_ARGS:D${TAG_ARGS},dev} ../../../../sys/dev/coreboot/corebootio.h \ + ${SDESTDIR}${INCLUDEDIR}/dev/coreboot; \ ${INSTALL_SYMLINK} ${TAG_ARGS:D${TAG_ARGS},dev} ../../../../sys/dev/veriexec/veriexec_ioctl.h \ ${SDESTDIR}${INCLUDEDIR}/dev/veriexec; .for i in ${LSUBSUBDIRS} diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 80b9ac16ce87..cd7a16c58923 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -126,6 +126,7 @@ MAN= aac.4 \ chromebook_platform.4 \ ${_chvgpio.4} \ ciss.4 \ + coreboot.4 \ ${_coretemp.4} \ cp2112.4 \ ${_cpuctl.4} \ diff --git a/share/man/man4/coreboot.4 b/share/man/man4/coreboot.4 new file mode 100644 index 000000000000..315a466cee8c --- /dev/null +++ b/share/man/man4/coreboot.4 @@ -0,0 +1,355 @@ +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd March 2, 2026 +.Dt COREBOOT 4 +.Os +.Sh NAME +.Nm coreboot +.Nd coreboot firmware table driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device coreboot" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +coreboot_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides access to firmware tables created by the coreboot +open-source firmware project. +It discovers the coreboot table by scanning low memory for the +.Dq LBIO +signature, follows any forward pointer to the real table in high memory, +and validates the IP-style checksums. +.Pp +The driver exposes firmware information through three interfaces: +.Bl -tag -width "/dev/coreboot_console" +.It Sy sysctl +A +.Xr sysctl 8 +tree under +.Va hw.coreboot +provides access to: +.Bl -bullet -compact +.It +firmware version, build date, and compiler information +.It +mainboard identification and board configuration +.It +MAC addresses, framebuffer, GPIO, and SPI flash details +.It +TPM information and SMMSTORE configuration +.It +ACPI RSDP address, PCIe controller base, and TSC frequency +.It +boot timestamps and CBMEM entry enumeration +.El +.It Pa /dev/coreboot_console +A character device providing read-only access to the CBMEM firmware console +ring buffer. +This contains coreboot's own boot log, analogous to +.Xr dmesg 8 +but for the firmware stage before the OS kernel starts. +The ring buffer uses bit 31 of its cursor as an overflow indicator; +when set, the buffer has wrapped and data is read starting from the +cursor position. +.It Pa /dev/cbmem +A character device providing +.Xr ioctl 2 +access to individual CBMEM entries. +CBMEM is coreboot's mechanism for passing data between firmware stages +and to the operating system. +.El +.Ss Verbose Output +By default, the driver prints a single attach summary line showing table +location and size. +.Pp +When +.Va bootverbose +is set +.Pq e.g., Dq Li boot -v , +the driver additionally prints firmware identity and parsed hardware details +such as mainboard, board config, MAC count, ACPI RSDP, SPI flash, framebuffer, +GPIO, and TPM summary. +.Pp +When +.Va hw.coreboot.debug +is non-zero, the driver prints extra internal diagnostics such as SMMSTORE, +timestamp, TPM CB log, and FMAP addresses. +.Pp +All sysctl nodes are registered regardless of verbosity settings. +.Ss sysctl Variables +The following +.Xr sysctl 8 +variables are available when the driver is loaded +(variables only appear if the corresponding table record exists): +.Bl -tag -width "hw.coreboot.smmstore.com_buffer" +.It Va hw.coreboot.debug +Enable verbose coreboot diagnostics (read-write, default 0). +Tunable via +.Xr loader.conf 5 +as +.Va hw.coreboot.debug . +.It Va hw.coreboot.version +Firmware version string. +.It Va hw.coreboot.build +Build date and time. +.It Va hw.coreboot.compile_time +Firmware compile time string. +.It Va hw.coreboot.compiler +Compiler identification string. +.It Va hw.coreboot.extra_version +Extra version information. +.It Va hw.coreboot.platform_blob_version +Platform blob version string. +.It Va hw.coreboot.serialno +Board serial number. +.It Va hw.coreboot.version_timestamp +Firmware version timestamp. +.It Va hw.coreboot.table_addr +Physical address of the coreboot table. +.It Va hw.coreboot.table_size +Total size of the coreboot table in bytes. +.It Va hw.coreboot.mainboard.vendor +Mainboard vendor name. +.It Va hw.coreboot.mainboard.part +Mainboard part number. +.It Va hw.coreboot.serial.baseaddr +Serial port base address. +.It Va hw.coreboot.serial.baud +Serial port baud rate. +.It Va hw.coreboot.serial.regwidth +Serial port register width. +.It Va hw.coreboot.tsc_freq_khz +TSC frequency in kilohertz as reported by firmware. +.It Va hw.coreboot.pcie_ctrl_base +PCIe controller base address. +.It Va hw.coreboot.acpi_rsdp +ACPI RSDP physical address. +.It Va hw.coreboot.board.fw_config +Firmware configuration bitmask. +.It Va hw.coreboot.board.board_id +Board ID. +.It Va hw.coreboot.board.ram_code +RAM code. +.It Va hw.coreboot.board.sku_id +SKU ID. +.It Va hw.coreboot.mac.N +Factory MAC address N (formatted as xx:xx:xx:xx:xx:xx). +.It Va hw.coreboot.boot_media.fmap_offset +FMAP offset from boot media start. +.It Va hw.coreboot.boot_media.cbfs_offset +CBFS offset from boot media start. +.It Va hw.coreboot.boot_media.cbfs_size +CBFS size in bytes. +.It Va hw.coreboot.boot_media.size +Boot media size in bytes. +.It Va hw.coreboot.mmc_early_cmd1_status +Early eMMC/MMC CMD1 status value. +.It Va hw.coreboot.spi_flash.size +SPI flash size in bytes. +.It Va hw.coreboot.spi_flash.sector_size +SPI flash sector size in bytes. +.It Va hw.coreboot.spi_flash.erase_cmd +SPI flash erase command byte. +.It Va hw.coreboot.console_type +Firmware console type (0=serial8250, 1=VGA, 2=BTEXT, 3=LOGBUF, 4=SROM, +5=EHCI, 6=serial8250mem). +.It Va hw.coreboot.framebuffer.addr +Framebuffer physical address. +.It Va hw.coreboot.framebuffer.x_res +Framebuffer horizontal resolution. +.It Va hw.coreboot.framebuffer.y_res +Framebuffer vertical resolution. +.It Va hw.coreboot.framebuffer.bpp +Framebuffer bits per pixel. +.It Va hw.coreboot.gpio.N.name +GPIO pin N name. +.It Va hw.coreboot.gpio.N.port +GPIO pin N port number. +.It Va hw.coreboot.gpio.N.value +GPIO pin N value. +.It Va hw.coreboot.gpio.N.polarity +GPIO pin N polarity. +.It Va hw.coreboot.tpm.version +TPM version (1=TPM 1.2, 2=TPM 2.0). +.It Va hw.coreboot.tpm.ppi_addr +TPM Physical Presence Interface address. +.It Va hw.coreboot.tpm.cblog_addr +TPM event log physical address. +.It Va hw.coreboot.smmstore.num_blocks +SMMSTORE v2 number of blocks. +.It Va hw.coreboot.smmstore.block_size +SMMSTORE v2 block size in bytes. +.It Va hw.coreboot.smmstore.mmap_addr +SMMSTORE v2 memory-mapped address. +.It Va hw.coreboot.smmstore.com_buffer +SMMSTORE v2 communication buffer address. +.It Va hw.coreboot.smmstore.apm_cmd +SMMSTORE v2 APM command byte. +.It Va hw.coreboot.cbmem_refs.acpi_gnvs +ACPI GNVS CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.acpi_cnvs +ACPI CNVS CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.vpd +VPD CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.wifi_calibration +WiFi calibration CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.fmap +FMAP CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.vboot_workbuf +Vboot work buffer CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.type_c_info +Type-C info CBMEM physical address. +.It Va hw.coreboot.cbmem_refs.root_bridge_info +Root bridge info CBMEM physical address. +.It Va hw.coreboot.timestamps_addr +Timestamps CBMEM physical address. +.It Va hw.coreboot.timestamps +Formatted boot stage timing table (read-only). +Each line shows the timestamp ID, stage name, absolute time in +microseconds, and delta from the previous stage. +.It Va hw.coreboot.cbmem.N.id +CBMEM entry ID. +.It Va hw.coreboot.cbmem.N.name +Human-readable CBMEM entry name. +.It Va hw.coreboot.cbmem.N.address +Physical address of the CBMEM entry. +.It Va hw.coreboot.cbmem.N.size +Size of the CBMEM entry in bytes. +.El +.Ss ioctl Interface +The +.Pa /dev/cbmem +device supports the following +.Xr ioctl 2 +commands, defined in +.In dev/coreboot/corebootio.h : +.Bl -tag -width CBMEM_IOC_LIST +.It Dv CBMEM_IOC_LIST +Returns a +.Vt struct cbmem_list +containing the count and metadata of all discovered CBMEM entries. +.It Dv CBMEM_IOC_READ +Reads data from a CBMEM entry identified by its ID. +Takes a +.Vt struct cbmem_read_req +specifying the entry ID, offset, size, and a userspace buffer. +.El +.Sh FILES +.Bl -tag -width "/dev/coreboot_console" +.It Pa /dev/coreboot_console +Firmware console ring buffer (read-only). +.It Pa /dev/cbmem +CBMEM entry access device. +.El +.Sh EXAMPLES +Display the coreboot firmware version: +.Pp +.Dl "sysctl hw.coreboot.version" +.Pp +Read the firmware boot log: +.Pp +.Dl "cat /dev/coreboot_console" +.Pp +List all CBMEM entries: +.Pp +.Dl "sysctl hw.coreboot.cbmem" +.Pp +Show board identification: +.Pp +.Dl "sysctl hw.coreboot.board" +.Pp +Show factory MAC addresses: +.Pp +.Dl "sysctl hw.coreboot.mac" +.Pp +Display boot stage timestamps: +.Pp +.Dl "sysctl hw.coreboot.timestamps" +.Pp +Enable debug output and reload the module (the sysctl value resets on +unload, so it must be set again or persisted via +.Xr loader.conf 5 ) : +.Bd -literal -offset indent +kldunload coreboot +kldload coreboot +sysctl hw.coreboot.debug=1 +dmesg | grep coreboot +.Ed +.Pp +To persist debug across reboots, add to +.Xr loader.conf 5 : +.Bd -literal -offset indent +hw.coreboot.debug=1 +.Ed +.Sh COMPATIBILITY +The +.Nm +driver works with any x86 system running coreboot firmware. +The coreboot table format uses a stable ABI with forward-compatible +tagged records; unknown tags are safely skipped. +.Pp +Linux provides similar functionality through a custom bus type with +separate modules for each table record type, exposed via sysfs. +The +.Fx +driver uses a single module with native +.Xr sysctl 8 +and +.Xr ioctl 2 +interfaces instead. +.Sh SEE ALSO +.Xr ioctl 2 , +.Xr smbios 4 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 16.0 . +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt freebsd@seuros.com . +.Sh CAVEATS +The driver discovers the coreboot table by scanning physical memory +addresses 0x0 through 0x1000 for the +.Dq LBIO +signature. +On systems without coreboot firmware, the driver will silently +not attach. +.Pp +The +.Pa /dev/coreboot_console +content is static after boot; coreboot does not write to the +console buffer after handing off control to the payload. diff --git a/sys/conf/files b/sys/conf/files index 567e024259b0..57da06f27883 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1358,6 +1358,10 @@ dev/cfi/cfi_core.c optional cfi dev/cfi/cfi_dev.c optional cfi dev/cfi/cfi_disk.c optional cfid dev/chromebook_platform/chromebook_platform.c optional chromebook_platform +dev/coreboot/coreboot.c optional coreboot +dev/coreboot/coreboot_cbmem.c optional coreboot +dev/coreboot/coreboot_console.c optional coreboot +dev/coreboot/coreboot_timestamps.c optional coreboot dev/ciss/ciss.c optional ciss dev/clk/clk.c optional clk dev/clk/clkdev_if.m optional clk diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 index d700041e01d0..8c02edbb6a35 100644 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -83,6 +83,10 @@ dev/bxe/bxe_elink.c optional bxe pci dev/bxe/57710_init_values.c optional bxe pci dev/bxe/57711_init_values.c optional bxe pci dev/bxe/57712_init_values.c optional bxe pci +dev/coreboot/coreboot.c optional coreboot +dev/coreboot/coreboot_console.c optional coreboot +dev/coreboot/coreboot_cbmem.c optional coreboot +dev/coreboot/coreboot_timestamps.c optional coreboot dev/coretemp/coretemp.c optional coretemp dev/cpuctl/cpuctl.c optional cpuctl dev/dpms/dpms.c optional dpms diff --git a/sys/dev/coreboot/coreboot.c b/sys/dev/coreboot/coreboot.c new file mode 100644 index 000000000000..66b49209e3af --- /dev/null +++ b/sys/dev/coreboot/coreboot.c @@ -0,0 +1,1502 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * coreboot(4) - FreeBSD driver for coreboot firmware tables + * + * Discovers the coreboot table by scanning low memory for the "LBIO" + * signature, follows CB_TAG_FORWARD to the high-memory table, and + * exposes firmware information through sysctl(9) and character devices. + */ + +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/sysctl.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/coreboot/coreboot.h> + +static struct coreboot_softc *coreboot_sc; + +/* + * Debug verbosity control, non-zero enables extra output. + * Tunable via loader.conf: hw.coreboot.debug=1 + * Runtime: sysctl hw.coreboot.debug=1 + * Registered dynamically under hw.coreboot in + * coreboot_register_sysctls(). + */ +static int coreboot_debug = 0; +TUNABLE_INT("hw.coreboot.debug", &coreboot_debug); + +struct coreboot_softc * +coreboot_get_softc(void) +{ + return (coreboot_sc); +} + +static void coreboot_identify(driver_t *, device_t); +static int coreboot_probe(device_t); +static int coreboot_attach(device_t); +static int coreboot_detach(device_t); +static int coreboot_modevent(module_t, int, void *); + +/* + * Scan a physical memory region for the "LBIO" signature. + * Returns the physical address of the header, or 0 if not found. + */ +static vm_paddr_t +coreboot_scan_region(vm_paddr_t start, vm_paddr_t end) +{ + vm_paddr_t addr; + void *va; + struct cb_header *hdr; + + for (addr = (start == 0 ? CB_SCAN_LOW_STEP : start); addr < end; + addr += CB_SCAN_LOW_STEP) { + va = pmap_mapbios(addr, sizeof(struct cb_header)); + if (va == NULL) + continue; + + hdr = (struct cb_header *)va; + if (memcmp(hdr->signature, CB_HEADER_SIGNATURE, + CB_HEADER_SIG_LEN) == 0) { + pmap_unmapbios(va, sizeof(struct cb_header)); + return (addr); + } + pmap_unmapbios(va, sizeof(struct cb_header)); + } + return (0); +} + +/* + * Validate length fields in the header before using them for mappings + * and pointer arithmetic. + */ +static int +coreboot_sanitize_header(const struct cb_header *hdr, vm_size_t *map_size) +{ + uint64_t total; + + if (hdr->header_bytes < sizeof(*hdr) || + hdr->header_bytes > CB_MAX_HEADER_BYTES) + return (EINVAL); + if ((hdr->header_bytes % CB_TABLE_ALIGN) != 0) + return (EINVAL); + if (hdr->table_bytes > CB_MAX_TABLE_BYTES) + return (EINVAL); + if ((hdr->table_bytes % CB_TABLE_ALIGN) != 0) + return (EINVAL); + + total = (uint64_t)hdr->header_bytes + (uint64_t)hdr->table_bytes; + if (total > CB_MAX_TABLE_MAP_BYTES) + return (EINVAL); + + *map_size = (vm_size_t)total; + return (0); +} + +/* + * Validate the coreboot header checksum. + * Returns 0 on success, non-zero on failure. + */ +static int +coreboot_validate_header(struct cb_header *hdr, vm_size_t mapped_len) +{ + uint16_t cksum; + + if (hdr->header_bytes > mapped_len) + return (EINVAL); + + cksum = cb_checksum(hdr, hdr->header_bytes); + if (cksum != 0) + return (EINVAL); + + return (0); +} + +/* + * Validate checksum for the table payload. + */ +static int +coreboot_validate_table(struct cb_header *hdr, vm_size_t mapped_len) +{ + const uint8_t *table; + uint16_t cksum; + + if (hdr->table_bytes == 0) + return (0); + + if ((uint64_t)hdr->header_bytes + (uint64_t)hdr->table_bytes > + mapped_len) + return (EINVAL); + if (hdr->table_checksum > UINT16_MAX) + return (EINVAL); + + table = (const uint8_t *)hdr + hdr->header_bytes; + cksum = cb_checksum(table, hdr->table_bytes); + if (cksum != (uint16_t)hdr->table_checksum) + return (EINVAL); + + return (0); +} + +static void +coreboot_copy_bounded_string(const char *src, size_t maxlen, char *dst, + size_t dstlen) +{ + size_t slen; + + if (dstlen == 0) + return; + + slen = strnlen(src, maxlen); + if (slen >= dstlen) + slen = dstlen - 1; + memcpy(dst, src, slen); + dst[slen] = '\0'; +} + +/* + * Copy a coreboot string record into a destination buffer. + */ +static void +coreboot_copy_string(const struct cb_string *rec, char *dst, size_t dstlen) +{ + size_t slen; + + slen = rec->size - sizeof(struct cb_record); + if (slen >= dstlen) + slen = dstlen - 1; + memcpy(dst, rec->string, slen); + dst[slen] = '\0'; + + /* Strip trailing whitespace/nulls */ + while (slen > 0 && (dst[slen - 1] == '\0' || dst[slen - 1] == ' ' || + dst[slen - 1] == '\n')) + dst[--slen] = '\0'; +} + +/* + * Extract mainboard vendor and part number from the strings field. + */ +static void +coreboot_parse_mainboard(struct coreboot_softc *sc, + const struct cb_mainboard *mb) +{ + const char *strings = (const char *)mb->strings; + size_t total = mb->size - offsetof(struct cb_mainboard, strings); + uint8_t vendor_off, part_off; + + vendor_off = mb->vendor_idx; + part_off = mb->part_idx; + + if (vendor_off < total) + coreboot_copy_bounded_string(strings + vendor_off, + total - vendor_off, sc->mb_vendor, sizeof(sc->mb_vendor)); + if (part_off < total) + coreboot_copy_bounded_string(strings + part_off, + total - part_off, sc->mb_part, sizeof(sc->mb_part)); +} + +/* + * Parse all records in the coreboot table and populate softc. + */ +static void +coreboot_parse_table(struct coreboot_softc *sc, struct cb_header *hdr) +{ + uint8_t *entry; + uint8_t *table_end; + struct cb_record *rec; + + entry = (uint8_t *)hdr + hdr->header_bytes; + table_end = entry + hdr->table_bytes; + + while ((size_t)(table_end - entry) >= sizeof(struct cb_record)) { + size_t rec_size; + + rec = (struct cb_record *)entry; + rec_size = rec->size; + + if (rec_size < sizeof(struct cb_record)) + break; + if (rec_size > (size_t)(table_end - entry)) + break; + + switch (rec->tag) { + case CB_TAG_VERSION: + coreboot_copy_string((struct cb_string *)rec, + sc->version, sizeof(sc->version)); + break; + + case CB_TAG_EXTRA_VERSION: + coreboot_copy_string((struct cb_string *)rec, + sc->extra_version, sizeof(sc->extra_version)); + break; + + case CB_TAG_BUILD: + coreboot_copy_string((struct cb_string *)rec, + sc->build, sizeof(sc->build)); + break; + + case CB_TAG_COMPILE_TIME: + coreboot_copy_string((struct cb_string *)rec, + sc->compile_time, sizeof(sc->compile_time)); + break; + + case CB_TAG_COMPILER: + coreboot_copy_string((struct cb_string *)rec, + sc->compiler, sizeof(sc->compiler)); + break; + + case CB_TAG_PLATFORM_BLOB_VERSION: + coreboot_copy_string((struct cb_string *)rec, + sc->platform_blob_version, + sizeof(sc->platform_blob_version)); + break; + + case CB_TAG_SERIALNO: + coreboot_copy_string((struct cb_string *)rec, + sc->serialno, sizeof(sc->serialno)); + break; + + case CB_TAG_VERSION_TIMESTAMP: { + struct cb_version_timestamp *ts = + (struct cb_version_timestamp *)rec; + + if (rec_size < sizeof(*ts)) + break; + sc->version_timestamp = ts->timestamp; + sc->has_version_timestamp = 1; + break; + } + + case CB_TAG_MAINBOARD: + if (rec_size < offsetof(struct cb_mainboard, strings)) + break; + coreboot_parse_mainboard(sc, + (struct cb_mainboard *)rec); + break; + + case CB_TAG_SERIAL: { + struct cb_serial *ser = (struct cb_serial *)rec; + + if (rec_size < sizeof(*ser)) + break; + sc->serial_baseaddr = ser->baseaddr; + sc->serial_baud = ser->baud; + sc->serial_regwidth = ser->regwidth; + sc->has_serial = 1; + break; + } + + case CB_TAG_TSC_INFO: { + struct cb_tsc_info *tsc = (struct cb_tsc_info *)rec; + + if (rec_size < sizeof(*tsc)) + break; + sc->tsc_freq_khz = tsc->freq_khz; + sc->has_tsc_info = 1; + break; + } + + case CB_TAG_PCIE: { + struct cb_pcie *pcie = (struct cb_pcie *)rec; + + if (rec_size < sizeof(*pcie)) + break; + sc->pcie_ctrl_base = pcie->ctrl_base; + sc->has_pcie = 1; + break; + } + + case CB_TAG_BOOT_MEDIA_PARAMS: { + struct cb_boot_media_params *bmp = + (struct cb_boot_media_params *)rec; + + if (rec_size < sizeof(*bmp)) + break; + sc->fmap_offset = bmp->fmap_offset; + sc->cbfs_offset = bmp->cbfs_offset; + sc->cbfs_size = bmp->cbfs_size; + sc->boot_media_size = bmp->boot_media_size; + sc->has_boot_media = 1; + break; + } + + case CB_TAG_MMC_INFO: { + struct cb_mmc_info *mmc = (struct cb_mmc_info *)rec; + + if (rec_size < sizeof(*mmc)) + break; + sc->mmc_early_cmd1_status = mmc->early_cmd1_status; + sc->has_mmc_info = 1; + break; + } + + case CB_TAG_CBMEM_CONSOLE: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->console_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_console = 1; + break; + } + + case CB_TAG_CBMEM_ENTRY: { + struct cb_cbmem_entry *ent = + (struct cb_cbmem_entry *)rec; + + if (rec_size < sizeof(*ent)) + break; + if (sc->cbmem_count < CB_MAX_CBMEM_ENTRIES) { + struct cbmem_entry_info *info = + &sc->cbmem_entries[sc->cbmem_count]; + info->id = ent->id; + info->address = ent->address; + info->size = ent->entry_size; + strlcpy(info->name, cbmem_id_to_name(ent->id), + sizeof(info->name)); + sc->cbmem_count++; + } + break; + } + + case CB_TAG_BOARD_CONFIG: { + struct cb_board_config *bc = + (struct cb_board_config *)rec; + + if (rec_size < sizeof(*bc)) + break; + sc->fw_config = bc->fw_config; + sc->board_id = bc->board_id; + sc->ram_code = bc->ram_code; + sc->sku_id = bc->sku_id; + sc->has_board_config = 1; + break; + } + + case CB_TAG_MAC_ADDRS: { + struct cb_macs *macs = (struct cb_macs *)rec; + uint32_t i, count; + + if (rec_size < sizeof(*macs)) + break; + count = macs->count; + if (count > CB_MAX_MAC_ADDRS) + count = CB_MAX_MAC_ADDRS; + if (rec_size < sizeof(*macs) + + count * sizeof(struct cb_mac_address)) + break; + for (i = 0; i < count; i++) + sc->macs[i] = macs->entries[i]; + sc->mac_count = count; + break; + } + + case CB_TAG_ACPI_RSDP: { + struct cb_acpi_rsdp *rsdp = + (struct cb_acpi_rsdp *)rec; + + if (rec_size < sizeof(*rsdp)) + break; + sc->acpi_rsdp = rsdp->rsdp_pointer; + sc->has_acpi_rsdp = 1; + break; + } + + case CB_TAG_SPI_FLASH: { + struct cb_spi_flash *spi = + (struct cb_spi_flash *)rec; + + if (rec_size < sizeof(*spi)) + break; + sc->spi_flash_size = spi->flash_size; + sc->spi_sector_size = spi->sector_size; + sc->spi_erase_cmd = spi->erase_cmd; + sc->spi_flags = spi->flags; + sc->has_spi_flash = 1; + break; + } + + case CB_TAG_CONSOLE: { + struct cb_console *con = (struct cb_console *)rec; + + if (rec_size < sizeof(*con)) + break; + sc->console_type = con->type; + sc->has_console_type = 1; + break; + } + + case CB_TAG_FRAMEBUFFER: { + struct cb_framebuffer *fb = + (struct cb_framebuffer *)rec; + + if (rec_size < CB_FRAMEBUFFER_MIN_SIZE) + break; + sc->fb_addr = fb->physical_address; + sc->fb_x_res = fb->x_resolution; + sc->fb_y_res = fb->y_resolution; + sc->fb_stride = fb->bytes_per_line; + sc->fb_bpp = fb->bits_per_pixel; + sc->has_framebuffer = 1; + break; + } + + case CB_TAG_GPIO: { + struct cb_gpios *gpios = (struct cb_gpios *)rec; + uint32_t i, count; + + if (rec_size < sizeof(*gpios)) + break; + count = gpios->count; + if (count > CB_MAX_GPIOS) + count = CB_MAX_GPIOS; + if (rec_size < sizeof(*gpios) + + count * sizeof(struct cb_gpio)) + break; + for (i = 0; i < count; i++) + sc->gpios[i] = gpios->entries[i]; + sc->gpio_count = count; + break; + } + + case CB_TAG_TPM_PPI_HANDOFF: { + struct cb_tpm_ppi *tpm = (struct cb_tpm_ppi *)rec; + + if (rec_size < sizeof(*tpm)) + break; + sc->tpm_ppi_addr = tpm->ppi_address; + sc->tpm_version = tpm->tpm_version; + sc->has_tpm = 1; + break; + } + + case CB_TAG_TIMESTAMPS: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->timestamps_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_timestamps = 1; + break; + } + + case CB_TAG_ACPI_GNVS: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->acpi_gnvs_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_acpi_gnvs = 1; + break; + } + + case CB_TAG_ACPI_CNVS: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->acpi_cnvs_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_acpi_cnvs = 1; + break; + } + + case CB_TAG_VPD: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->vpd_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_vpd = 1; + break; + } + + case CB_TAG_WIFI_CALIBRATION: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->wifi_cal_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_wifi_cal = 1; + break; + } + + case CB_TAG_FMAP: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->fmap_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_fmap = 1; + break; + } + + case CB_TAG_VBOOT_WORKBUF: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->vboot_workbuf_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_vboot_workbuf = 1; + break; + } + + case CB_TAG_TYPE_C_INFO: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->type_c_info_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_type_c_info = 1; + break; + } + + case CB_TAG_ROOT_BRIDGE_INFO: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->root_bridge_info_paddr = + (vm_paddr_t)ref->cbmem_addr; + sc->has_root_bridge_info = 1; + break; + } + + case CB_TAG_TPM_CB_LOG: { + struct cb_cbmem_ref *ref = (struct cb_cbmem_ref *)rec; + + if (rec_size < sizeof(*ref)) + break; + sc->tpm_log_paddr = (vm_paddr_t)ref->cbmem_addr; + sc->has_tpm_log = 1; + break; + } + + case CB_TAG_SMMSTOREV2: { + struct cb_smmstorev2 *smm = + (struct cb_smmstorev2 *)rec; + + if (rec_size < CB_SMMSTOREV2_BASE_SIZE) + break; + sc->smmstore_num_blocks = smm->num_blocks; + sc->smmstore_block_size = smm->block_size; + sc->smmstore_com_buffer = smm->com_buffer; + sc->smmstore_apm_cmd = smm->apm_cmd; + /* 64-bit mmap_addr only present in newer coreboot */ + if (rec_size >= sizeof(*smm)) + sc->smmstore_mmap_addr = smm->mmap_addr; + else + sc->smmstore_mmap_addr = + (uint64_t)smm->mmap_addr_lo; + sc->has_smmstore = 1; + break; + } + + default: + break; + } + + entry += rec_size; + } +} + +/* + * Table-driven sysctl registration. + * + * Each leaf descriptor specifies the parent node, name, type, data offset + * into softc, a guard flag offset (or -1 for unconditional), and flags. + * Nodes that group related leaves are indexed by cb_sysctl_node. + */ + +/* Node indices for parent selection */ +enum cb_sysctl_node { + CB_NODE_ROOT = 0, + CB_NODE_MAINBOARD, + CB_NODE_SERIAL, + CB_NODE_BOARD, + CB_NODE_BOOT_MEDIA, + CB_NODE_SPI_FLASH, + CB_NODE_FRAMEBUFFER, + CB_NODE_TPM, + CB_NODE_SMMSTORE, + CB_NODE_CBMEM_REFS, + CB_NODE_COUNT +}; + +/* Sysctl value type discriminator */ +enum cb_sysctl_type { + CB_SYSCTL_U8, + CB_SYSCTL_U16, + CB_SYSCTL_U32, + CB_SYSCTL_S32, + CB_SYSCTL_U64, + CB_SYSCTL_ULONG, + CB_SYSCTL_STRING, +}; + +/* + * Guard mode: how to decide whether a leaf should be registered. + * STR_NONEMPTY: check that the char[] at guard_off is non-empty + * FLAG_SET: check that the int at guard_off is non-zero + * ALWAYS: unconditional (guard_off ignored) + */ +enum cb_sysctl_guard { + CB_GUARD_ALWAYS, + CB_GUARD_FLAG_SET, + CB_GUARD_STR_NONEMPTY, +}; + +struct cb_sysctl_node_desc { + enum cb_sysctl_node id; + enum cb_sysctl_node parent; + const char *name; + const char *desc; +}; + +struct cb_sysctl_leaf { + enum cb_sysctl_node parent; + const char *name; + enum cb_sysctl_type type; + size_t data_off; + enum cb_sysctl_guard guard; + size_t guard_off; + int flags; + const char *desc; +}; + +/* Helper macros for field offset within coreboot_softc */ +#define SC_OFF(field) offsetof(struct coreboot_softc, field) + +static const struct cb_sysctl_node_desc cb_nodes[] = { + { CB_NODE_MAINBOARD, CB_NODE_ROOT, "mainboard", + "Mainboard information" }, + { CB_NODE_SERIAL, CB_NODE_ROOT, "serial", + "Serial port" }, + { CB_NODE_BOARD, CB_NODE_ROOT, "board", + "Board identification" }, + { CB_NODE_BOOT_MEDIA, CB_NODE_ROOT, "boot_media", + "Boot media parameters" }, + { CB_NODE_SPI_FLASH, CB_NODE_ROOT, "spi_flash", + "SPI flash parameters" }, + { CB_NODE_FRAMEBUFFER, CB_NODE_ROOT, "framebuffer", + "Framebuffer information" }, + { CB_NODE_TPM, CB_NODE_ROOT, "tpm", + "TPM information" }, + { CB_NODE_SMMSTORE, CB_NODE_ROOT, "smmstore", + "SMMSTORE v2 configuration" }, + { CB_NODE_CBMEM_REFS, CB_NODE_ROOT, "cbmem_refs", + "Additional CBMEM reference addresses" }, +}; + +static const struct cb_sysctl_leaf cb_leaves[] = { + /* Root-level strings (guarded by non-empty string) */ + { CB_NODE_ROOT, "version", CB_SYSCTL_STRING, + SC_OFF(version), CB_GUARD_STR_NONEMPTY, SC_OFF(version), + CTLFLAG_RD, "Firmware version" }, + { CB_NODE_ROOT, "build", CB_SYSCTL_STRING, + SC_OFF(build), CB_GUARD_STR_NONEMPTY, SC_OFF(build), + CTLFLAG_RD, "Build date" }, + { CB_NODE_ROOT, "compile_time", CB_SYSCTL_STRING, + SC_OFF(compile_time), CB_GUARD_STR_NONEMPTY, SC_OFF(compile_time), + CTLFLAG_RD, "Firmware compile time" }, + { CB_NODE_ROOT, "compiler", CB_SYSCTL_STRING, + SC_OFF(compiler), CB_GUARD_STR_NONEMPTY, SC_OFF(compiler), + CTLFLAG_RD, "Compiler info" }, + { CB_NODE_ROOT, "extra_version", CB_SYSCTL_STRING, + SC_OFF(extra_version), CB_GUARD_STR_NONEMPTY, SC_OFF(extra_version), + CTLFLAG_RD, "Extra version info" }, + { CB_NODE_ROOT, "serialno", CB_SYSCTL_STRING, + SC_OFF(serialno), CB_GUARD_STR_NONEMPTY, SC_OFF(serialno), + CTLFLAG_RD, "Serial number" }, + { CB_NODE_ROOT, "platform_blob_version", CB_SYSCTL_STRING, + SC_OFF(platform_blob_version), CB_GUARD_STR_NONEMPTY, + SC_OFF(platform_blob_version), + CTLFLAG_RD, "Platform blob version" }, + + /* Root-level scalars */ + { CB_NODE_ROOT, "version_timestamp", CB_SYSCTL_U32, + SC_OFF(version_timestamp), CB_GUARD_FLAG_SET, + SC_OFF(has_version_timestamp), + CTLFLAG_RD, "Firmware version timestamp" }, + { CB_NODE_ROOT, "table_addr", CB_SYSCTL_U64, + SC_OFF(table_paddr), CB_GUARD_ALWAYS, 0, + CTLFLAG_RD, "Physical address of coreboot table" }, + { CB_NODE_ROOT, "table_size", CB_SYSCTL_ULONG, + SC_OFF(table_size), CB_GUARD_ALWAYS, 0, + CTLFLAG_RD, "Total coreboot table size" }, + { CB_NODE_ROOT, "tsc_freq_khz", CB_SYSCTL_U32, + SC_OFF(tsc_freq_khz), CB_GUARD_FLAG_SET, SC_OFF(has_tsc_info), + CTLFLAG_RD, "TSC frequency in kHz" }, + { CB_NODE_ROOT, "pcie_ctrl_base", CB_SYSCTL_U64, + SC_OFF(pcie_ctrl_base), CB_GUARD_FLAG_SET, SC_OFF(has_pcie), + CTLFLAG_RD, "PCIe controller base address" }, + { CB_NODE_ROOT, "acpi_rsdp", CB_SYSCTL_U64, + SC_OFF(acpi_rsdp), CB_GUARD_FLAG_SET, SC_OFF(has_acpi_rsdp), + CTLFLAG_RD, "ACPI RSDP physical address" }, + { CB_NODE_ROOT, "mmc_early_cmd1_status", CB_SYSCTL_S32, + SC_OFF(mmc_early_cmd1_status), CB_GUARD_FLAG_SET, + SC_OFF(has_mmc_info), + CTLFLAG_RD, "Early eMMC CMD1 status" }, + { CB_NODE_ROOT, "console_type", CB_SYSCTL_U16, + SC_OFF(console_type), CB_GUARD_FLAG_SET, SC_OFF(has_console_type), + CTLFLAG_RD, "Firmware console type" }, + { CB_NODE_ROOT, "timestamps_addr", CB_SYSCTL_U64, + SC_OFF(timestamps_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_timestamps), + CTLFLAG_RD, "Timestamps CBMEM physical address" }, + + /* Mainboard children */ + { CB_NODE_MAINBOARD, "vendor", CB_SYSCTL_STRING, + SC_OFF(mb_vendor), CB_GUARD_STR_NONEMPTY, SC_OFF(mb_vendor), + CTLFLAG_RD, "Board vendor" }, + { CB_NODE_MAINBOARD, "part", CB_SYSCTL_STRING, + SC_OFF(mb_part), CB_GUARD_STR_NONEMPTY, SC_OFF(mb_part), + CTLFLAG_RD, "Board part number" }, + + /* Serial children */ + { CB_NODE_SERIAL, "baseaddr", CB_SYSCTL_U32, + SC_OFF(serial_baseaddr), CB_GUARD_FLAG_SET, SC_OFF(has_serial), + CTLFLAG_RD, "Base address" }, + { CB_NODE_SERIAL, "baud", CB_SYSCTL_U32, + SC_OFF(serial_baud), CB_GUARD_FLAG_SET, SC_OFF(has_serial), + CTLFLAG_RD, "Baud rate" }, + { CB_NODE_SERIAL, "regwidth", CB_SYSCTL_U32, + SC_OFF(serial_regwidth), CB_GUARD_FLAG_SET, SC_OFF(has_serial), + CTLFLAG_RD, "Register width" }, + + /* Board config children */ + { CB_NODE_BOARD, "fw_config", CB_SYSCTL_U64, + SC_OFF(fw_config), CB_GUARD_FLAG_SET, SC_OFF(has_board_config), + CTLFLAG_RD, "Firmware configuration bitmask" }, + { CB_NODE_BOARD, "board_id", CB_SYSCTL_U32, + SC_OFF(board_id), CB_GUARD_FLAG_SET, SC_OFF(has_board_config), + CTLFLAG_RD, "Board ID" }, + { CB_NODE_BOARD, "ram_code", CB_SYSCTL_U32, + SC_OFF(ram_code), CB_GUARD_FLAG_SET, SC_OFF(has_board_config), + CTLFLAG_RD, "RAM code" }, + { CB_NODE_BOARD, "sku_id", CB_SYSCTL_U32, + SC_OFF(sku_id), CB_GUARD_FLAG_SET, SC_OFF(has_board_config), + CTLFLAG_RD, "SKU ID" }, + + /* Boot media children */ + { CB_NODE_BOOT_MEDIA, "fmap_offset", CB_SYSCTL_U64, + SC_OFF(fmap_offset), CB_GUARD_FLAG_SET, SC_OFF(has_boot_media), + CTLFLAG_RD, "FMAP offset from boot media start" }, + { CB_NODE_BOOT_MEDIA, "cbfs_offset", CB_SYSCTL_U64, + SC_OFF(cbfs_offset), CB_GUARD_FLAG_SET, SC_OFF(has_boot_media), + CTLFLAG_RD, "CBFS offset from boot media start" }, + { CB_NODE_BOOT_MEDIA, "cbfs_size", CB_SYSCTL_U64, + SC_OFF(cbfs_size), CB_GUARD_FLAG_SET, SC_OFF(has_boot_media), + CTLFLAG_RD, "CBFS size in bytes" }, + { CB_NODE_BOOT_MEDIA, "size", CB_SYSCTL_U64, + SC_OFF(boot_media_size), CB_GUARD_FLAG_SET, SC_OFF(has_boot_media), + CTLFLAG_RD, "Boot media size in bytes" }, + + /* SPI flash children */ + { CB_NODE_SPI_FLASH, "size", CB_SYSCTL_U32, + SC_OFF(spi_flash_size), CB_GUARD_FLAG_SET, SC_OFF(has_spi_flash), + CTLFLAG_RD, "Flash size in bytes" }, + { CB_NODE_SPI_FLASH, "sector_size", CB_SYSCTL_U32, + SC_OFF(spi_sector_size), CB_GUARD_FLAG_SET, SC_OFF(has_spi_flash), + CTLFLAG_RD, "Sector size in bytes" }, + { CB_NODE_SPI_FLASH, "erase_cmd", CB_SYSCTL_U8, + SC_OFF(spi_erase_cmd), CB_GUARD_FLAG_SET, SC_OFF(has_spi_flash), + CTLFLAG_RD, "Erase command byte" }, + + /* Framebuffer children */ + { CB_NODE_FRAMEBUFFER, "addr", CB_SYSCTL_U64, + SC_OFF(fb_addr), CB_GUARD_FLAG_SET, SC_OFF(has_framebuffer), + CTLFLAG_RD, "Physical address" }, + { CB_NODE_FRAMEBUFFER, "x_res", CB_SYSCTL_U32, + SC_OFF(fb_x_res), CB_GUARD_FLAG_SET, SC_OFF(has_framebuffer), + CTLFLAG_RD, "Horizontal resolution" }, + { CB_NODE_FRAMEBUFFER, "y_res", CB_SYSCTL_U32, + SC_OFF(fb_y_res), CB_GUARD_FLAG_SET, SC_OFF(has_framebuffer), + CTLFLAG_RD, "Vertical resolution" }, + { CB_NODE_FRAMEBUFFER, "bpp", CB_SYSCTL_U8, + SC_OFF(fb_bpp), CB_GUARD_FLAG_SET, SC_OFF(has_framebuffer), + CTLFLAG_RD, "Bits per pixel" }, + + /* TPM children */ + { CB_NODE_TPM, "version", CB_SYSCTL_U8, + SC_OFF(tpm_version), CB_GUARD_FLAG_SET, SC_OFF(has_tpm), + CTLFLAG_RD, "TPM version (1=1.2, 2=2.0)" }, + { CB_NODE_TPM, "ppi_addr", CB_SYSCTL_U32, + SC_OFF(tpm_ppi_addr), CB_GUARD_FLAG_SET, SC_OFF(has_tpm), + CTLFLAG_RD, "PPI address" }, + { CB_NODE_TPM, "cblog_addr", CB_SYSCTL_U64, + SC_OFF(tpm_log_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_tpm_log), + CTLFLAG_RD, "TPM event log physical address" }, + + /* SMMSTORE children */ + { CB_NODE_SMMSTORE, "num_blocks", CB_SYSCTL_U32, + SC_OFF(smmstore_num_blocks), CB_GUARD_FLAG_SET, + SC_OFF(has_smmstore), + CTLFLAG_RD, "Number of blocks" }, + { CB_NODE_SMMSTORE, "block_size", CB_SYSCTL_U32, + SC_OFF(smmstore_block_size), CB_GUARD_FLAG_SET, + SC_OFF(has_smmstore), + CTLFLAG_RD, "Block size in bytes" }, + { CB_NODE_SMMSTORE, "mmap_addr", CB_SYSCTL_U64, + SC_OFF(smmstore_mmap_addr), CB_GUARD_FLAG_SET, + SC_OFF(has_smmstore), + CTLFLAG_RD, "Memory-mapped address" }, + { CB_NODE_SMMSTORE, "com_buffer", CB_SYSCTL_U32, + SC_OFF(smmstore_com_buffer), CB_GUARD_FLAG_SET, + SC_OFF(has_smmstore), + CTLFLAG_RD, "Communication buffer address" }, + { CB_NODE_SMMSTORE, "apm_cmd", CB_SYSCTL_U8, + SC_OFF(smmstore_apm_cmd), CB_GUARD_FLAG_SET, + SC_OFF(has_smmstore), + CTLFLAG_RD, "APM command byte" }, + + /* CBMEM reference addresses */ + { CB_NODE_CBMEM_REFS, "acpi_gnvs", CB_SYSCTL_U64, + SC_OFF(acpi_gnvs_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_acpi_gnvs), + CTLFLAG_RD, "ACPI GNVS CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "acpi_cnvs", CB_SYSCTL_U64, + SC_OFF(acpi_cnvs_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_acpi_cnvs), + CTLFLAG_RD, "ACPI CNVS CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "vpd", CB_SYSCTL_U64, + SC_OFF(vpd_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_vpd), + CTLFLAG_RD, "VPD CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "wifi_calibration", CB_SYSCTL_U64, + SC_OFF(wifi_cal_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_wifi_cal), + CTLFLAG_RD, "WiFi calibration CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "fmap", CB_SYSCTL_U64, + SC_OFF(fmap_paddr), CB_GUARD_FLAG_SET, SC_OFF(has_fmap), + CTLFLAG_RD, "FMAP CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "vboot_workbuf", CB_SYSCTL_U64, + SC_OFF(vboot_workbuf_paddr), CB_GUARD_FLAG_SET, + SC_OFF(has_vboot_workbuf), + CTLFLAG_RD, "Vboot work buffer CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "type_c_info", CB_SYSCTL_U64, + SC_OFF(type_c_info_paddr), CB_GUARD_FLAG_SET, + SC_OFF(has_type_c_info), + CTLFLAG_RD, "Type-C info CBMEM physical address" }, + { CB_NODE_CBMEM_REFS, "root_bridge_info", CB_SYSCTL_U64, + SC_OFF(root_bridge_info_paddr), CB_GUARD_FLAG_SET, + SC_OFF(has_root_bridge_info), + CTLFLAG_RD, "Root bridge info CBMEM physical address" }, +}; + +/* + * Check whether a leaf's guard condition is satisfied. + */ +static int +cb_sysctl_guard_check(const struct cb_sysctl_leaf *leaf, + const struct coreboot_softc *sc) +{ + const char *base; + + base = (const char *)sc; + switch (leaf->guard) { + case CB_GUARD_ALWAYS: + return (1); + case CB_GUARD_FLAG_SET: + return (*(const int *)(base + leaf->guard_off) != 0); + case CB_GUARD_STR_NONEMPTY: + return (*(base + leaf->guard_off) != '\0'); + } + return (0); +} + +/* + * Type-to-handler mapping for sysctl_add_oid(). + * Mirrors the SYSCTL_ADD_* macros but avoids their CTASSERT on flags. + */ +static const struct { + int ctltype; + int (*handler)(SYSCTL_HANDLER_ARGS); + const char *fmt; +} cb_sysctl_types[] = { + [CB_SYSCTL_U8] = { CTLTYPE_U8, sysctl_handle_8, "CU" }, + [CB_SYSCTL_U16] = { CTLTYPE_U16, sysctl_handle_16, "SU" }, + [CB_SYSCTL_U32] = { CTLTYPE_U32, sysctl_handle_32, "IU" }, + [CB_SYSCTL_S32] = { CTLTYPE_S32, sysctl_handle_32, "I" }, + [CB_SYSCTL_U64] = { CTLTYPE_U64, sysctl_handle_64, "QU" }, + [CB_SYSCTL_ULONG] = { CTLTYPE_ULONG, sysctl_handle_long, "LU" }, + [CB_SYSCTL_STRING] = { CTLTYPE_STRING, sysctl_handle_string, "A" }, +}; + +/* + * Add a single sysctl leaf under the given parent OID. + */ +static void +cb_sysctl_add_leaf(struct sysctl_ctx_list *ctx, struct sysctl_oid *parent, + const struct cb_sysctl_leaf *leaf, struct coreboot_softc *sc) +{ + void *ptr; + + ptr = (char *)sc + leaf->data_off; + + sysctl_add_oid(ctx, SYSCTL_CHILDREN(parent), OID_AUTO, + leaf->name, + cb_sysctl_types[leaf->type].ctltype | CTLFLAG_MPSAFE | leaf->flags, + ptr, 0, + cb_sysctl_types[leaf->type].handler, + cb_sysctl_types[leaf->type].fmt, + __DESCR(leaf->desc), NULL); +} + +/* + * Register the sysctl tree under hw.coreboot.* + * + * Static leaves and nodes are driven by the cb_leaves[] and cb_nodes[] + * tables. Dynamic entries (CBMEM, MAC, GPIO) that require loops over + * runtime-determined counts are handled explicitly below the table loop. + */ +static void +coreboot_register_sysctls(struct coreboot_softc *sc) +{ + struct sysctl_oid *nodes[CB_NODE_COUNT]; + struct sysctl_oid *oid_cbmem, *oid_entry; + struct sysctl_oid *oid_mac, *oid_gpio, *oid_pin; + char numstr[8]; + uint32_t i; + int any_cbref; + + sysctl_ctx_init(&sc->sysctl_ctx); + sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "coreboot", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "coreboot firmware information"); + + if (sc->sysctl_tree == NULL) + return; + + memset(nodes, 0, sizeof(nodes)); + nodes[CB_NODE_ROOT] = sc->sysctl_tree; + + SYSCTL_ADD_INT(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "debug", + CTLFLAG_RW, &coreboot_debug, 0, + "Enable verbose coreboot diagnostics"); + + /* + * Create intermediate nodes on demand. + * + * The mainboard node is special: it appears when either vendor or + * part is present. The TPM node appears when has_tpm or has_tpm_log + * is set. The cbmem_refs node appears when any of its children + * would be registered. All other nodes are gated by the guard + * flags on their children (a node is created the first time a child + * needs it). + */ + + /* Pre-create mainboard node if either string is populated */ + if (sc->mb_vendor[0] != '\0' || sc->mb_part[0] != '\0') + nodes[CB_NODE_MAINBOARD] = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "mainboard", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Mainboard information"); + + /* TPM node appears for has_tpm OR has_tpm_log */ + if (sc->has_tpm || sc->has_tpm_log) + nodes[CB_NODE_TPM] = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "tpm", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "TPM information"); + + /* cbmem_refs node: created if any ref address is present */ + any_cbref = sc->has_acpi_gnvs || sc->has_acpi_cnvs || sc->has_vpd || + sc->has_wifi_cal || sc->has_fmap || sc->has_vboot_workbuf || + sc->has_type_c_info || sc->has_root_bridge_info; + if (any_cbref) + nodes[CB_NODE_CBMEM_REFS] = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "cbmem_refs", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "Additional CBMEM reference addresses"); + + /* Walk the leaf table and register matching entries */ + for (i = 0; i < nitems(cb_leaves); i++) { + const struct cb_sysctl_leaf *leaf = &cb_leaves[i]; + enum cb_sysctl_node nid = leaf->parent; + + if (!cb_sysctl_guard_check(leaf, sc)) + continue; + + /* Lazily create the parent node if not yet instantiated */ + if (nodes[nid] == NULL) { + const struct cb_sysctl_node_desc *nd; + uint32_t j; + + for (j = 0; j < nitems(cb_nodes); j++) { + if (cb_nodes[j].id == nid) + break; + } + if (j >= nitems(cb_nodes)) + continue; + nd = &cb_nodes[j]; + nodes[nid] = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(nodes[nd->parent]), OID_AUTO, + nd->name, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + nd->desc); + if (nodes[nid] == NULL) + continue; + } + + cb_sysctl_add_leaf(&sc->sysctl_ctx, nodes[nid], leaf, sc); + } + + /* --- Dynamic entries that don't fit the static table --- */ + + /* CBMEM entry enumeration */ + if (sc->cbmem_count > 0) { + oid_cbmem = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "cbmem", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "CBMEM entries"); + + for (i = 0; i < sc->cbmem_count; i++) { + struct cbmem_entry_info *info = &sc->cbmem_entries[i]; + + snprintf(numstr, sizeof(numstr), "%u", i); + oid_entry = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_cbmem), OID_AUTO, numstr, + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "CBMEM entry"); + + SYSCTL_ADD_STRING(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_entry), OID_AUTO, "name", + CTLFLAG_RD, info->name, 0, "Entry name"); + + SYSCTL_ADD_U32(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_entry), OID_AUTO, "id", + CTLFLAG_RD, &info->id, 0, "Entry ID (hex)"); + + SYSCTL_ADD_U64(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_entry), OID_AUTO, "address", + CTLFLAG_RD, (uint64_t *)&info->address, 0, + "Physical address"); + + SYSCTL_ADD_U32(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_entry), OID_AUTO, "size", + CTLFLAG_RD, &info->size, 0, "Entry size"); + } + } + + /* Factory MAC addresses */ + if (sc->mac_count > 0) { + oid_mac = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "mac", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "Factory MAC addresses"); + + for (i = 0; i < sc->mac_count; i++) { + uint8_t *m = sc->macs[i].mac_addr; + + snprintf(numstr, sizeof(numstr), "%u", i); + snprintf(sc->mac_strs[i], sizeof(sc->mac_strs[i]), + "%02x:%02x:%02x:%02x:%02x:%02x", + m[0], m[1], m[2], m[3], m[4], m[5]); + + SYSCTL_ADD_STRING(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_mac), OID_AUTO, numstr, + CTLFLAG_RD, sc->mac_strs[i], 0, + "MAC address"); + } + } + + /* GPIO pins */ + if (sc->gpio_count > 0) { + oid_gpio = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "gpio", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "GPIO pin states"); + + for (i = 0; i < sc->gpio_count; i++) { + struct cb_gpio *g = &sc->gpios[i]; + + snprintf(numstr, sizeof(numstr), "%u", i); + oid_pin = SYSCTL_ADD_NODE(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_gpio), OID_AUTO, numstr, + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "GPIO pin"); + + /* Ensure name is NUL-terminated */ + g->name[sizeof(g->name) - 1] = '\0'; + SYSCTL_ADD_STRING(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_pin), OID_AUTO, "name", + CTLFLAG_RD, g->name, 0, "Pin name"); + + SYSCTL_ADD_U32(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_pin), OID_AUTO, "port", + CTLFLAG_RD, &g->port, 0, "Port number"); + + SYSCTL_ADD_U32(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_pin), OID_AUTO, "value", + CTLFLAG_RD, &g->value, 0, "Pin value"); + + SYSCTL_ADD_U32(&sc->sysctl_ctx, + SYSCTL_CHILDREN(oid_pin), OID_AUTO, "polarity", + CTLFLAG_RD, &g->polarity, 0, "Pin polarity"); + } + } + + /* Timestamp PROC sysctl */ + if (sc->has_timestamps) + coreboot_timestamps_register(sc, sc->sysctl_tree); +} + +/* + * Map and validate a coreboot table at physical address pa. + * On success, *vap points to the mapped table and *sizep is the total size. + * The caller must pmap_unmapbios(*vap, *sizep) when done. + */ +static int +coreboot_map_table(vm_paddr_t pa, void **vap, vm_size_t *sizep) +{ + struct cb_header *hdr; + void *va; + vm_size_t map_size; + int error; + + va = pmap_mapbios(pa, sizeof(struct cb_header)); + if (va == NULL) + return (ENOMEM); + + hdr = (struct cb_header *)va; + if (memcmp(hdr->signature, CB_HEADER_SIGNATURE, + CB_HEADER_SIG_LEN) != 0) { + pmap_unmapbios(va, sizeof(struct cb_header)); + return (ENXIO); + } + + error = coreboot_sanitize_header(hdr, &map_size); + pmap_unmapbios(va, sizeof(struct cb_header)); + if (error != 0) + return (ENXIO); + + va = pmap_mapbios(pa, map_size); + if (va == NULL) + return (ENOMEM); + + hdr = (struct cb_header *)va; + if (coreboot_validate_header(hdr, map_size) != 0 || + coreboot_validate_table(hdr, map_size) != 0) { + pmap_unmapbios(va, map_size); + return (ENXIO); + } + + *vap = va; + *sizep = map_size; + return (0); +} + +/* + * Identify: scan low memory for "LBIO" signature and register a child + */ +static void +coreboot_identify(driver_t *driver, device_t parent) +{ + vm_paddr_t low_addr, real_addr; + struct cb_header *hdr; + uint8_t *entry, *table_end; + struct cb_record *rec; + device_t child; + void *va; + vm_size_t map_size; + int error; + + if (!device_is_alive(parent)) + return; + + if (device_find_child(parent, "coreboot", -1) != NULL) + return; + + low_addr = coreboot_scan_region(CB_SCAN_LOW_START, CB_SCAN_LOW_END); + if (low_addr == 0) + return; + + va = pmap_mapbios(low_addr, sizeof(struct cb_header)); + if (va == NULL) + return; + + /* Scan already verified the signature; re-read to get sizes. */ + hdr = (struct cb_header *)va; + error = coreboot_sanitize_header(hdr, &map_size); + pmap_unmapbios(va, sizeof(struct cb_header)); + if (error != 0) + return; + + va = pmap_mapbios(low_addr, map_size); + if (va == NULL) + return; + + hdr = (struct cb_header *)va; + if (coreboot_validate_header(hdr, map_size) != 0 || + coreboot_validate_table(hdr, map_size) != 0) { + pmap_unmapbios(va, map_size); + return; + } + + /* Look for CB_TAG_FORWARD to find the real table in high memory */ + real_addr = low_addr; + entry = (uint8_t *)hdr + hdr->header_bytes; + table_end = entry + hdr->table_bytes; + while ((size_t)(table_end - entry) >= sizeof(struct cb_record)) { + size_t rec_size; + + rec = (struct cb_record *)entry; + rec_size = rec->size; + if (rec_size < sizeof(struct cb_record)) + break; + if (rec_size > (size_t)(table_end - entry)) + break; + if (rec->tag == CB_TAG_FORWARD) { + if (rec_size >= sizeof(struct cb_forward)) { + struct cb_forward *fwd; + + fwd = (struct cb_forward *)entry; + real_addr = (vm_paddr_t)fwd->forward; + } + break; + } + entry += rec_size; + } + pmap_unmapbios(va, map_size); + + child = BUS_ADD_CHILD(parent, 5, "coreboot", DEVICE_UNIT_ANY); + if (child == NULL) + return; + device_set_driver(child, driver); + + bus_set_resource(child, SYS_RES_MEMORY, 0, real_addr, PAGE_SIZE); + device_set_desc(child, "coreboot firmware table"); +} + +/* + * Probe: validate the coreboot header at the discovered address + */ +static int +coreboot_probe(device_t dev) +{ + vm_paddr_t pa; + void *va; + vm_size_t map_size; + int error; + + pa = bus_get_resource_start(dev, SYS_RES_MEMORY, 0); + if (pa == 0) + return (ENXIO); + + error = coreboot_map_table(pa, &va, &map_size); + if (error != 0) + return (error); + + pmap_unmapbios(va, map_size); + return (BUS_PROBE_SPECIFIC); +} + +/* + * Attach: map the full table, parse records, register sysctls and cdevs + */ +static int +coreboot_attach(device_t dev) +{ + struct coreboot_softc *sc; + struct cb_header *hdr; + vm_paddr_t pa; + void *va; + vm_size_t map_size; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + + pa = bus_get_resource_start(dev, SYS_RES_MEMORY, 0); + + error = coreboot_map_table(pa, &va, &map_size); + if (error != 0) { + device_printf(dev, "coreboot table validation failed at %#jx\n", + (uintmax_t)pa); + return (error); + } + + sc->table_paddr = pa; + sc->table_size = map_size; + sc->table_vaddr = va; + + hdr = (struct cb_header *)va; + device_printf(dev, "coreboot table at %#jx (%u entries, %u bytes)\n", + (uintmax_t)pa, hdr->table_entries, hdr->table_bytes); + + coreboot_parse_table(sc, hdr); + + if (sc->version[0] != '\0') + device_printf(dev, "firmware: %s\n", sc->version); + if (sc->mb_vendor[0] != '\0') + device_printf(dev, "mainboard: %s %s\n", sc->mb_vendor, + sc->mb_part); + if (sc->has_console) + device_printf(dev, "CBMEM console at %#jx\n", + (uintmax_t)sc->console_paddr); + device_printf(dev, "CBMEM entries: %u\n", sc->cbmem_count); + if (sc->has_board_config) + device_printf(dev, + "board: id=%u sku=%u fw_config=%#jx\n", + sc->board_id, sc->sku_id, + (uintmax_t)sc->fw_config); + if (sc->mac_count > 0) + device_printf(dev, "factory MAC addresses: %u\n", + sc->mac_count); + if (sc->has_acpi_rsdp) + device_printf(dev, "ACPI RSDP at %#jx\n", + (uintmax_t)sc->acpi_rsdp); + if (sc->has_pcie) + device_printf(dev, "PCIe controller at %#jx\n", + (uintmax_t)sc->pcie_ctrl_base); + if (sc->has_boot_media) + device_printf(dev, "boot media: %#jx bytes, CBFS %#jx+%#jx\n", + (uintmax_t)sc->boot_media_size, + (uintmax_t)sc->cbfs_offset, (uintmax_t)sc->cbfs_size); + if (sc->has_mmc_info) + device_printf(dev, "MMC early CMD1 status: %d\n", + sc->mmc_early_cmd1_status); + + if (bootverbose) { + if (sc->has_spi_flash) + device_printf(dev, + "SPI flash: %u bytes, sector %u, erase %#x\n", + sc->spi_flash_size, sc->spi_sector_size, + sc->spi_erase_cmd); + if (sc->has_console_type) + device_printf(dev, "console type: %u\n", + sc->console_type); + if (sc->has_framebuffer) + device_printf(dev, + "framebuffer: %ux%u@%ubpp at %#jx\n", + sc->fb_x_res, sc->fb_y_res, sc->fb_bpp, + (uintmax_t)sc->fb_addr); + if (sc->gpio_count > 0) + device_printf(dev, "GPIO pins: %u\n", + sc->gpio_count); + if (sc->has_tpm) + device_printf(dev, "TPM %u.%u PPI at %#x\n", + sc->tpm_version == 2 ? 2 : 1, + sc->tpm_version == 2 ? 0 : 2, + sc->tpm_ppi_addr); + } + + if (coreboot_debug) { + if (sc->has_smmstore) + device_printf(dev, + "SMMSTORE v2: %u blocks x %u bytes, " + "apm_cmd=%#x\n", + sc->smmstore_num_blocks, + sc->smmstore_block_size, + sc->smmstore_apm_cmd); + if (sc->has_timestamps) + device_printf(dev, "timestamps at %#jx\n", + (uintmax_t)sc->timestamps_paddr); + if (sc->has_tpm_log) + device_printf(dev, "TPM CB log at %#jx\n", + (uintmax_t)sc->tpm_log_paddr); + if (sc->has_fmap) + device_printf(dev, "FMAP at %#jx\n", + (uintmax_t)sc->fmap_paddr); + } + + coreboot_register_sysctls(sc); + + coreboot_sc = sc; + + if (sc->has_console) { + error = coreboot_console_create(sc); + if (error != 0) + device_printf(dev, + "failed to create /dev/coreboot_console (%d)\n", + error); + } + if (sc->cbmem_count > 0) { + error = coreboot_cbmem_create(sc); + if (error != 0) + device_printf(dev, "failed to create /dev/cbmem (%d)\n", + error); + } + + return (0); +} + +/* + * Detach: unmap table, destroy cdevs and sysctls + */ +static int +coreboot_detach(device_t dev) +{ + struct coreboot_softc *sc; + + sc = device_get_softc(dev); + + coreboot_cbmem_destroy(sc); + coreboot_console_destroy(sc); + + coreboot_sc = NULL; + + sysctl_ctx_free(&sc->sysctl_ctx); + + if (sc->table_vaddr != NULL) { + pmap_unmapbios(sc->table_vaddr, sc->table_size); + sc->table_vaddr = NULL; + } + + if (sc->console_vaddr != NULL) { + pmap_unmapbios(sc->console_vaddr, sc->console_size); + sc->console_vaddr = NULL; + sc->console_size = 0; + sc->console_data_size = 0; + } + + return (0); +} + +static int +coreboot_modevent(module_t mod, int what, void *arg) +{ + device_t *devs; + int count, i; + + switch (what) { + case MOD_LOAD: + break; + case MOD_UNLOAD: + devclass_get_devices(devclass_find("coreboot"), &devs, &count); + for (i = 0; i < count; i++) + device_delete_child(device_get_parent(devs[i]), + devs[i]); + free(devs, M_TEMP); + break; + default: + break; + } + + return (0); +} + +static device_method_t coreboot_methods[] = { + DEVMETHOD(device_identify, coreboot_identify), + DEVMETHOD(device_probe, coreboot_probe), + DEVMETHOD(device_attach, coreboot_attach), + DEVMETHOD(device_detach, coreboot_detach), + DEVMETHOD_END +}; + +static driver_t coreboot_driver = { + "coreboot", + coreboot_methods, + sizeof(struct coreboot_softc), +}; + +DRIVER_MODULE(coreboot, nexus, coreboot_driver, coreboot_modevent, NULL); +MODULE_VERSION(coreboot, 1); diff --git a/sys/dev/coreboot/coreboot.h b/sys/dev/coreboot/coreboot.h new file mode 100644 index 000000000000..e2dd84da269f --- /dev/null +++ b/sys/dev/coreboot/coreboot.h @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * coreboot(4) driver for FreeBSD + * + * Structures and constants derived from the coreboot table specification. + */ + +#ifndef _DEV_COREBOOT_COREBOOT_H_ +#define _DEV_COREBOOT_COREBOOT_H_ + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/sysctl.h> +#include <sys/bus.h> + +#include <dev/coreboot/corebootio.h> + +#define CB_HEADER_SIGNATURE "LBIO" +#define CB_HEADER_SIG_LEN 4 + +/* + * Memory scan range for coreboot table discovery. + * Low memory (0x0–0x1000) contains a forward pointer to the real table. + */ +#define CB_SCAN_LOW_START 0x00000000 +#define CB_SCAN_LOW_END 0x00001000 +#define CB_SCAN_LOW_STEP 16 + +/* + * Defensive limits for parsing untrusted firmware-provided lengths. + */ +#define CB_TABLE_ALIGN 4 +#define CB_MAX_HEADER_BYTES 4096 +#define CB_MAX_TABLE_BYTES (1024 * 1024) +#define CB_MAX_TABLE_MAP_BYTES (CB_MAX_HEADER_BYTES + CB_MAX_TABLE_BYTES) +#define CB_MAX_CONSOLE_BYTES (1024 * 1024) + +/* + * Coreboot table record tags - only tags we actually parse. + * Full enum preserved for forward compatibility (unknown tags are skipped). + */ +enum cb_tag { + CB_TAG_UNUSED = 0x0000, + CB_TAG_MAINBOARD = 0x0003, + CB_TAG_VERSION = 0x0004, + CB_TAG_EXTRA_VERSION = 0x0005, + CB_TAG_BUILD = 0x0006, + CB_TAG_COMPILE_TIME = 0x0007, + CB_TAG_COMPILER = 0x000b, + CB_TAG_SERIAL = 0x000f, + CB_TAG_CONSOLE = 0x0010, + CB_TAG_FORWARD = 0x0011, + CB_TAG_FRAMEBUFFER = 0x0012, + CB_TAG_GPIO = 0x0013, + CB_TAG_TIMESTAMPS = 0x0016, + CB_TAG_CBMEM_CONSOLE = 0x0017, + CB_TAG_ACPI_GNVS = 0x0024, + CB_TAG_VERSION_TIMESTAMP = 0x0026, + CB_TAG_WIFI_CALIBRATION = 0x0027, + CB_TAG_SPI_FLASH = 0x0029, + CB_TAG_SERIALNO = 0x002a, + CB_TAG_VPD = 0x002c, + CB_TAG_BOOT_MEDIA_PARAMS = 0x0030, + CB_TAG_CBMEM_ENTRY = 0x0031, + CB_TAG_TSC_INFO = 0x0032, + CB_TAG_MAC_ADDRS = 0x0033, + CB_TAG_VBOOT_WORKBUF = 0x0034, + CB_TAG_MMC_INFO = 0x0035, + CB_TAG_TPM_CB_LOG = 0x0036, + CB_TAG_FMAP = 0x0037, + CB_TAG_PLATFORM_BLOB_VERSION = 0x0038, + CB_TAG_SMMSTOREV2 = 0x0039, + CB_TAG_TPM_PPI_HANDOFF = 0x003a, + CB_TAG_BOARD_CONFIG = 0x0040, + CB_TAG_ACPI_CNVS = 0x0041, + CB_TAG_TYPE_C_INFO = 0x0042, + CB_TAG_ACPI_RSDP = 0x0043, + CB_TAG_PCIE = 0x0044, + CB_TAG_ROOT_BRIDGE_INFO = 0x0048, +}; + +/* + * Coreboot table header - located at a physical address found by scanning + * low memory for the "LBIO" signature. + */ +struct cb_header { + uint8_t signature[CB_HEADER_SIG_LEN]; + uint32_t header_bytes; + uint32_t header_checksum; + uint32_t table_bytes; + uint32_t table_checksum; + uint32_t table_entries; +} __packed; + +/* + * Generic record header - every table entry starts with this. + */ +struct cb_record { + uint32_t tag; + uint32_t size; +} __packed; + +/* + * CB_TAG_FORWARD - pointer to the real table in high memory. + */ +struct cb_forward { + uint32_t tag; + uint32_t size; + uint64_t forward; +} __packed; + +/* + * CB_TAG_MAINBOARD - board vendor and part number. + * Strings are packed after the struct, indexed by vendor_idx and part_idx. + */ +struct cb_mainboard { + uint32_t tag; + uint32_t size; + uint8_t vendor_idx; + uint8_t part_idx; + uint8_t strings[]; +} __packed; + +/* + * Variable-length string record - used by VERSION, BUILD, COMPILE_*, etc. + */ +struct cb_string { + uint32_t tag; + uint32_t size; + uint8_t string[]; +} __packed; + +/* + * CB_TAG_SERIAL - serial port configuration. + */ +struct cb_serial { + uint32_t tag; + uint32_t size; + uint32_t type; + uint32_t baseaddr; + uint32_t baud; + uint32_t regwidth; + uint32_t input_hertz; +} __packed; + +/* + * CB_TAG_CBMEM_CONSOLE - pointer to firmware console ring buffer. + */ +struct cb_cbmem_ref { + uint32_t tag; + uint32_t size; + uint64_t cbmem_addr; +} __packed; + +/* + * CB_TAG_CBMEM_ENTRY - one per CBMEM region. + */ +struct cb_cbmem_entry { + uint32_t tag; + uint32_t size; + uint64_t address; + uint32_t entry_size; + uint32_t id; +} __packed; + +/* + * CB_TAG_TSC_INFO - TSC frequency. + */ +struct cb_tsc_info { + uint32_t tag; + uint32_t size; + uint32_t freq_khz; +} __packed; + +/* + * CB_TAG_VERSION_TIMESTAMP - build version timestamp. + * timestamp is Unix time in seconds (seconds since 1970-01-01 UTC). + */ +struct cb_version_timestamp { + uint32_t tag; + uint32_t size; + uint32_t timestamp; +} __packed; + +/* + * CB_TAG_BOARD_CONFIG - board identification and firmware config. + */ +struct cb_board_config { + uint32_t tag; + uint32_t size; + uint64_t fw_config; + uint32_t board_id; + uint32_t ram_code; + uint32_t sku_id; +} __packed; + +/* + * CB_TAG_BOOT_MEDIA_PARAMS - offsets and sizes on boot media. + */ +struct cb_boot_media_params { + uint32_t tag; + uint32_t size; + uint64_t fmap_offset; + uint64_t cbfs_offset; + uint64_t cbfs_size; + uint64_t boot_media_size; +} __packed; + +/* + * CB_TAG_MMC_INFO - early eMMC/MMC status. + */ +struct cb_mmc_info { + uint32_t tag; + uint32_t size; + int32_t early_cmd1_status; +} __packed; + +/* + * CB_TAG_PCIE - PCIe controller base address. + */ +struct cb_pcie { + uint32_t tag; + uint32_t size; + uint64_t ctrl_base; +} __packed; + +/* + * CB_TAG_MAC_ADDRS - factory-provisioned MAC addresses. + */ +struct cb_mac_address { + uint8_t mac_addr[6]; + uint8_t pad[2]; +} __packed; + +struct cb_macs { + uint32_t tag; + uint32_t size; + uint32_t count; + struct cb_mac_address entries[]; +} __packed; + +/* + * CB_TAG_SPI_FLASH - SPI flash chip parameters. + */ +struct cb_flash_mmap_window { + uint32_t flash_base; + uint32_t host_base; + uint32_t size; +} __packed; + +struct cb_spi_flash { + uint32_t tag; + uint32_t size; + uint32_t flash_size; + uint32_t sector_size; + uint8_t erase_cmd; + uint8_t flags; + uint16_t reserved; + uint32_t mmap_count; + struct cb_flash_mmap_window mmap_table[]; +} __packed; + +/* + * CB_TAG_CONSOLE - firmware console type. + */ +struct cb_console { + uint32_t tag; + uint32_t size; + uint16_t type; + uint8_t pad[2]; +} __packed; + +#define CB_CONSOLE_SERIAL8250 0 +#define CB_CONSOLE_VGA 1 /* obsolete */ +#define CB_CONSOLE_EHCI 5 +#define CB_CONSOLE_SERIAL8250MEM 6 + +/* + * CB_TAG_FRAMEBUFFER - linear framebuffer info. + * The orientation, flags, and pad fields were added later. + * Minimum record size is up to reserved_mask_size (29 bytes). + */ +#define CB_FRAMEBUFFER_MIN_SIZE 29 +struct cb_framebuffer { + uint32_t tag; + uint32_t size; + uint64_t physical_address; + uint32_t x_resolution; + uint32_t y_resolution; + uint32_t bytes_per_line; + uint8_t bits_per_pixel; + uint8_t red_mask_pos; + uint8_t red_mask_size; + uint8_t green_mask_pos; + uint8_t green_mask_size; + uint8_t blue_mask_pos; + uint8_t blue_mask_size; + uint8_t reserved_mask_pos; + uint8_t reserved_mask_size; + uint8_t orientation; + uint8_t flags; + uint8_t pad; +} __packed; + +/* + * CB_TAG_GPIO - GPIO pin states. + */ +struct cb_gpio { + uint32_t port; + uint32_t polarity; + uint32_t value; + uint8_t name[16]; +} __packed; + +struct cb_gpios { + uint32_t tag; + uint32_t size; + uint32_t count; + struct cb_gpio entries[]; +} __packed; + +/* + * CB_TAG_TPM_PPI_HANDOFF - TPM Physical Presence Interface handoff. + */ +struct cb_tpm_ppi { + uint32_t tag; + uint32_t size; + uint32_t ppi_address; + uint8_t tpm_version; /* 1=TPM1.2, 2=TPM2.0 */ + uint8_t ppi_version; /* BCD encoded */ + uint8_t pad[2]; +} __packed; + +/* + * CB_TAG_SMMSTOREV2 - SMM-based variable store configuration. + * + * The mmap_addr field was added after the initial implementation. + * Older coreboot emits a shorter record (32 bytes) without it. + * Consumers must check rec->size to detect whether mmap_addr is present. + */ +#define CB_SMMSTOREV2_BASE_SIZE 32 /* size without mmap_addr */ +struct cb_smmstorev2 { + uint32_t tag; + uint32_t size; + uint32_t num_blocks; + uint32_t block_size; + uint32_t mmap_addr_lo; /* deprecated 32-bit address */ + uint32_t com_buffer; + uint32_t com_buffer_size; + uint8_t apm_cmd; + uint8_t unused[3]; + /* Fields below only present if size > CB_SMMSTOREV2_BASE_SIZE */ + uint64_t mmap_addr; /* 64-bit address (preferred) */ +} __packed; + +/* + * CB_TAG_ACPI_RSDP - ACPI Root System Description Pointer address. + */ +struct cb_acpi_rsdp { + uint32_t tag; + uint32_t size; + uint64_t rsdp_pointer; +} __packed; + +/* + * CBMEM console ring buffer (in-memory structure at cbmem_addr). + * Bit 31 of cursor indicates overflow (ring has wrapped). + */ +struct cbmem_console { + uint32_t size; + uint32_t cursor; + uint8_t body[]; +} __packed; + +#define CBMEM_CONSOLE_CURSOR_MASK ((1 << 28) - 1) +#define CBMEM_CONSOLE_OVERFLOW (1 << 31) + +#define CB_MAX_MAC_ADDRS 8 +#define CB_MAX_GPIOS 32 + +/* + * Driver softc + */ +struct coreboot_softc { + device_t dev; + + vm_paddr_t table_paddr; + vm_size_t table_size; + void *table_vaddr; + + char version[64]; + char build[64]; + char compile_time[64]; + char compiler[128]; + char extra_version[64]; + char platform_blob_version[64]; + char serialno[64]; + uint32_t version_timestamp; + int has_version_timestamp; + + char mb_vendor[64]; + char mb_part[64]; + + uint32_t serial_baseaddr; + uint32_t serial_baud; + uint32_t serial_regwidth; + int has_serial; + + uint32_t tsc_freq_khz; + int has_tsc_info; + + uint64_t pcie_ctrl_base; + int has_pcie; + + uint64_t fmap_offset; + uint64_t cbfs_offset; + uint64_t cbfs_size; + uint64_t boot_media_size; + int has_boot_media; + + int32_t mmc_early_cmd1_status; + int has_mmc_info; + + vm_paddr_t console_paddr; + vm_size_t console_size; + uint32_t console_data_size; + struct cbmem_console *console_vaddr; + int has_console; + struct cdev *console_cdev; + + uint32_t cbmem_count; + struct cbmem_entry_info cbmem_entries[CB_MAX_CBMEM_ENTRIES]; + struct cdev *cbmem_cdev; + + uint64_t fw_config; + uint32_t board_id; + uint32_t ram_code; + uint32_t sku_id; + int has_board_config; + + uint32_t mac_count; + struct cb_mac_address macs[CB_MAX_MAC_ADDRS]; + char mac_strs[CB_MAX_MAC_ADDRS][18]; + + uint64_t acpi_rsdp; + int has_acpi_rsdp; + + uint32_t spi_flash_size; + uint32_t spi_sector_size; + uint8_t spi_erase_cmd; + uint8_t spi_flags; + int has_spi_flash; + + uint16_t console_type; + int has_console_type; + + uint64_t fb_addr; + uint32_t fb_x_res; + uint32_t fb_y_res; + uint32_t fb_stride; + uint8_t fb_bpp; + int has_framebuffer; + + uint32_t gpio_count; + struct cb_gpio gpios[CB_MAX_GPIOS]; + + uint32_t tpm_ppi_addr; + uint8_t tpm_version; + int has_tpm; + + vm_paddr_t tpm_log_paddr; + int has_tpm_log; + + vm_paddr_t acpi_gnvs_paddr; + int has_acpi_gnvs; + vm_paddr_t acpi_cnvs_paddr; + int has_acpi_cnvs; + vm_paddr_t vpd_paddr; + int has_vpd; + vm_paddr_t wifi_cal_paddr; + int has_wifi_cal; + vm_paddr_t fmap_paddr; + int has_fmap; + vm_paddr_t vboot_workbuf_paddr; + int has_vboot_workbuf; + vm_paddr_t type_c_info_paddr; + int has_type_c_info; + vm_paddr_t root_bridge_info_paddr; + int has_root_bridge_info; + + uint32_t smmstore_num_blocks; + uint32_t smmstore_block_size; + uint64_t smmstore_mmap_addr; + uint32_t smmstore_com_buffer; + uint8_t smmstore_apm_cmd; + int has_smmstore; + + vm_paddr_t timestamps_paddr; + int has_timestamps; + + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; +}; + +/* + * Generic ID-to-name linear search. + * The table must be terminated by an entry with name == NULL. + * Returns fallback if no match is found. + */ +struct cb_id_name { + uint32_t id; + const char *name; +}; + +static __inline const char * +cb_id_lookup(const struct cb_id_name *tbl, uint32_t id, const char *fallback) +{ + int i; + + for (i = 0; tbl[i].name != NULL; i++) { + if (tbl[i].id == id) + return (tbl[i].name); + } + return (fallback); +} + +/* + * CBMEM ID to human-readable name lookup. + */ +static __inline const char * +cbmem_id_to_name(uint32_t id) +{ + static const struct cb_id_name names[] = { + { CBMEM_ID_ACPI, "ACPI" }, + { CBMEM_ID_ACPI_GNVS, "ACPI GNVS" }, + { CBMEM_ID_AFTER_CAR, "AFTER CAR" }, + { CBMEM_ID_CBTABLE, "COREBOOT" }, + { CBMEM_ID_CBTABLE_FWD, "COREBOOT FWD" }, + { CBMEM_ID_CBFS_RO_MCACHE, "RO MCACHE" }, + { CBMEM_ID_CBFS_RW_MCACHE, "RW MCACHE" }, + { CBMEM_ID_CONSOLE, "CONSOLE" }, + { CBMEM_ID_ELOG, "ELOG" }, + { CBMEM_ID_FMAP, "FMAP" }, + { CBMEM_ID_FREESPACE, "FREE SPACE" }, + { CBMEM_ID_FSP_RESERVED_MEMORY, "FSP MEMORY" }, + { CBMEM_ID_FSP_RUNTIME, "FSP RUNTIME" }, + { CBMEM_ID_FSPM_VERSION, "FSPM VERSION" }, + { CBMEM_ID_IGD_OPREGION, "IGD OPREGION" }, + { CBMEM_ID_IMD_ROOT, "IMD ROOT" }, + { CBMEM_ID_IMD_SMALL, "IMD SMALL" }, + { CBMEM_ID_MEMINFO, "MEM INFO" }, + { CBMEM_ID_MPTABLE, "SMP TABLE" }, + { CBMEM_ID_MRCDATA, "MRC DATA" }, + { CBMEM_ID_PIRQ, "IRQ TABLE" }, + { CBMEM_ID_POWER_STATE, "POWER STATE" }, + { CBMEM_ID_RAM_OOPS, "RAMOOPS" }, + { CBMEM_ID_RAMSTAGE, "RAMSTAGE" }, + { CBMEM_ID_REFCODE, "REFCODE" }, + { CBMEM_ID_RESUME, "ACPI RESUME" }, + { CBMEM_ID_ROMSTAGE_INFO, "ROMSTAGE" }, + { CBMEM_ID_ROMSTAGE_RAM_STACK, "ROMSTG STACK" }, + { CBMEM_ID_ROOT, "CBMEM ROOT" }, + { CBMEM_ID_SMBIOS, "SMBIOS" }, + { CBMEM_ID_SMM_COMBUFFER, "SMM COMBUF" }, + { CBMEM_ID_SMM_SAVE_SPACE, "SMM BACKUP" }, + { CBMEM_ID_TIMESTAMP, "TIMESTAMP" }, + { CBMEM_ID_VBOOT_WORKBUF, "VBOOT WORK" }, + { CBMEM_ID_VPD, "VPD" }, + { 0, NULL } + }; + + return (cb_id_lookup(names, id, "UNKNOWN")); +} + +/* + * IP-style 16-bit checksum (RFC 1071) over 16-bit words. + * When computed over data including its checksum field, result is 0. + */ +static __inline uint16_t +cb_checksum(const void *ptr, size_t len) +{ + const uint8_t *p = (const uint8_t *)ptr; + uint32_t sum = 0; + size_t i; + + for (i = 0; i + 1 < len; i += 2) + sum += (uint32_t)p[i] | ((uint32_t)p[i + 1] << 8); + + if (i < len) + sum += p[i]; + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ((uint16_t)~sum); +} + +/* + * Destroy a character device and clear the pointer. + * Safe to call with a NULL cdev pointer. + */ +static __inline void +coreboot_cdev_destroy(struct cdev **cdevp) +{ + + if (*cdevp != NULL) { + destroy_dev(*cdevp); + *cdevp = NULL; + } +} + +/* Functions exported from coreboot.c */ +struct coreboot_softc *coreboot_get_softc(void); + +/* Functions exported from coreboot_console.c */ +int coreboot_console_create(struct coreboot_softc *sc); +void coreboot_console_destroy(struct coreboot_softc *sc); + +/* Functions exported from coreboot_cbmem.c */ +int coreboot_cbmem_create(struct coreboot_softc *sc); +void coreboot_cbmem_destroy(struct coreboot_softc *sc); + +/* Functions exported from coreboot_timestamps.c */ +int coreboot_timestamps_register(struct coreboot_softc *sc, + struct sysctl_oid *parent); + +#endif /* _DEV_COREBOOT_COREBOOT_H_ */ diff --git a/sys/dev/coreboot/coreboot_cbmem.c b/sys/dev/coreboot/coreboot_cbmem.c new file mode 100644 index 000000000000..b74e09e2d553 --- /dev/null +++ b/sys/dev/coreboot/coreboot_cbmem.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * /dev/cbmem — ioctl interface for CBMEM entry enumeration and read + * + * Provides structured access to coreboot's CBMEM entries without + * requiring /dev/mem. Entries can be listed (CBMEM_IOC_LIST) or + * read by ID (CBMEM_IOC_READ). + */ + +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/ioccom.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/uio.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <dev/coreboot/coreboot.h> + +static d_ioctl_t coreboot_cbmem_ioctl; + +static struct cdevsw coreboot_cbmem_cdevsw = { + .d_version = D_VERSION, + .d_ioctl = coreboot_cbmem_ioctl, + .d_name = "cbmem", +}; + +/* + * Find a CBMEM entry by its ID. + */ +static struct cbmem_entry_info * +cbmem_find_entry(struct coreboot_softc *sc, uint32_t id) +{ + uint32_t i; + + for (i = 0; i < sc->cbmem_count; i++) { + if (sc->cbmem_entries[i].id == id) + return (&sc->cbmem_entries[i]); + } + return (NULL); +} + +static int +coreboot_cbmem_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) +{ + struct coreboot_softc *sc; + int error; + + sc = dev->si_drv1; + + switch (cmd) { + case CBMEM_IOC_LIST: { + struct cbmem_list *list = (struct cbmem_list *)data; + + list->count = sc->cbmem_count; + memcpy(list->entries, sc->cbmem_entries, + sc->cbmem_count * sizeof(struct cbmem_entry_info)); + error = 0; + break; + } + + case CBMEM_IOC_READ: { + struct cbmem_read_req *req = (struct cbmem_read_req *)data; + struct cbmem_entry_info *info; + void *mapped; + vm_size_t map_size; + uint64_t paddr; + + info = cbmem_find_entry(sc, req->id); + if (info == NULL) { + error = ENOENT; + break; + } + + if (req->offset >= info->size) { + error = EINVAL; + break; + } + + if (req->size > info->size - req->offset) + req->size = info->size - req->offset; + + if (req->size == 0) { + error = 0; + break; + } + + if (info->address > UINT64_MAX - req->offset) { + error = EINVAL; + break; + } + paddr = info->address + req->offset; + map_size = req->size; + mapped = pmap_mapbios((vm_paddr_t)paddr, map_size); + if (mapped == NULL) { + error = ENOMEM; + break; + } + + error = copyout(mapped, req->buffer, req->size); + + pmap_unmapbios(mapped, map_size); + break; + } + + default: + error = ENOTTY; + break; + } + + return (error); +} + +int +coreboot_cbmem_create(struct coreboot_softc *sc) +{ + struct make_dev_args args; + int error; + + if (sc->cbmem_count == 0) + return (ENXIO); + + make_dev_args_init(&args); + args.mda_devsw = &coreboot_cbmem_cdevsw; + args.mda_uid = UID_ROOT; + args.mda_gid = GID_WHEEL; + args.mda_mode = 0440; + args.mda_si_drv1 = sc; + error = make_dev_s(&args, &sc->cbmem_cdev, "cbmem"); + if (error != 0) + return (error); + + return (0); +} + +void +coreboot_cbmem_destroy(struct coreboot_softc *sc) +{ + + coreboot_cdev_destroy(&sc->cbmem_cdev); +} diff --git a/sys/dev/coreboot/coreboot_console.c b/sys/dev/coreboot/coreboot_console.c new file mode 100644 index 000000000000..0e68fac007e9 --- /dev/null +++ b/sys/dev/coreboot/coreboot_console.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * /dev/coreboot_console — read-only access to the CBMEM firmware console + * + * The CBMEM console is a ring buffer written by coreboot during boot. + * Bit 31 of the cursor field indicates overflow (the buffer has wrapped). + * When overflow is set, data starts at cursor and wraps around. + * When not set, data starts at offset 0 and ends at cursor. + */ + +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/uio.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <dev/coreboot/coreboot.h> + +static d_read_t coreboot_console_read; + +static struct cdevsw coreboot_console_cdevsw = { + .d_version = D_VERSION, + .d_read = coreboot_console_read, + .d_name = "coreboot_console", +}; + +/* + * Read the console ring buffer. + * + * The ring buffer has a cursor that may have wrapped. If the OVERFLOW + * bit (bit 31) is set, the ring has wrapped and data goes from + * body[cursor..size-1] then body[0..cursor-1]. Otherwise it's just + * body[0..cursor-1]. + * + * We linearize the buffer into a contiguous view for the uiomove. + */ +static int +coreboot_console_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct coreboot_softc *sc; + struct cbmem_console *cons; + uint32_t cursor, flags, buf_size; + uint32_t data_start, data_len; + off_t offset; + size_t todo; + int error; + + sc = dev->si_drv1; + if (sc->console_vaddr == NULL) + return (ENXIO); + + cons = sc->console_vaddr; + cursor = cons->cursor & CBMEM_CONSOLE_CURSOR_MASK; + flags = cons->cursor & ~CBMEM_CONSOLE_CURSOR_MASK; + buf_size = sc->console_data_size; + + if (cursor > buf_size) + cursor = 0; + + if (flags & CBMEM_CONSOLE_OVERFLOW) { + /* + * Ring has wrapped. Logical order: + * segment 1: body[cursor .. buf_size-1] (oldest data) + * segment 2: body[0 .. cursor-1] (newest data) + * Total length = buf_size + */ + data_len = buf_size; + offset = uio->uio_offset; + + if (offset >= data_len) + return (0); + + while (uio->uio_resid > 0 && offset < data_len) { + uint32_t seg_start, seg_len; + uint32_t seg1_len = buf_size - cursor; + + if (offset < seg1_len) { + seg_start = cursor + offset; + seg_len = seg1_len - offset; + } else { + seg_start = offset - seg1_len; + seg_len = cursor - seg_start; + } + + todo = MIN(uio->uio_resid, seg_len); + error = uiomove(cons->body + seg_start, todo, uio); + if (error != 0) + return (error); + offset += todo; + } + } else { + /* + * No overflow — data is linear: body[0 .. cursor-1] + */ + data_start = 0; + data_len = cursor; + offset = uio->uio_offset; + + if (offset >= data_len) + return (0); + + todo = MIN(uio->uio_resid, data_len - offset); + error = uiomove(cons->body + data_start + offset, todo, uio); + if (error != 0) + return (error); + } + + return (0); +} + +int +coreboot_console_create(struct coreboot_softc *sc) +{ + struct cbmem_console *tmp; + vm_size_t map_size; + uint32_t cons_size; + + if (!sc->has_console || sc->console_paddr == 0) + return (ENXIO); + + tmp = (struct cbmem_console *)pmap_mapbios(sc->console_paddr, + sizeof(struct cbmem_console)); + if (tmp == NULL) { + device_printf(sc->dev, + "unable to map CBMEM console header\n"); + return (ENOMEM); + } + + cons_size = tmp->size; + pmap_unmapbios(tmp, sizeof(struct cbmem_console)); + if (cons_size == 0 || cons_size > CB_MAX_CONSOLE_BYTES) { + device_printf(sc->dev, "invalid CBMEM console size: %u\n", + cons_size); + return (EINVAL); + } + map_size = sizeof(struct cbmem_console) + cons_size; + + sc->console_vaddr = (struct cbmem_console *)pmap_mapbios( + sc->console_paddr, map_size); + if (sc->console_vaddr == NULL) { + device_printf(sc->dev, + "unable to map CBMEM console (%zu bytes)\n", + (size_t)map_size); + return (ENOMEM); + } + sc->console_size = map_size; + sc->console_data_size = cons_size; + + struct make_dev_args args; + int error; + + make_dev_args_init(&args); + args.mda_devsw = &coreboot_console_cdevsw; + args.mda_uid = UID_ROOT; + args.mda_gid = GID_WHEEL; + args.mda_mode = 0440; + args.mda_si_drv1 = sc; + error = make_dev_s(&args, &sc->console_cdev, "coreboot_console"); + if (error != 0) { + pmap_unmapbios(sc->console_vaddr, sc->console_size); + sc->console_vaddr = NULL; + sc->console_size = 0; + sc->console_data_size = 0; + return (error); + } + + device_printf(sc->dev, + "CBMEM console: %u bytes, cursor at %u%s\n", + sc->console_vaddr->size, + sc->console_vaddr->cursor & CBMEM_CONSOLE_CURSOR_MASK, + (sc->console_vaddr->cursor & CBMEM_CONSOLE_OVERFLOW) ? + " (wrapped)" : ""); + + return (0); +} + +void +coreboot_console_destroy(struct coreboot_softc *sc) +{ + + coreboot_cdev_destroy(&sc->console_cdev); +} diff --git a/sys/dev/coreboot/coreboot_timestamps.c b/sys/dev/coreboot/coreboot_timestamps.c new file mode 100644 index 000000000000..ab0e5b2cd114 --- /dev/null +++ b/sys/dev/coreboot/coreboot_timestamps.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * coreboot(4) timestamp CBMEM reader + * + * Maps the timestamp CBMEM region on demand and formats boot stage + * timing as a human-readable sysctl. The mapping is transient — + * created when the sysctl is read and released immediately after. + */ + +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/sbuf.h> +#include <sys/sysctl.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <dev/coreboot/coreboot.h> + +/* + * Timestamp ID to name mapping. + * IDs from coreboot's src/commonlib/include/commonlib/timestamp_serialized.h. + */ +static const struct cb_id_name ts_names[] = { + { 1, "start of romstage" }, + { 2, "before RAM initialization" }, + { 3, "after RAM initialization" }, + { 4, "end of romstage" }, + { 5, "start of verified boot" }, + { 6, "end of verified boot" }, + { 8, "starting to load ramstage" }, + { 9, "finished loading ramstage" }, + { 10, "start of ramstage" }, + { 11, "start of bootblock" }, + { 12, "end of bootblock" }, + { 13, "starting to load romstage" }, + { 14, "finished loading romstage" }, + { 15, "starting LZMA decompress" }, + { 16, "finished LZMA decompress" }, + { 17, "starting LZ4 decompress" }, + { 18, "finished LZ4 decompress" }, + { 19, "starting ZSTD decompress" }, + { 20, "finished ZSTD decompress" }, + { 30, "early chipset initialization" }, + { 31, "device enumeration" }, + { 40, "device configure" }, + { 50, "device enable" }, + { 60, "device initialization" }, + { 65, "option ROM initialization" }, + { 66, "option ROM copy done" }, + { 67, "option ROM run done" }, + { 70, "device setup done" }, + { 75, "cbmem post" }, + { 80, "write tables" }, + { 85, "finalize chips" }, + { 90, "starting to load payload" }, + { 98, "acpi wake jump" }, + { 99, "selfboot jump" }, + { 100, "start of postcar" }, + { 101, "end of postcar" }, + { 110, "forced delay start" }, + { 111, "forced delay end" }, + { 112, "read microcode start" }, + { 113, "read microcode end" }, + { 114, "elog init start" }, + { 115, "elog init end" }, + { 950, "fsp memory init start" }, + { 951, "fsp memory init end" }, + { 952, "fsp temp RAM exit start" }, + { 953, "fsp temp RAM exit end" }, + { 954, "fsp silicon init start" }, + { 955, "fsp silicon init end" }, + { 956, "fsp enumerate start" }, + { 957, "fsp enumerate end" }, + { 958, "fsp finalize start" }, + { 959, "fsp finalize end" }, + { 960, "fsp end-of-firmware start" }, + { 961, "fsp end-of-firmware end" }, + { 962, "fsp multi-phase silicon init start" }, + { 963, "fsp multi-phase silicon init end" }, + { 964, "fsp multi-phase memory init start" }, + { 965, "fsp multi-phase memory init end" }, + { 970, "fsp memory init load" }, + { 971, "fsp silicon init load" }, + { 1000, "depthcharge start" }, + { 1100, "vboot done" }, + { 1101, "kernel start" }, + { 1102, "kernel decompression" }, + { 0, NULL } +}; + +static const char * +ts_id_to_name(uint32_t id) +{ + + return (cb_id_lookup(ts_names, id, NULL)); +} + +/* + * Sysctl handler for hw.coreboot.timestamps. + * Maps the CBMEM timestamp region, formats entries, and unmaps. + */ +static int +coreboot_timestamps_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct coreboot_softc *sc = arg1; + struct cb_timestamp_table *tst; + struct sbuf *sb; + void *va; + vm_size_t map_size; + uint32_t i, max, num; + uint64_t base; + uint64_t total; + uint32_t freq; + int error; + + if (sc->timestamps_paddr == 0) + return (ENXIO); + + va = pmap_mapbios(sc->timestamps_paddr, + sizeof(struct cb_timestamp_table)); + if (va == NULL) + return (ENOMEM); + + tst = (struct cb_timestamp_table *)va; + max = tst->max_entries; + num = tst->num_entries; + freq = tst->tick_freq_mhz; + base = tst->base_time; + + if (max > 1024) + max = 1024; + if (num > max) + num = max; + + pmap_unmapbios(va, sizeof(struct cb_timestamp_table)); + + total = (uint64_t)sizeof(struct cb_timestamp_table) + + (uint64_t)num * sizeof(struct cb_timestamp_entry); + if (total > SIZE_MAX) + return (EINVAL); + map_size = (vm_size_t)total; + va = pmap_mapbios(sc->timestamps_paddr, map_size); + if (va == NULL) + return (ENOMEM); + + tst = (struct cb_timestamp_table *)va; + + sb = sbuf_new_for_sysctl(NULL, NULL, 0, req); + if (sb == NULL) { + pmap_unmapbios(va, map_size); + return (ENOMEM); + } + + sbuf_printf(sb, "\n%-4s %-40s %12s %12s\n", + "ID", "Stage", "Stamp (us)", "Delta (us)"); + sbuf_printf(sb, "---- ----------------------------------------" + " ------------ ------------\n"); + + for (i = 0; i < num; i++) { + uint32_t eid = tst->entries[i].entry_id; + int64_t stamp = tst->entries[i].entry_stamp; + int64_t us, delta_us; + const char *name; + + if (freq > 0) + us = (stamp - (int64_t)base) / (int64_t)freq; + else + us = stamp; + + if (i > 0 && freq > 0) { + int64_t prev = tst->entries[i - 1].entry_stamp; + delta_us = (stamp - prev) / (int64_t)freq; + } else { + delta_us = 0; + } + + name = ts_id_to_name(eid); + if (name != NULL) + sbuf_printf(sb, "%4u %-40s %12jd %12jd\n", + eid, name, + (intmax_t)us, (intmax_t)delta_us); + else + sbuf_printf(sb, "%4u %-40s %12jd %12jd\n", + eid, "(unknown)", + (intmax_t)us, (intmax_t)delta_us); + } + + error = sbuf_finish(sb); + sbuf_delete(sb); + pmap_unmapbios(va, map_size); + + return (error); +} + +int +coreboot_timestamps_register(struct coreboot_softc *sc, + struct sysctl_oid *parent) +{ + + SYSCTL_ADD_PROC(&sc->sysctl_ctx, + SYSCTL_CHILDREN(parent), OID_AUTO, "timestamps", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, coreboot_timestamps_sysctl, "A", + "Boot stage timing from coreboot timestamps"); + + return (0); +} diff --git a/sys/dev/coreboot/corebootio.h b/sys/dev/coreboot/corebootio.h new file mode 100644 index 000000000000..ab4224d8593d --- /dev/null +++ b/sys/dev/coreboot/corebootio.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * coreboot(4) userland-visible definitions. + * + * This header is safe to include from both kernel and userland code. + * It contains the /dev/cbmem ioctl interface, CBMEM ID constants, + * and timestamp structures needed by the cbmem(8) utility. + */ + +#ifndef _DEV_COREBOOT_COREBOOT_IOCTL_H_ +#define _DEV_COREBOOT_COREBOOT_IOCTL_H_ + +#include <sys/types.h> +#include <sys/ioccom.h> + +/* + * CBMEM ID constants (from coreboot's BSD-3-Clause cbmem_id.h). + * Used for human-readable CBMEM entry names in sysctl and ioctl output. + */ +#define CBMEM_ID_ACPI 0x41435049 +#define CBMEM_ID_ACPI_GNVS 0x474e5653 +#define CBMEM_ID_AFTER_CAR 0xc4787a93 +#define CBMEM_ID_CBTABLE 0x43425442 +#define CBMEM_ID_CBTABLE_FWD 0x43425443 +#define CBMEM_ID_CBFS_RO_MCACHE 0x524d5346 +#define CBMEM_ID_CBFS_RW_MCACHE 0x574d5346 +#define CBMEM_ID_CONSOLE 0x434f4e53 +#define CBMEM_ID_ELOG 0x454c4f47 +#define CBMEM_ID_FMAP 0x464d4150 +#define CBMEM_ID_FREESPACE 0x46524545 +#define CBMEM_ID_FSP_RESERVED_MEMORY 0x46535052 +#define CBMEM_ID_FSP_RUNTIME 0x52505346 +#define CBMEM_ID_FSPM_VERSION 0x56505346 +#define CBMEM_ID_IGD_OPREGION 0x4f444749 +#define CBMEM_ID_IMD_ROOT 0xff4017ff +#define CBMEM_ID_IMD_SMALL 0x53a11439 +#define CBMEM_ID_MEMINFO 0x494d454d +#define CBMEM_ID_MPTABLE 0x534d5054 +#define CBMEM_ID_MRCDATA 0x4d524344 +#define CBMEM_ID_PIRQ 0x49525154 +#define CBMEM_ID_POWER_STATE 0x50535454 +#define CBMEM_ID_RAM_OOPS 0x05430095 +#define CBMEM_ID_RAMSTAGE 0x9a357a9e +#define CBMEM_ID_REFCODE 0x04efc0de +#define CBMEM_ID_RESUME 0x5245534d +#define CBMEM_ID_ROMSTAGE_INFO 0x47545352 +#define CBMEM_ID_ROMSTAGE_RAM_STACK 0x90357ac4 +#define CBMEM_ID_ROOT 0xff4007ff +#define CBMEM_ID_SMBIOS 0x534d4254 +#define CBMEM_ID_SMM_COMBUFFER 0x53534d32 +#define CBMEM_ID_SMM_SAVE_SPACE 0x07e9acee +#define CBMEM_ID_TIMESTAMP 0x54494d45 +#define CBMEM_ID_TPM_CB_LOG 0x54435041 +#define CBMEM_ID_VBOOT_WORKBUF 0x78007343 +#define CBMEM_ID_VPD 0x56504420 + +#define CB_MAX_CBMEM_ENTRIES 64 + +/* + * /dev/cbmem ioctl interface + */ +struct cbmem_entry_info { + uint32_t id; + uint64_t address; + uint32_t size; + char name[16]; +}; + +struct cbmem_list { + uint32_t count; + struct cbmem_entry_info entries[CB_MAX_CBMEM_ENTRIES]; +}; + +struct cbmem_read_req { + uint32_t id; + uint64_t offset; + uint32_t size; + void *buffer; +}; + +#define CBMEM_IOC_LIST _IOR('C', 1, struct cbmem_list) +#define CBMEM_IOC_READ _IOWR('C', 2, struct cbmem_read_req) + +/* + * Timestamp structures - stored in a CBMEM region. + * Used by both the kernel driver and cbmem(8) utility. + */ +struct cb_timestamp_entry { + uint32_t entry_id; + int64_t entry_stamp; +} __packed; + +struct cb_timestamp_table { + uint64_t base_time; + uint16_t max_entries; + uint16_t tick_freq_mhz; + uint32_t num_entries; + struct cb_timestamp_entry entries[]; +} __packed; + +/* + * TPM CB log structures - stored in a CBMEM region. + * Used by both the kernel driver and cbmem(8) utility. + */ +struct tpm_cb_log_entry { + uint32_t pcr; + uint32_t event_type; + uint8_t digest[20]; /* SHA-1 */ + uint32_t data_len; +} __packed; + +struct tpm_cb_log_table { + uint16_t max_entries; + uint16_t num_entries; + uint32_t entry_size; +} __packed; + +#endif /* _DEV_COREBOOT_COREBOOT_IOCTL_H_ */ diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 972441264fb9..df4cb4b63ded 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -87,6 +87,7 @@ SUBDIR= \ ${_cfi} \ ${_chromebook_platform} \ ${_ciss} \ + ${_coreboot} \ ${_coretemp} \ ${_cpsw} \ ${_cpuctl} \ @@ -812,6 +813,7 @@ _bytgpio= bytgpio _chvgpio= chvgpio _ciss= ciss _chromebook_platform= chromebook_platform +_coreboot= coreboot _coretemp= coretemp .if ${MK_SOURCELESS_HOST} != "no" && empty(KCSAN_ENABLED) _hpt27xx= hpt27xx diff --git a/sys/modules/coreboot/Makefile b/sys/modules/coreboot/Makefile new file mode 100644 index 000000000000..756d93208182 --- /dev/null +++ b/sys/modules/coreboot/Makefile @@ -0,0 +1,7 @@ +.PATH: ${SRCTOP}/sys/dev/coreboot + +KMOD= coreboot +SRCS= coreboot.c coreboot_console.c coreboot_cbmem.c coreboot_timestamps.c +SRCS+= bus_if.h device_if.h + +.include <bsd.kmod.mk> |
