#include <linux/align.h>
#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include "pci.h"
#define PCI_ROM_HEADER_SIZE 0x1A
#define PCI_ROM_POINTER_TO_DATA_STRUCT 0x18
#define PCI_ROM_LAST_IMAGE_INDICATOR 0x15
#define PCI_ROM_LAST_IMAGE_INDICATOR_BIT BIT(7)
#define PCI_ROM_IMAGE_LEN 0x10
#define PCI_ROM_IMAGE_SECTOR_SIZE SZ_512
#define PCI_ROM_IMAGE_SIGNATURE 0xAA55
#define PCI_ROM_DATA_STRUCT_SIGNATURE 0x52494350
#define PCI_ROM_DATA_STRUCT_LEN 0x0A
#define PCI_ROM_DATA_STRUCT_MIN_LEN 0x18
int pci_enable_rom(struct pci_dev *pdev)
{
struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
struct pci_bus_region region;
u32 rom_addr;
if (!res->flags)
return -1;
if (res->flags & IORESOURCE_ROM_SHADOW)
return 0;
pcibios_resource_to_bus(pdev->bus, ®ion, res);
pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr);
rom_addr &= ~PCI_ROM_ADDRESS_MASK;
rom_addr |= region.start | PCI_ROM_ADDRESS_ENABLE;
pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr);
return 0;
}
EXPORT_SYMBOL_GPL(pci_enable_rom);
void pci_disable_rom(struct pci_dev *pdev)
{
struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
u32 rom_addr;
if (res->flags & IORESOURCE_ROM_SHADOW)
return;
pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr);
rom_addr &= ~PCI_ROM_ADDRESS_ENABLE;
pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr);
}
EXPORT_SYMBOL_GPL(pci_disable_rom);
static bool pci_rom_header_valid(struct pci_dev *pdev, void __iomem *image,
void __iomem *rom, size_t size,
bool expect_valid)
{
unsigned long rom_end = (unsigned long)rom + size - 1;
unsigned long header_end;
u16 signature;
if (!IS_ALIGNED((unsigned long)image, PCI_ROM_IMAGE_SECTOR_SIZE))
return false;
if (check_add_overflow((unsigned long)image, PCI_ROM_HEADER_SIZE - 1,
&header_end))
return false;
if (image < rom || header_end > rom_end)
return false;
signature = readw(image);
if (signature != PCI_ROM_IMAGE_SIGNATURE) {
if (expect_valid) {
pci_info(pdev, "Invalid PCI ROM header signature: expecting %#06x, got %#06x\n",
PCI_ROM_IMAGE_SIGNATURE, signature);
} else {
pci_info(pdev, "No more images in PCI ROM\n");
}
return false;
}
return true;
}
static bool pci_rom_data_struct_valid(struct pci_dev *pdev, void __iomem *pds,
void __iomem *rom, size_t size)
{
unsigned long rom_end = (unsigned long)rom + size - 1;
unsigned long end;
u32 signature;
u16 data_len;
if (!IS_ALIGNED((unsigned long)pds, 4))
return false;
if (check_add_overflow((unsigned long)pds, PCI_ROM_DATA_STRUCT_LEN + 1,
&end))
return false;
if (pds < rom || end > rom_end)
return false;
signature = readl(pds);
if (signature != PCI_ROM_DATA_STRUCT_SIGNATURE) {
pci_info(pdev, "Invalid PCI ROM data signature: expecting %#010x, got %#010x\n",
PCI_ROM_DATA_STRUCT_SIGNATURE, signature);
return false;
}
data_len = readw(pds + PCI_ROM_DATA_STRUCT_LEN);
if (data_len < PCI_ROM_DATA_STRUCT_MIN_LEN || data_len == U16_MAX)
return false;
if (check_add_overflow((unsigned long)pds, data_len - 1, &end))
return false;
if (end > rom_end)
return false;
return true;
}
static size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom,
size_t size)
{
void __iomem *image;
unsigned int length;
bool last_image;
image = rom;
do {
void __iomem *pds;
if (!pci_rom_header_valid(pdev, image, rom, size, true))
break;
pds = image + readw(image + PCI_ROM_POINTER_TO_DATA_STRUCT);
if (!pci_rom_data_struct_valid(pdev, pds, rom, size))
break;
last_image = readb(pds + PCI_ROM_LAST_IMAGE_INDICATOR) &
PCI_ROM_LAST_IMAGE_INDICATOR_BIT;
length = readw(pds + PCI_ROM_IMAGE_LEN);
image += length * PCI_ROM_IMAGE_SECTOR_SIZE;
if (!last_image &&
!pci_rom_header_valid(pdev, image, rom, size, false))
break;
} while (length && !last_image);
return min((size_t)(image - rom), size);
}
void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size)
{
struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
loff_t start;
void __iomem *rom;
if (res->parent == NULL && pci_assign_resource(pdev, PCI_ROM_RESOURCE))
return NULL;
start = pci_resource_start(pdev, PCI_ROM_RESOURCE);
*size = pci_resource_len(pdev, PCI_ROM_RESOURCE);
if (*size == 0)
return NULL;
if (pci_enable_rom(pdev))
return NULL;
rom = ioremap(start, *size);
if (!rom)
goto err_ioremap;
*size = pci_get_rom_size(pdev, rom, *size);
if (!*size)
goto invalid_rom;
return rom;
invalid_rom:
iounmap(rom);
err_ioremap:
if (!(res->flags & IORESOURCE_ROM_ENABLE))
pci_disable_rom(pdev);
return NULL;
}
EXPORT_SYMBOL(pci_map_rom);
void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom)
{
struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
iounmap(rom);
if (!(res->flags & IORESOURCE_ROM_ENABLE))
pci_disable_rom(pdev);
}
EXPORT_SYMBOL(pci_unmap_rom);