Currently we assume that the addresses on the PCI bus are mapped 1:1 to the host CPU. This is not always true. Specifically on Rockchip RK3568 and RK3588 we have ranges properties in the device tree which introduce a different mapping. We already worked around this by changing the ranges property to a 1:1 mapping in b10ee53 ("ARM: rockchip: rock-5a: Disable non working devices"). To get rid of the workaround this patch introduces support for non 1:1 mappings. We do this by porting the bare minimum from Linux drivers/pci/host-bridge.c over to barebox. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- drivers/pci/Makefile | 2 +- drivers/pci/bus.c | 23 ++++++++++++ drivers/pci/host-bridge.c | 76 +++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.c | 15 ++++++-- include/linux/pci.h | 20 +++++++++++ 5 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 drivers/pci/host-bridge.c diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 9249bffecb..c222f77b3b 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -2,7 +2,7 @@ # # Makefile for the PCI bus specific drivers. # -obj-y += pci.o bus.o pci_iomap.o +obj-y += pci.o bus.o pci_iomap.o host-bridge.o ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index b6eab56d87..37bdc9a22f 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -3,6 +3,29 @@ #include <init.h> #include <driver.h> #include <linux/pci.h> +#include <linux/resource_ext.h> + +void pci_add_resource_offset(struct list_head *resources, struct resource *res, + resource_size_t offset) +{ + struct resource_entry *entry; + + entry = resource_list_create_entry(res, 0); + if (!entry) { + pr_err("PCI: can't add host bridge window %pR\n", res); + return; + } + entry->offset = offset; + + resource_list_add_tail(entry, resources); +} +EXPORT_SYMBOL(pci_add_resource_offset); + +void pci_add_resource(struct list_head *resources, struct resource *res) +{ + pci_add_resource_offset(resources, res, 0); +} +EXPORT_SYMBOL(pci_add_resource); /** * pci_match_one_device - Tell if a PCI device structure has a matching diff --git a/drivers/pci/host-bridge.c b/drivers/pci/host-bridge.c new file mode 100644 index 0000000000..4460ad1971 --- /dev/null +++ b/drivers/pci/host-bridge.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Host bridge related code + */ + +#include <common.h> +#include <linux/sizes.h> +#include <linux/pci.h> + +static struct pci_bus *find_pci_root_bus(struct pci_bus *bus) +{ + while (bus->parent) + bus = bus->parent; + + return bus; +} + +struct pci_controller *pci_find_host_bridge(struct pci_bus *bus) +{ + struct pci_bus *root_bus = find_pci_root_bus(bus); + + return root_bus->host; +} +EXPORT_SYMBOL_GPL(pci_find_host_bridge); + +void pcibios_resource_to_bus(struct pci_bus *bus, struct pci_bus_region *region, + struct resource *res) +{ + struct pci_controller *bridge = pci_find_host_bridge(bus); + struct resource_entry *window; + resource_size_t offset = 0; + + resource_list_for_each_entry(window, &bridge->windows) { + if (resource_contains(window->res, res)) { + offset = window->offset; + break; + } + } + + region->start = res->start - offset; + region->end = res->end - offset; +} +EXPORT_SYMBOL(pcibios_resource_to_bus); + +static bool region_contains(struct pci_bus_region *region1, + struct pci_bus_region *region2) +{ + return region1->start <= region2->start && region1->end >= region2->end; +} + +void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res, + struct pci_bus_region *region) +{ + struct pci_controller *bridge = pci_find_host_bridge(bus); + struct resource_entry *window; + resource_size_t offset = 0; + + resource_list_for_each_entry(window, &bridge->windows) { + struct pci_bus_region bus_region; + + if (resource_type(res) != resource_type(window->res)) + continue; + + bus_region.start = window->res->start - window->offset; + bus_region.end = window->res->end - window->offset; + + if (region_contains(&bus_region, region)) { + offset = window->offset; + break; + } + } + + res->start = region->start + offset; + res->end = region->end + offset; +} +EXPORT_SYMBOL(pcibios_bus_to_resource); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 67a085d34e..5113abe0fc 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -44,6 +44,7 @@ static void pci_bus_register_devices(struct pci_bus *bus) void pci_controller_init(struct pci_controller *hose) { + INIT_LIST_HEAD(&hose->windows); } void register_pci_controller(struct pci_controller *hose) @@ -401,6 +402,9 @@ static void setup_device(struct pci_dev *dev, int max_bar) size); if (pcibios_assign_all_busses()) { + struct resource res; + struct pci_bus_region region; + if (ALIGN(*last_addr, size) + size > dev->bus->resource[busres]->end) { pr_debug("BAR does not fit within bus %s res\n", kind); @@ -408,11 +412,18 @@ static void setup_device(struct pci_dev *dev, int max_bar) } *last_addr = ALIGN(*last_addr, size); + + res.flags = flags; + res.start = *last_addr; + res.end = res.start + size - 1; + + pcibios_resource_to_bus(dev->bus, ®ion, &res); + pci_write_config_dword(dev, pci_base_address_0, - lower_32_bits(*last_addr)); + lower_32_bits(region.start)); if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) pci_write_config_dword(dev, pci_base_address_1, - upper_32_bits(*last_addr)); + upper_32_bits(region.start)); start = *last_addr; *last_addr += size; } else { diff --git a/include/linux/pci.h b/include/linux/pci.h index 2fea9ba6c7..73117afa08 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -25,6 +25,7 @@ #include <linux/ioport.h> #include <linux/list.h> #include <linux/compiler.h> +#include <linux/resource_ext.h> #include <driver.h> #include <errno.h> #include <io.h> @@ -185,6 +186,8 @@ struct pci_controller { unsigned long io_offset; unsigned long io_map_base; + struct list_head windows; /* resource_entry */ + unsigned int index; /* Optional access method for writing the bus number */ @@ -395,4 +398,21 @@ static inline const struct pci_device_id *pci_match_id(const struct pci_device_i { return NULL; } #endif +/* drivers/pci/bus.c */ +void pci_add_resource_offset(struct list_head *resources, struct resource *res, + resource_size_t offset); +void pci_add_resource(struct list_head *resources, struct resource *res); + +struct pci_controller *pci_find_host_bridge(struct pci_bus *bus); + +struct pci_bus_region { + u64 start; + u64 end; +}; + +void pcibios_resource_to_bus(struct pci_bus *bus, struct pci_bus_region *region, + struct resource *res); +void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res, + struct pci_bus_region *region); + #endif /* LINUX_PCI_H */ -- 2.39.2