diff options
author | Tom Rini <trini@konsulko.com> | 2022-05-03 18:33:46 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2022-05-03 18:33:46 -0400 |
commit | 46eb29201c17e1273d1cabeafde378b0759c0d7d (patch) | |
tree | 8c0241b1adb7345fe8df34da06ff1dac83f74e4f /drivers/pci/pci-uclass.c | |
parent | f8e7670f8b2a5ba8f25682eee56039fa5f0a20ca (diff) | |
parent | 3b920186752518fe669cb337c433a69ee021bc30 (diff) |
Merge branch '2022-05-03-virtio-pci-add-and-fix-consistency-checks'
To quote the author:
The virtio PCI drivers forgo a number of consistency checks,
particularly around pointer validation and bounds checking. This series
focuses on the modern driver to add those checks.
The start of the series adds and fixes some basic bounds checks. Later
patches ensure PCI addresses fall within the expected regions rather
than any arbitrary address. This is acheived by introducing range
parameters to a few of the dm_pci_* functions that allow the ranges to
be checked.
The series also adds a few new configs to allow parts of virtio and PCI
to be disabled where the features may be unused and the current
implementations don't have the needed consistencty checks.
Diffstat (limited to 'drivers/pci/pci-uclass.c')
-rw-r--r-- | drivers/pci/pci-uclass.c | 185 |
1 files changed, 85 insertions, 100 deletions
diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c index 33dda00002e..970ee1adf1b 100644 --- a/drivers/pci/pci-uclass.c +++ b/drivers/pci/pci-uclass.c @@ -645,7 +645,11 @@ int dm_pci_hose_probe_bus(struct udevice *bus) return log_msg_ret("probe", -EINVAL); } - ea_pos = dm_pci_find_capability(bus, PCI_CAP_ID_EA); + if (IS_ENABLED(CONFIG_PCI_ENHANCED_ALLOCATION)) + ea_pos = dm_pci_find_capability(bus, PCI_CAP_ID_EA); + else + ea_pos = 0; + if (ea_pos) { dm_pci_read_config8(bus, ea_pos + sizeof(u32) + sizeof(u8), ®); @@ -1013,7 +1017,22 @@ static void decode_regions(struct pci_controller *hose, ofnode parent_node, if (!IS_ENABLED(CONFIG_SYS_PCI_64BIT) && type == PCI_REGION_MEM && upper_32_bits(pci_addr)) { - debug(" - beyond the 32-bit boundary, ignoring\n"); + debug(" - pci_addr beyond the 32-bit boundary, ignoring\n"); + continue; + } + + if (!IS_ENABLED(CONFIG_PHYS_64BIT) && upper_32_bits(addr)) { + debug(" - addr beyond the 32-bit boundary, ignoring\n"); + continue; + } + + if (~((pci_addr_t)0) - pci_addr < size) { + debug(" - PCI range exceeds max address, ignoring\n"); + continue; + } + + if (~((phys_addr_t)0) - addr < size) { + debug(" - phys range exceeds max address, ignoring\n"); continue; } @@ -1375,131 +1394,84 @@ void dm_pci_write_bar32(struct udevice *dev, int barnum, u32 addr) dm_pci_write_config32(dev, bar, addr); } -static int _dm_pci_bus_to_phys(struct udevice *ctlr, - pci_addr_t bus_addr, unsigned long flags, - unsigned long skip_mask, phys_addr_t *pa) +phys_addr_t dm_pci_bus_to_phys(struct udevice *dev, pci_addr_t bus_addr, + size_t len, unsigned long mask, + unsigned long flags) { - struct pci_controller *hose = dev_get_uclass_priv(ctlr); + struct udevice *ctlr; + struct pci_controller *hose; struct pci_region *res; + pci_addr_t offset; int i; - if (hose->region_count == 0) { - *pa = bus_addr; - return 0; - } + /* The root controller has the region information */ + ctlr = pci_get_controller(dev); + hose = dev_get_uclass_priv(ctlr); + + if (hose->region_count == 0) + return bus_addr; for (i = 0; i < hose->region_count; i++) { res = &hose->regions[i]; - if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0) + if ((res->flags & mask) != flags) continue; - if (res->flags & skip_mask) + if (bus_addr < res->bus_start) continue; - if (bus_addr >= res->bus_start && - (bus_addr - res->bus_start) < res->size) { - *pa = (bus_addr - res->bus_start + res->phys_start); - return 0; - } - } - - return 1; -} - -phys_addr_t dm_pci_bus_to_phys(struct udevice *dev, pci_addr_t bus_addr, - unsigned long flags) -{ - phys_addr_t phys_addr = 0; - struct udevice *ctlr; - int ret; + offset = bus_addr - res->bus_start; + if (offset >= res->size) + continue; - /* The root controller has the region information */ - ctlr = pci_get_controller(dev); + if (len > res->size - offset) + continue; - /* - * if PCI_REGION_MEM is set we do a two pass search with preference - * on matches that don't have PCI_REGION_SYS_MEMORY set - */ - if ((flags & PCI_REGION_TYPE) == PCI_REGION_MEM) { - ret = _dm_pci_bus_to_phys(ctlr, bus_addr, - flags, PCI_REGION_SYS_MEMORY, - &phys_addr); - if (!ret) - return phys_addr; + return res->phys_start + offset; } - ret = _dm_pci_bus_to_phys(ctlr, bus_addr, flags, 0, &phys_addr); - - if (ret) - puts("pci_hose_bus_to_phys: invalid physical address\n"); - - return phys_addr; + puts("pci_hose_bus_to_phys: invalid physical address\n"); + return 0; } -static int _dm_pci_phys_to_bus(struct udevice *dev, phys_addr_t phys_addr, - unsigned long flags, unsigned long skip_mask, - pci_addr_t *ba) +pci_addr_t dm_pci_phys_to_bus(struct udevice *dev, phys_addr_t phys_addr, + size_t len, unsigned long mask, + unsigned long flags) { - struct pci_region *res; struct udevice *ctlr; - pci_addr_t bus_addr; - int i; struct pci_controller *hose; + struct pci_region *res; + phys_addr_t offset; + int i; /* The root controller has the region information */ ctlr = pci_get_controller(dev); hose = dev_get_uclass_priv(ctlr); - if (hose->region_count == 0) { - *ba = phys_addr; - return 0; - } + if (hose->region_count == 0) + return phys_addr; for (i = 0; i < hose->region_count; i++) { res = &hose->regions[i]; - if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0) + if ((res->flags & mask) != flags) continue; - if (res->flags & skip_mask) + if (phys_addr < res->phys_start) continue; - bus_addr = phys_addr - res->phys_start + res->bus_start; - - if (bus_addr >= res->bus_start && - (bus_addr - res->bus_start) < res->size) { - *ba = bus_addr; - return 0; - } - } - - return 1; -} + offset = phys_addr - res->phys_start; + if (offset >= res->size) + continue; -pci_addr_t dm_pci_phys_to_bus(struct udevice *dev, phys_addr_t phys_addr, - unsigned long flags) -{ - pci_addr_t bus_addr = 0; - int ret; + if (len > res->size - offset) + continue; - /* - * if PCI_REGION_MEM is set we do a two pass search with preference - * on matches that don't have PCI_REGION_SYS_MEMORY set - */ - if ((flags & PCI_REGION_TYPE) == PCI_REGION_MEM) { - ret = _dm_pci_phys_to_bus(dev, phys_addr, flags, - PCI_REGION_SYS_MEMORY, &bus_addr); - if (!ret) - return bus_addr; + return res->bus_start + offset; } - ret = _dm_pci_phys_to_bus(dev, phys_addr, flags, 0, &bus_addr); - - if (ret) - puts("pci_hose_phys_to_bus: invalid physical address\n"); - - return bus_addr; + puts("pci_hose_phys_to_bus: invalid physical address\n"); + return 0; } static phys_addr_t dm_pci_map_ea_virt(struct udevice *dev, int ea_off, @@ -1533,8 +1505,9 @@ static phys_addr_t dm_pci_map_ea_virt(struct udevice *dev, int ea_off, return addr; } -static void *dm_pci_map_ea_bar(struct udevice *dev, int bar, int flags, - int ea_off, struct pci_child_plat *pdata) +static void *dm_pci_map_ea_bar(struct udevice *dev, int bar, size_t offset, + size_t len, int ea_off, + struct pci_child_plat *pdata) { int ea_cnt, i, entry_size; int bar_id = (bar - PCI_BASE_ADDRESS_0) >> 2; @@ -1576,14 +1549,18 @@ static void *dm_pci_map_ea_bar(struct udevice *dev, int bar, int flags, if (IS_ENABLED(CONFIG_PCI_SRIOV)) addr += dm_pci_map_ea_virt(dev, ea_off, pdata); + if (~((phys_addr_t)0) - addr < offset) + return NULL; + /* size ignored for now */ - return map_physmem(addr, 0, flags); + return map_physmem(addr + offset, len, MAP_NOCACHE); } return 0; } -void *dm_pci_map_bar(struct udevice *dev, int bar, int flags) +void *dm_pci_map_bar(struct udevice *dev, int bar, size_t offset, size_t len, + unsigned long mask, unsigned long flags) { struct pci_child_plat *pdata = dev_get_parent_plat(dev); struct udevice *udev = dev; @@ -1606,21 +1583,29 @@ void *dm_pci_map_bar(struct udevice *dev, int bar, int flags) * Incase of virtual functions, pdata will help read VF BEI * and EA entry size. */ - ea_off = dm_pci_find_capability(udev, PCI_CAP_ID_EA); + if (IS_ENABLED(CONFIG_PCI_ENHANCED_ALLOCATION)) + ea_off = dm_pci_find_capability(udev, PCI_CAP_ID_EA); + else + ea_off = 0; + if (ea_off) - return dm_pci_map_ea_bar(udev, bar, flags, ea_off, pdata); + return dm_pci_map_ea_bar(udev, bar, offset, len, ea_off, pdata); /* read BAR address */ dm_pci_read_config32(udev, bar, &bar_response); pci_bus_addr = (pci_addr_t)(bar_response & ~0xf); + if (~((pci_addr_t)0) - pci_bus_addr < offset) + return NULL; + /* - * Pass "0" as the length argument to pci_bus_to_virt. The arg - * isn't actually used on any platform because U-Boot assumes a static - * linear mapping. In the future, this could read the BAR size - * and pass that as the size if needed. + * Forward the length argument to dm_pci_bus_to_virt. The length will + * be used to check that the entire address range has been declared as + * a PCI range, but a better check would be to probe for the size of + * the bar and prevent overflow more locally. */ - return dm_pci_bus_to_virt(udev, pci_bus_addr, flags, 0, MAP_NOCACHE); + return dm_pci_bus_to_virt(udev, pci_bus_addr + offset, len, mask, flags, + MAP_NOCACHE); } static int _dm_pci_find_next_capability(struct udevice *dev, u8 pos, int cap) |