| /* |
| * Copyright (c) 2021 BayLibre, SAS |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(pcie_ecam, LOG_LEVEL_ERR); |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/pcie/pcie.h> |
| #include <zephyr/drivers/pcie/controller.h> |
| #ifdef CONFIG_GIC_V3_ITS |
| #include <zephyr/drivers/interrupt_controller/gicv3_its.h> |
| #endif |
| |
| #define DT_DRV_COMPAT pci_host_ecam_generic |
| |
| /* |
| * PCIe Controllers Regions |
| * |
| * TOFIX: |
| * - handle prefetchable regions |
| */ |
| enum pcie_region_type { |
| PCIE_REGION_IO = 0, |
| PCIE_REGION_MEM, |
| PCIE_REGION_MEM64, |
| PCIE_REGION_MAX, |
| }; |
| |
| struct pcie_ecam_data { |
| uintptr_t cfg_phys_addr; |
| mm_reg_t cfg_addr; |
| size_t cfg_size; |
| struct { |
| uintptr_t phys_start; |
| uintptr_t bus_start; |
| size_t size; |
| size_t allocation_offset; |
| } regions[PCIE_REGION_MAX]; |
| }; |
| |
| static int pcie_ecam_init(const struct device *dev) |
| { |
| const struct pcie_ctrl_config *cfg = dev->config; |
| struct pcie_ecam_data *data = dev->data; |
| int i; |
| |
| /* |
| * Flags defined in the PCI Bus Binding to IEEE Std 1275-1994 : |
| * Bit# 33222222 22221111 11111100 00000000 |
| * 10987654 32109876 54321098 76543210 |
| * |
| * phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr |
| * phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh |
| * phys.lo cell: llllllll llllllll llllllll llllllll |
| * |
| * where: |
| * |
| * n is 0 if the address is relocatable, 1 otherwise |
| * p is 1 if the addressable region is "prefetchable", 0 otherwise |
| * t is 1 if the address is aliased (for non-relocatable I/O), below 1 MB (for Memory), |
| * or below 64 KB (for relocatable I/O). |
| * ss is the space code, denoting the address space |
| * 00 denotes Configuration Space |
| * 01 denotes I/O Space |
| * 10 denotes 32-bit-address Memory Space |
| * 11 denotes 64-bit-address Memory Space |
| * bbbbbbbb is the 8-bit Bus Number |
| * ddddd is the 5-bit Device Number |
| * fff is the 3-bit Function Number |
| * rrrrrrrr is the 8-bit Register Number |
| * hh...hh is a 32-bit unsigned number |
| * ll...ll is a 32-bit unsigned number |
| * for I/O Space is the 32-bit offset from the start of the region |
| * for 32-bit-address Memory Space is the 32-bit offset from the start of the region |
| * for 64-bit-address Memory Space is the 64-bit offset from the start of the region |
| * |
| * Here we only handle the p, ss, hh and ll fields. |
| * |
| * TOFIX: |
| * - handle prefetchable bit |
| */ |
| for (i = 0 ; i < cfg->ranges_count ; ++i) { |
| switch ((cfg->ranges[i].flags >> 24) & 0x03) { |
| case 0x01: |
| data->regions[PCIE_REGION_IO].bus_start = cfg->ranges[i].pcie_bus_addr; |
| data->regions[PCIE_REGION_IO].phys_start = cfg->ranges[i].host_map_addr; |
| data->regions[PCIE_REGION_IO].size = cfg->ranges[i].map_length; |
| /* Linux & U-Boot avoids allocating PCI resources from address 0 */ |
| if (data->regions[PCIE_REGION_IO].bus_start < 0x1000) { |
| data->regions[PCIE_REGION_IO].allocation_offset = 0x1000; |
| } |
| break; |
| case 0x02: |
| data->regions[PCIE_REGION_MEM].bus_start = cfg->ranges[i].pcie_bus_addr; |
| data->regions[PCIE_REGION_MEM].phys_start = cfg->ranges[i].host_map_addr; |
| data->regions[PCIE_REGION_MEM].size = cfg->ranges[i].map_length; |
| /* Linux & U-Boot avoids allocating PCI resources from address 0 */ |
| if (data->regions[PCIE_REGION_MEM].bus_start < 0x1000) { |
| data->regions[PCIE_REGION_MEM].allocation_offset = 0x1000; |
| } |
| break; |
| case 0x03: |
| data->regions[PCIE_REGION_MEM64].bus_start = cfg->ranges[i].pcie_bus_addr; |
| data->regions[PCIE_REGION_MEM64].phys_start = cfg->ranges[i].host_map_addr; |
| data->regions[PCIE_REGION_MEM64].size = cfg->ranges[i].map_length; |
| /* Linux & U-Boot avoids allocating PCI resources from address 0 */ |
| if (data->regions[PCIE_REGION_MEM64].bus_start < 0x1000) { |
| data->regions[PCIE_REGION_MEM64].allocation_offset = 0x1000; |
| } |
| break; |
| } |
| } |
| |
| if (!data->regions[PCIE_REGION_IO].size && |
| !data->regions[PCIE_REGION_MEM].size && |
| !data->regions[PCIE_REGION_MEM64].size) { |
| LOG_ERR("No regions defined"); |
| return -EINVAL; |
| } |
| |
| /* Get Config address space physical address & size */ |
| data->cfg_phys_addr = cfg->cfg_addr; |
| data->cfg_size = cfg->cfg_size; |
| |
| if (data->regions[PCIE_REGION_IO].size) { |
| LOG_DBG("IO bus [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_IO].bus_start, |
| (data->regions[PCIE_REGION_IO].bus_start + |
| data->regions[PCIE_REGION_IO].size - 1), |
| data->regions[PCIE_REGION_IO].size); |
| LOG_DBG("IO space [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_IO].phys_start, |
| (data->regions[PCIE_REGION_IO].phys_start + |
| data->regions[PCIE_REGION_IO].size - 1), |
| data->regions[PCIE_REGION_IO].size); |
| } |
| if (data->regions[PCIE_REGION_MEM].size) { |
| LOG_DBG("MEM bus [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_MEM].bus_start, |
| (data->regions[PCIE_REGION_MEM].bus_start + |
| data->regions[PCIE_REGION_MEM].size - 1), |
| data->regions[PCIE_REGION_MEM].size); |
| LOG_DBG("MEM space [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_MEM].phys_start, |
| (data->regions[PCIE_REGION_MEM].phys_start + |
| data->regions[PCIE_REGION_MEM].size - 1), |
| data->regions[PCIE_REGION_MEM].size); |
| } |
| if (data->regions[PCIE_REGION_MEM64].size) { |
| LOG_DBG("MEM64 bus [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_MEM64].bus_start, |
| (data->regions[PCIE_REGION_MEM64].bus_start + |
| data->regions[PCIE_REGION_MEM64].size - 1), |
| data->regions[PCIE_REGION_MEM64].size); |
| LOG_DBG("MEM64 space [0x%lx - 0x%lx, size 0x%lx]", |
| data->regions[PCIE_REGION_MEM64].phys_start, |
| (data->regions[PCIE_REGION_MEM64].phys_start + |
| data->regions[PCIE_REGION_MEM64].size - 1), |
| data->regions[PCIE_REGION_MEM64].size); |
| } |
| |
| /* Map config space to be used by the pcie_generic_ctrl_conf_read/write callbacks */ |
| device_map(&data->cfg_addr, data->cfg_phys_addr, data->cfg_size, K_MEM_CACHE_NONE); |
| |
| LOG_DBG("Config space [0x%lx - 0x%lx, size 0x%lx]", |
| data->cfg_phys_addr, (data->cfg_phys_addr + data->cfg_size - 1), data->cfg_size); |
| LOG_DBG("Config mapped [0x%lx - 0x%lx, size 0x%lx]", |
| data->cfg_addr, (data->cfg_addr + data->cfg_size - 1), data->cfg_size); |
| |
| pcie_generic_ctrl_enumerate(dev, PCIE_BDF(0, 0, 0)); |
| |
| return 0; |
| } |
| |
| static uint32_t pcie_ecam_ctrl_conf_read(const struct device *dev, pcie_bdf_t bdf, unsigned int reg) |
| { |
| struct pcie_ecam_data *data = dev->data; |
| |
| return pcie_generic_ctrl_conf_read(data->cfg_addr, bdf, reg); |
| } |
| |
| static void pcie_ecam_ctrl_conf_write(const struct device *dev, pcie_bdf_t bdf, unsigned int reg, |
| uint32_t reg_data) |
| { |
| struct pcie_ecam_data *data = dev->data; |
| |
| pcie_generic_ctrl_conf_write(data->cfg_addr, bdf, reg, reg_data); |
| } |
| |
| static bool pcie_ecam_region_allocate_type(struct pcie_ecam_data *data, pcie_bdf_t bdf, |
| size_t bar_size, uintptr_t *bar_bus_addr, |
| enum pcie_region_type type) |
| { |
| uintptr_t addr; |
| |
| addr = (((data->regions[type].bus_start + data->regions[type].allocation_offset) - 1) | |
| ((bar_size) - 1)) + 1; |
| |
| if (addr - data->regions[type].bus_start + bar_size > data->regions[type].size) { |
| return false; |
| } |
| |
| *bar_bus_addr = addr; |
| data->regions[type].allocation_offset = addr - data->regions[type].bus_start + bar_size; |
| |
| return true; |
| } |
| |
| static bool pcie_ecam_region_allocate(const struct device *dev, pcie_bdf_t bdf, |
| bool mem, bool mem64, size_t bar_size, |
| uintptr_t *bar_bus_addr) |
| { |
| struct pcie_ecam_data *data = dev->data; |
| enum pcie_region_type type; |
| |
| if (mem && !data->regions[PCIE_REGION_MEM64].size && |
| !data->regions[PCIE_REGION_MEM].size) { |
| LOG_DBG("bdf %x no mem region defined for allocation", bdf); |
| return false; |
| } |
| |
| if (!mem && !data->regions[PCIE_REGION_IO].size) { |
| LOG_DBG("bdf %x no io region defined for allocation", bdf); |
| return false; |
| } |
| |
| /* |
| * Allocate into mem64 region if available or is the only available |
| * |
| * TOFIX: |
| * - handle allocation from/to mem/mem64 when a region is full |
| */ |
| if (mem && ((mem64 && data->regions[PCIE_REGION_MEM64].size) || |
| (data->regions[PCIE_REGION_MEM64].size && |
| !data->regions[PCIE_REGION_MEM].size))) { |
| type = PCIE_REGION_MEM64; |
| } else if (mem) { |
| type = PCIE_REGION_MEM; |
| } else { |
| type = PCIE_REGION_IO; |
| } |
| |
| return pcie_ecam_region_allocate_type(data, bdf, bar_size, bar_bus_addr, type); |
| } |
| |
| static bool pcie_ecam_region_get_allocate_base(const struct device *dev, pcie_bdf_t bdf, |
| bool mem, bool mem64, size_t align, |
| uintptr_t *bar_base_addr) |
| { |
| struct pcie_ecam_data *data = (struct pcie_ecam_data *)dev->data; |
| enum pcie_region_type type; |
| |
| if (mem && !data->regions[PCIE_REGION_MEM64].size && |
| !data->regions[PCIE_REGION_MEM].size) { |
| LOG_DBG("bdf %x no mem region defined for allocation", bdf); |
| return false; |
| } |
| |
| if (!mem && !data->regions[PCIE_REGION_IO].size) { |
| LOG_DBG("bdf %x no io region defined for allocation", bdf); |
| return false; |
| } |
| |
| /* |
| * Allocate into mem64 region if available or is the only available |
| * |
| * TOFIX: |
| * - handle allocation from/to mem/mem64 when a region is full |
| */ |
| if (mem && ((mem64 && data->regions[PCIE_REGION_MEM64].size) || |
| (data->regions[PCIE_REGION_MEM64].size && |
| !data->regions[PCIE_REGION_MEM].size))) { |
| type = PCIE_REGION_MEM64; |
| } else if (mem) { |
| type = PCIE_REGION_MEM; |
| } else { |
| type = PCIE_REGION_IO; |
| } |
| |
| *bar_base_addr = (((data->regions[type].bus_start + |
| data->regions[type].allocation_offset) - 1) | ((align) - 1)) + 1; |
| |
| return true; |
| } |
| |
| static bool pcie_ecam_region_translate(const struct device *dev, pcie_bdf_t bdf, |
| bool mem, bool mem64, uintptr_t bar_bus_addr, |
| uintptr_t *bar_addr) |
| { |
| struct pcie_ecam_data *data = dev->data; |
| enum pcie_region_type type; |
| |
| /* Means it hasn't been allocated */ |
| if (!bar_bus_addr) { |
| return false; |
| } |
| |
| if (mem && ((mem64 && data->regions[PCIE_REGION_MEM64].size) || |
| (data->regions[PCIE_REGION_MEM64].size && |
| !data->regions[PCIE_REGION_MEM].size))) { |
| type = PCIE_REGION_MEM64; |
| } else if (mem) { |
| type = PCIE_REGION_MEM; |
| } else { |
| type = PCIE_REGION_IO; |
| } |
| |
| *bar_addr = data->regions[type].phys_start + (bar_bus_addr - data->regions[type].bus_start); |
| |
| return true; |
| } |
| |
| #if CONFIG_PCIE_MSI |
| static uint8_t pcie_ecam_msi_device_setup(const struct device *dev, unsigned int priority, |
| msi_vector_t *vectors, uint8_t n_vector) |
| { |
| #ifdef CONFIG_GIC_V3_ITS |
| const struct pcie_ctrl_config *cfg = (const struct pcie_ctrl_config *)dev->config; |
| unsigned int device_id; |
| pcie_bdf_t bdf; |
| int ret, i; |
| |
| if (!n_vector) { |
| return 0; |
| } |
| |
| bdf = vectors[0].bdf; |
| |
| /* We do not support allocating vectors for multiple BDFs for now, |
| * This would need tracking vectors already allocated for a BDF and |
| * re-allocating a proper table in ITS for each BDF since we can't be |
| * sure more vectors for each BDF will be allocated later. |
| * Simply bail-out if it's the case here. |
| */ |
| for (i = 1; i < n_vector; i++) { |
| if (vectors[i].bdf != bdf) { |
| LOG_ERR("Multiple BDFs in a single MSI vector allocation isn't supported"); |
| return 0; |
| } |
| } |
| |
| device_id = PCI_BDF_TO_DEVID(bdf); |
| |
| ret = its_setup_deviceid(cfg->msi_parent, device_id, n_vector); |
| if (ret) { |
| return 0; |
| } |
| |
| for (i = 0; i < n_vector; i++) { |
| vectors[i].arch.irq = its_alloc_intid(cfg->msi_parent); |
| vectors[i].arch.address = its_get_msi_addr(cfg->msi_parent); |
| vectors[i].arch.eventid = i; |
| vectors[i].arch.priority = priority; |
| |
| ret = its_map_intid(cfg->msi_parent, device_id, |
| vectors[i].arch.eventid, vectors[i].arch.irq); |
| if (ret) { |
| break; |
| } |
| } |
| |
| return i; |
| #else |
| return 0; |
| #endif |
| } |
| #endif |
| |
| static const struct pcie_ctrl_driver_api pcie_ecam_api = { |
| .conf_read = pcie_ecam_ctrl_conf_read, |
| .conf_write = pcie_ecam_ctrl_conf_write, |
| .region_allocate = pcie_ecam_region_allocate, |
| .region_get_allocate_base = pcie_ecam_region_get_allocate_base, |
| .region_translate = pcie_ecam_region_translate, |
| #if CONFIG_PCIE_MSI |
| .msi_device_setup = pcie_ecam_msi_device_setup, |
| #endif |
| }; |
| |
| #if CONFIG_PCIE_MSI |
| #define DEVICE_DT_GET_MSI_PARENT(n) \ |
| .msi_parent = DEVICE_DT_GET(DT_PHANDLE(DT_DRV_INST(n), msi_parent)), |
| #else |
| #define DEVICE_DT_GET_MSI_PARENT(n) |
| #endif |
| |
| #define PCIE_ECAM_INIT(n) \ |
| static struct pcie_ecam_data pcie_ecam_data##n; \ |
| static const struct pcie_ctrl_config pcie_ecam_config##n = { \ |
| DEVICE_DT_GET_MSI_PARENT(n) \ |
| .cfg_addr = DT_INST_REG_ADDR(n), \ |
| .cfg_size = DT_INST_REG_SIZE(n), \ |
| .ranges_count = DT_NUM_RANGES(DT_DRV_INST(n)), \ |
| .ranges = { \ |
| DT_FOREACH_RANGE(DT_DRV_INST(n), PCIE_RANGE_FORMAT) \ |
| }, \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, &pcie_ecam_init, NULL, \ |
| &pcie_ecam_data##n, \ |
| &pcie_ecam_config##n, \ |
| PRE_KERNEL_1, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
| &pcie_ecam_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PCIE_ECAM_INIT) |