// SPDX-License-Identifier: GPL-2.0 /* Copyright 2023 AVM GmbH */ #include #include #include "efistub.h" #include #define PCI_DEVICE_ID_LPC_PUMA7 0x2b9c #define PCI_BUS_OF_INTEL_SPI 0 #define PCI_DEVICE_NR_OF_INTEL_SPI 31 /* Copied from the Intel SPI NOR driver (intel-spi) */ #define BYT_FREG_NUM 5 #define FREG_NUM BYT_FREG_NUM #define FREG(n) (0x54 + ((n) * 4)) #define FREG_BASE_MASK 0x3fff #define FREG_SECTOR_SHIFT 12 #define FREG_LIMIT_SHIFT 16 #define FREG_LIMIT_MASK (0x03fff << FREG_LIMIT_SHIFT) /* Copied from the intel-ce2700 driver */ #define CE2700_PCU_SPI_BAR_BASE 0x54 struct pci_id { __le16 vendor; __le16 device; } __packed; struct pci_bdf { unsigned long seg; unsigned long bus; unsigned long dev; unsigned long fn; }; struct area { unsigned long offset; unsigned long size; }; struct data { u8 *data; size_t len; }; static bool intel_spi_pci_bdf_matches(const struct pci_bdf *bdf) { return bdf->bus == PCI_BUS_OF_INTEL_SPI && bdf->dev == PCI_DEVICE_NR_OF_INTEL_SPI && bdf->fn == 0; } static bool intel_spi_pci_id_matches(const struct pci_id *pci_id) { return pci_id->vendor == PCI_VENDOR_ID_INTEL && pci_id->device == PCI_DEVICE_ID_LPC_PUMA7; } static efi_status_t intel_spi_get_iomem_base(void *iface, void *arg) { efi_pci_io_protocol_t *pci = iface; void __iomem **intel_spi_base = arg; efi_status_t status; struct pci_id pci_id; struct pci_bdf bdf; u32 spi_base; status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 0, 2, &pci_id); if (status != EFI_SUCCESS) { efi_err("Unable to read PCI ID: %lu\n", status); return status; } status = efi_call_proto(pci, get_location, &bdf.seg, &bdf.bus, &bdf.dev, &bdf.fn); if (status != EFI_SUCCESS) { efi_err("Unable to get location of PCI device: %lu\n", status); return status; } if (!intel_spi_pci_id_matches(&pci_id)) return EFI_SUCCESS; if (!intel_spi_pci_bdf_matches(&bdf)) efi_warn("Found Intel SPI at unexpected location (bus=%lu, device=%lu)\n", bdf.bus, bdf.dev); status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint32, CE2700_PCU_SPI_BAR_BASE, 1, &spi_base); if (status != EFI_SUCCESS) { efi_err("Reading Intel SPI iomem base failed: %lu\n", status); return status; } spi_base &= PCI_BASE_ADDRESS_MEM_MASK; efi_debug("Intel SPI iomem found at 0x%08x\n", spi_base); /* * Note that EFI memory is identity-mapped, so we can use the physical * address right away. */ *intel_spi_base = (void *)(unsigned long)spi_base; return EFI_AVM_LOOP_STOP; } #define INTEL_SPI_ERASE_SIZ SZ_4K /** * intel_spi_execute_read() - Execute a read on an SPI flash device * @spi: SPI protocol opened by handle_protocol * @offset: starting offset for the read * @len: number of bytes to read * @buf: buffer to read into */ static efi_status_t intel_spi_execute_read(efi_puma7_spi_flash_protocol_t *spi, unsigned long offset, u32 len, void *buf) { /* * This is a proprietary protocol that is puma7 specific, for details * consult the puma7 UEFI sources. */ return efi_call_proto(spi, execute, 1, 0, 1, 1, 0, offset, len, buf, 0); } /** * intel_spi_find_pdr() - Get the SPI offset of the PDR image directory * @spi: Puma SPI protocol * @intel_spi_base: Starting offset for the read * @pdr: PDR area found */ static efi_status_t intel_spi_find_pdr(efi_puma7_spi_flash_protocol_t *spi, void __iomem *intel_spi_base, struct area *pdr) { unsigned long base, limit, offset, len; efi_status_t status; u32 region; u32 probe; int i; for (i = 0; i < FREG_NUM; i++) { region = readl(intel_spi_base + FREG(i)); base = region & FREG_BASE_MASK; limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT; efi_debug("Probing Intel SPI region %i: base=0x%08x limit=0x%08x\n", i, base, limit); if (base > limit || (i > 0 && limit == 0)) continue; offset = base << FREG_SECTOR_SHIFT; len = (limit + 1 - base) << FREG_SECTOR_SHIFT; status = intel_spi_execute_read(spi, offset, sizeof(probe), &probe); if (status != EFI_SUCCESS) { efi_err("SPI read failed at offset 0x%08lx: %lu\n", offset, status); return status; } if (probe == PDR_IMAGE_ID_IMG_DIR) goto found; } efi_err("No PDR image directory found in SPI flash\n"); return EFI_NOT_FOUND; found: efi_debug("PDR image directory found in flash region %d: offset 0x%08lx, length 0x%lx\n", i, offset, len); pdr->size = len; pdr->offset = offset; return EFI_SUCCESS; } /** * intel_spi_find_pdr_entry() - Find the data area for a given PDR entry * @spi: Puma SPI protocol * @id: The PDR entry ID to look for * @entry_data: The flash area of the entry, if found */ static efi_status_t intel_spi_find_pdr_entry(efi_puma7_spi_flash_protocol_t *spi, const struct area *pdr, u32 id, struct area *entry_data) { struct pdr_image_dir entry; efi_status_t status; for (int i = 0; i < PDR_IMAGE_DIR_MAX_CNT; ++i) { unsigned long offset = pdr->offset + i * sizeof(entry); status = intel_spi_execute_read(spi, offset, sizeof(entry), &entry); if (status != EFI_SUCCESS) { efi_err("SPI read failed at offset 0x%08lx: %lu\n", offset, status); return status; } if (entry.id == PDR_IMAGE_ID_END) break; if (entry.id == id) goto found; } efi_warn("PDR entry type %d not found in PDR image directory\n", id, pdr->offset); return EFI_NOT_FOUND; found: if (entry.offset + entry.size > pdr->size) { efi_err("PDR entry size mismatch, offset=0x%08x + size=0x%x > pdr_size=0x%lx\n", entry.offset, entry.size, pdr->size); } entry_data->offset = pdr->offset + entry.offset; entry_data->size = entry.size; efi_debug("PDR directory entry type %u found at 0x%08x with length 0x%lx\n", id, entry_data->offset, entry_data->size); return EFI_SUCCESS; } /** * intel_spi_read_avm_calib_data() - Read an AVM calibration data entry * @spi: SPI protocol opened by handle_protocol * @oem0: OEM0 PDR directory entry area * @type: The desired calibration data type * @data: Receives the calibration data * * This function allocates memory, it's up to the caller to manage its * lifetime. */ static efi_status_t intel_spi_read_avm_calib_data(efi_puma7_spi_flash_protocol_t *spi, const struct area *oem0, enum avm_prom_config_type type, struct data *data) { unsigned long offset, end; efi_status_t status; void *_data; u16 len; offset = oem0->offset; end = offset + oem0->size; while (offset < end) { struct avm_prom_config_hdr hdr; status = intel_spi_execute_read(spi, offset, sizeof(hdr), &hdr); if (status != EFI_SUCCESS) { efi_err("SPI read failed at offset 0x%08lx: %lu\n", offset, status); return status; } if (hdr.type == AVM_PROM_CONFIG_NONE) { offset = ALIGN(offset+1, INTEL_SPI_ERASE_SIZ); continue; } if (hdr.version != 2) { efi_err("Invalid calibration data at 0x%08lx\n", offset); return EFI_NOT_FOUND; } offset += sizeof(hdr); len = be16_to_cpu(hdr.len); if (hdr.type == type) goto found; offset += len; } efi_info("No device-tree overlay found in OEM0 region\n"); return EFI_NOT_FOUND; found: efi_info("Found device-tree overlay at offset 0x%08lx (0x%x bytes)\n", offset, len); status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, len, &_data); if (status != EFI_SUCCESS) { efi_err("Failed to allocate %u bytes for dtb overlay\n", len); return status; } status = intel_spi_execute_read(spi, offset, len, _data); if (status != EFI_SUCCESS) { efi_err("SPI read failed at offset 0x%08lx: %lu\n", offset, status); efi_bs_call(free_pool, &_data); return status; } efi_debug("Device-tree overlay loaded into memory at 0x%p\n", _data); data->data = _data; data->len = len; return EFI_SUCCESS; } static char *get_cmd_line_ptr(struct boot_params *boot_params) { unsigned long cmd_line_ptr = boot_params->hdr.cmd_line_ptr; cmd_line_ptr |= (u64)boot_params->ext_cmd_line_ptr << 32; return (char *)cmd_line_ptr; } /** * avm_cmd_line_add_dtbo() - Add option for DTB overlay to kernel cmdline * @boot_params: Boot parameters used to boot the kernel. * New kernel cmdline will be set here * @dtbo: DTB overlay buffer */ static efi_status_t avm_cmd_line_add_dtbo(struct boot_params *boot_params, const struct data *dtbo) { struct setup_header *hdr = &boot_params->hdr; u8 cmdline_append[39]; int append_len; efi_status_t status; char *cmdline, *new_cmdline; size_t new_cmdline_size; /* cmdline has already been converted to ascii in efi_pe_entry() */ cmdline = get_cmd_line_ptr(boot_params); append_len = snprintf(cmdline_append, sizeof(cmdline_append), " avm,fdt-overlay=0x%p,0x%08lx", dtbo->data, dtbo->len); if (append_len >= sizeof(cmdline_append)) { efi_err("Error formatting new kernel cmdline for dtbo argument\n"); return EFI_BAD_BUFFER_SIZE; } new_cmdline_size = strlen(cmdline) + append_len + 1; status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, new_cmdline_size, (void **)&new_cmdline); if (status != EFI_SUCCESS) { efi_err("Failed to allocate %lu bytes for cmdline\n", new_cmdline_size); return status; } snprintf(new_cmdline, new_cmdline_size, "%s%s", cmdline, cmdline_append); efi_set_u64_split((unsigned long)new_cmdline, &hdr->cmd_line_ptr, &boot_params->ext_cmd_line_ptr); hdr->cmdline_size = new_cmdline_size; /* The old cmdline has been allocated in efi_convert_cmdline() */ efi_bs_call(free_pool, cmdline); return EFI_SUCCESS; } /** * avm_load_dtb_overlay() - Load DTB overlay from SPI NOR and add it to kernel cmdline * @boot_params: Boot parameters used to boot the kernel. * New kernel cmdline will be set here. */ efi_status_t avm_load_dtb_overlay(struct boot_params *boot_params) { efi_guid_t pci_io_guid = EFI_PCI_IO_PROTOCOL_GUID; efi_guid_t spi_guid = EFI_PUMA7_SPI_FLASH_PROTOCOL_GUID; efi_puma7_spi_flash_protocol_t *spi_protocol; void __iomem *intel_spi_base; struct area pdr, oem0; efi_status_t status; struct data dtbo; efi_info("Scanning calibration data flash region for a device-tree overlay\n"); status = avm_for_each_protocol_call(&pci_io_guid, intel_spi_get_iomem_base, &intel_spi_base); if (status != EFI_AVM_LOOP_STOP) { efi_err("Failed to find Intel SPI iomem base: %lu\n", status); return status; } status = efi_bs_call(locate_protocol, &spi_guid, NULL, (void **)&spi_protocol); if (status != EFI_SUCCESS) { efi_err("Failed to locate Intel SPI flash protocol\n"); return status; } status = intel_spi_find_pdr(spi_protocol, intel_spi_base, &pdr); if (status) return status; status = intel_spi_find_pdr_entry(spi_protocol, &pdr, PDR_IMAGE_ID_OEMD0, &oem0); if (status) return status; status = intel_spi_read_avm_calib_data(spi_protocol, &oem0, DTB_OVERLAY, &dtbo); if (status) return status; return avm_cmd_line_add_dtbo(boot_params, &dtbo); }