/* * Copyright (c) 2021 BayLibre, SAS * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(pcie_core, LOG_LEVEL_INF); #include #include #include #if CONFIG_PCIE_MSI #include #endif /* arch agnostic PCIe API implementation */ uint32_t pcie_conf_read(pcie_bdf_t bdf, unsigned int reg) { const struct device *dev; dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_pcie_controller)); if (!dev) { LOG_ERR("Failed to get PCIe root complex"); return 0xffffffff; } return pcie_ctrl_conf_read(dev, bdf, reg); } void pcie_conf_write(pcie_bdf_t bdf, unsigned int reg, uint32_t data) { const struct device *dev; dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_pcie_controller)); if (!dev) { LOG_ERR("Failed to get PCIe root complex"); return; } pcie_ctrl_conf_write(dev, bdf, reg, data); } uint32_t pcie_generic_ctrl_conf_read(mm_reg_t cfg_addr, pcie_bdf_t bdf, unsigned int reg) { volatile uint32_t *bdf_cfg_mem = (volatile uint32_t *)((uintptr_t)cfg_addr + (bdf << 4)); if (!cfg_addr) { return 0xffffffff; } return bdf_cfg_mem[reg]; } void pcie_generic_ctrl_conf_write(mm_reg_t cfg_addr, pcie_bdf_t bdf, unsigned int reg, uint32_t data) { volatile uint32_t *bdf_cfg_mem = (volatile uint32_t *)((uintptr_t)cfg_addr + (bdf << 4)); if (!cfg_addr) { return; } bdf_cfg_mem[reg] = data; } static void pcie_generic_ctrl_enumerate_bars(const struct device *ctrl_dev, pcie_bdf_t bdf, unsigned int nbars) { unsigned int bar, reg, data; uintptr_t scratch, bar_bus_addr; size_t size, bar_size; for (bar = 0, reg = PCIE_CONF_BAR0; bar < nbars && reg <= PCIE_CONF_BAR5; reg ++, bar++) { bool found_mem64 = false; bool found_mem = false; data = scratch = pcie_conf_read(bdf, reg); if (PCIE_CONF_BAR_INVAL_FLAGS(data)) { continue; } if (PCIE_CONF_BAR_MEM(data)) { found_mem = true; if (PCIE_CONF_BAR_64(data)) { found_mem64 = true; scratch |= ((uint64_t)pcie_conf_read(bdf, reg + 1)) << 32; if (PCIE_CONF_BAR_ADDR(scratch) == PCIE_CONF_BAR_INVAL64) { continue; } } else { if (PCIE_CONF_BAR_ADDR(scratch) == PCIE_CONF_BAR_INVAL) { continue; } } } pcie_conf_write(bdf, reg, 0xFFFFFFFF); size = pcie_conf_read(bdf, reg); pcie_conf_write(bdf, reg, scratch & 0xFFFFFFFF); if (found_mem64) { pcie_conf_write(bdf, reg + 1, 0xFFFFFFFF); size |= ((uint64_t)pcie_conf_read(bdf, reg + 1)) << 32; pcie_conf_write(bdf, reg + 1, scratch >> 32); } if (!PCIE_CONF_BAR_ADDR(size)) { if (found_mem64) { reg++; } continue; } if (found_mem) { if (found_mem64) { bar_size = (uint64_t)~PCIE_CONF_BAR_ADDR(size) + 1; } else { bar_size = (uint32_t)~PCIE_CONF_BAR_ADDR(size) + 1; } } else { bar_size = (uint32_t)~PCIE_CONF_BAR_IO_ADDR(size) + 1; } if (pcie_ctrl_region_allocate(ctrl_dev, bdf, found_mem, found_mem64, bar_size, &bar_bus_addr)) { uintptr_t bar_phys_addr; pcie_ctrl_region_translate(ctrl_dev, bdf, found_mem, found_mem64, bar_bus_addr, &bar_phys_addr); LOG_INF("[%02x:%02x.%x] BAR%d size 0x%lx " "assigned [%s 0x%lx-0x%lx -> 0x%lx-0x%lx]", PCIE_BDF_TO_BUS(bdf), PCIE_BDF_TO_DEV(bdf), PCIE_BDF_TO_FUNC(bdf), bar, bar_size, found_mem ? (found_mem64 ? "mem64" : "mem") : "io", bar_bus_addr, bar_bus_addr + bar_size - 1, bar_phys_addr, bar_phys_addr + bar_size - 1); pcie_conf_write(bdf, reg, bar_bus_addr & 0xFFFFFFFF); if (found_mem64) { pcie_conf_write(bdf, reg + 1, bar_bus_addr >> 32); } } else { LOG_INF("[%02x:%02x.%x] BAR%d size 0x%lx Failed memory allocation.", PCIE_BDF_TO_BUS(bdf), PCIE_BDF_TO_DEV(bdf), PCIE_BDF_TO_FUNC(bdf), bar, bar_size); } if (found_mem64) { reg++; } } } static bool pcie_generic_ctrl_enumerate_type1(const struct device *ctrl_dev, pcie_bdf_t bdf, unsigned int bus_number) { uint32_t class = pcie_conf_read(bdf, PCIE_CONF_CLASSREV); /* Handle only PCI-to-PCI bridge for now */ if (PCIE_CONF_CLASSREV_CLASS(class) == 0x06 && PCIE_CONF_CLASSREV_SUBCLASS(class) == 0x04) { uint32_t number = pcie_conf_read(bdf, PCIE_BUS_NUMBER); uintptr_t bar_base_addr; pcie_generic_ctrl_enumerate_bars(ctrl_dev, bdf, 2); /* Configure bus number registers */ pcie_conf_write(bdf, PCIE_BUS_NUMBER, PCIE_BUS_NUMBER_VAL(PCIE_BDF_TO_BUS(bdf), bus_number, 0xff, /* set max until we finished scanning */ PCIE_SECONDARY_LATENCY_TIMER(number))); /* I/O align on 4k boundary */ if (pcie_ctrl_region_get_allocate_base(ctrl_dev, bdf, false, false, KB(4), &bar_base_addr)) { uint32_t io = pcie_conf_read(bdf, PCIE_IO_SEC_STATUS); uint32_t io_upper = pcie_conf_read(bdf, PCIE_IO_BASE_LIMIT_UPPER); pcie_conf_write(bdf, PCIE_IO_SEC_STATUS, PCIE_IO_SEC_STATUS_VAL(PCIE_IO_BASE(io), PCIE_IO_LIMIT(io), PCIE_SEC_STATUS(io))); pcie_conf_write(bdf, PCIE_IO_BASE_LIMIT_UPPER, PCIE_IO_BASE_LIMIT_UPPER_VAL(PCIE_IO_BASE_UPPER(io_upper), PCIE_IO_LIMIT_UPPER(io_upper))); pcie_set_cmd(bdf, PCIE_CONF_CMDSTAT_IO, true); } /* MEM align on 1MiB boundary */ if (pcie_ctrl_region_get_allocate_base(ctrl_dev, bdf, true, false, MB(1), &bar_base_addr)) { uint32_t mem = pcie_conf_read(bdf, PCIE_MEM_BASE_LIMIT); pcie_conf_write(bdf, PCIE_MEM_BASE_LIMIT, PCIE_MEM_BASE_LIMIT_VAL((bar_base_addr & 0xfff00000) >> 16, PCIE_MEM_LIMIT(mem))); pcie_set_cmd(bdf, PCIE_CONF_CMDSTAT_MEM, true); } /* TODO: add support for prefetchable */ pcie_set_cmd(bdf, PCIE_CONF_CMDSTAT_MASTER, true); return true; } return false; } static void pcie_generic_ctrl_post_enumerate_type1(const struct device *ctrl_dev, pcie_bdf_t bdf, unsigned int bus_number) { uint32_t number = pcie_conf_read(bdf, PCIE_BUS_NUMBER); uintptr_t bar_base_addr; /* Configure bus subordinate */ pcie_conf_write(bdf, PCIE_BUS_NUMBER, PCIE_BUS_NUMBER_VAL(PCIE_BUS_PRIMARY_NUMBER(number), PCIE_BUS_SECONDARY_NUMBER(number), bus_number - 1, PCIE_SECONDARY_LATENCY_TIMER(number))); /* I/O align on 4k boundary */ if (pcie_ctrl_region_get_allocate_base(ctrl_dev, bdf, false, false, KB(4), &bar_base_addr)) { uint32_t io = pcie_conf_read(bdf, PCIE_IO_SEC_STATUS); uint32_t io_upper = pcie_conf_read(bdf, PCIE_IO_BASE_LIMIT_UPPER); pcie_conf_write(bdf, PCIE_IO_SEC_STATUS, PCIE_IO_SEC_STATUS_VAL(PCIE_IO_BASE(io), ((bar_base_addr - 1) & 0x0000f000) >> 16, PCIE_SEC_STATUS(io))); pcie_conf_write(bdf, PCIE_IO_BASE_LIMIT_UPPER, PCIE_IO_BASE_LIMIT_UPPER_VAL(PCIE_IO_BASE_UPPER(io_upper), ((bar_base_addr - 1) & 0xffff0000) >> 16)); } /* MEM align on 1MiB boundary */ if (pcie_ctrl_region_get_allocate_base(ctrl_dev, bdf, true, false, MB(1), &bar_base_addr)) { uint32_t mem = pcie_conf_read(bdf, PCIE_MEM_BASE_LIMIT); pcie_conf_write(bdf, PCIE_MEM_BASE_LIMIT, PCIE_MEM_BASE_LIMIT_VAL(PCIE_MEM_BASE(mem), (bar_base_addr - 1) >> 16)); } /* TODO: add support for prefetchable */ } static void pcie_generic_ctrl_enumerate_type0(const struct device *ctrl_dev, pcie_bdf_t bdf) { /* Setup Type0 BARs */ pcie_generic_ctrl_enumerate_bars(ctrl_dev, bdf, 6); } static bool pcie_generic_ctrl_enumerate_endpoint(const struct device *ctrl_dev, pcie_bdf_t bdf, unsigned int bus_number, bool *skip_next_func) { bool multifunction_device = false; bool layout_type_1 = false; uint32_t data, class, id; bool is_bridge = false; *skip_next_func = false; id = pcie_conf_read(bdf, PCIE_CONF_ID); if (id == PCIE_ID_NONE) { return false; } class = pcie_conf_read(bdf, PCIE_CONF_CLASSREV); data = pcie_conf_read(bdf, PCIE_CONF_TYPE); multifunction_device = PCIE_CONF_MULTIFUNCTION(data); layout_type_1 = PCIE_CONF_TYPE_BRIDGE(data); LOG_INF("[%02x:%02x.%x] %04x:%04x class %x subclass %x progif %x " "rev %x Type%x multifunction %s", PCIE_BDF_TO_BUS(bdf), PCIE_BDF_TO_DEV(bdf), PCIE_BDF_TO_FUNC(bdf), id & 0xffff, id >> 16, PCIE_CONF_CLASSREV_CLASS(class), PCIE_CONF_CLASSREV_SUBCLASS(class), PCIE_CONF_CLASSREV_PROGIF(class), PCIE_CONF_CLASSREV_REV(class), layout_type_1 ? 1 : 0, multifunction_device ? "true" : "false"); /* Do not enumerate sub-functions if not a multifunction device */ if (PCIE_BDF_TO_FUNC(bdf) == 0 && !multifunction_device) { *skip_next_func = true; } if (layout_type_1) { is_bridge = pcie_generic_ctrl_enumerate_type1(ctrl_dev, bdf, bus_number); } else { pcie_generic_ctrl_enumerate_type0(ctrl_dev, bdf); } return is_bridge; } /* Return the next BDF or PCIE_BDF_NONE without changing bus number */ static inline unsigned int pcie_bdf_bus_next(unsigned int bdf, bool skip_next_func) { if (skip_next_func) { if (PCIE_BDF_TO_DEV(bdf) == PCIE_BDF_DEV_MASK) { return PCIE_BDF_NONE; } return PCIE_BDF(PCIE_BDF_TO_BUS(bdf), PCIE_BDF_TO_DEV(bdf) + 1, 0); } if (PCIE_BDF_TO_DEV(bdf) == PCIE_BDF_DEV_MASK && PCIE_BDF_TO_FUNC(bdf) == PCIE_BDF_FUNC_MASK) { return PCIE_BDF_NONE; } return PCIE_BDF(PCIE_BDF_TO_BUS(bdf), (PCIE_BDF_TO_DEV(bdf) + ((PCIE_BDF_TO_FUNC(bdf) + 1) / (PCIE_BDF_FUNC_MASK + 1))), ((PCIE_BDF_TO_FUNC(bdf) + 1) & PCIE_BDF_FUNC_MASK)); } struct pcie_bus_state { /* Current scanned bus BDF, always valid */ unsigned int bus_bdf; /* Current bridge endpoint BDF, either valid or PCIE_BDF_NONE */ unsigned int bridge_bdf; /* Next BDF to scan on bus, either valid or PCIE_BDF_NONE when all EP scanned */ unsigned int next_bdf; }; #define MAX_TRAVERSE_STACK 256 /* Non-recursive stack based PCIe bus & bridge enumeration */ void pcie_generic_ctrl_enumerate(const struct device *ctrl_dev, pcie_bdf_t bdf_start) { struct pcie_bus_state stack[MAX_TRAVERSE_STACK], *state; unsigned int bus_number = PCIE_BDF_TO_BUS(bdf_start) + 1; bool skip_next_func = false; bool is_bridge = false; int stack_top = 0; /* Start with first endpoint of immediate Root Controller bus */ stack[stack_top].bus_bdf = PCIE_BDF(PCIE_BDF_TO_BUS(bdf_start), 0, 0); stack[stack_top].bridge_bdf = PCIE_BDF_NONE; stack[stack_top].next_bdf = bdf_start; while (stack_top >= 0) { /* Top of stack contains the current PCIe bus to traverse */ state = &stack[stack_top]; /* Finish current bridge configuration before scanning other endpoints */ if (state->bridge_bdf != PCIE_BDF_NONE) { pcie_generic_ctrl_post_enumerate_type1(ctrl_dev, state->bridge_bdf, bus_number); state->bridge_bdf = PCIE_BDF_NONE; } /* We still have more endpoints to scan */ if (state->next_bdf != PCIE_BDF_NONE) { while (state->next_bdf != PCIE_BDF_NONE) { is_bridge = pcie_generic_ctrl_enumerate_endpoint(ctrl_dev, state->next_bdf, bus_number, &skip_next_func); if (is_bridge) { state->bridge_bdf = state->next_bdf; state->next_bdf = pcie_bdf_bus_next(state->next_bdf, skip_next_func); /* If we can't handle more bridges, don't go further */ if (stack_top == (MAX_TRAVERSE_STACK - 1) || bus_number == PCIE_BDF_BUS_MASK) { break; } /* Push to stack to scan this bus */ stack_top++; stack[stack_top].bus_bdf = PCIE_BDF(bus_number, 0, 0); stack[stack_top].bridge_bdf = PCIE_BDF_NONE; stack[stack_top].next_bdf = PCIE_BDF(bus_number, 0, 0); /* Increase bus number */ bus_number++; break; } state->next_bdf = pcie_bdf_bus_next(state->next_bdf, skip_next_func); } } else { /* We finished scanning this bus, go back and scan next endpoints */ stack_top--; } } } #ifdef CONFIG_PCIE_MSI uint32_t pcie_msi_map(unsigned int irq, msi_vector_t *vector, uint8_t n_vector) { ARG_UNUSED(irq); return vector->arch.address; } uint16_t pcie_msi_mdr(unsigned int irq, msi_vector_t *vector) { ARG_UNUSED(irq); return vector->arch.eventid; } uint8_t arch_pcie_msi_vectors_allocate(unsigned int priority, msi_vector_t *vectors, uint8_t n_vector) { const struct device *dev; dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_pcie_controller)); if (!dev) { LOG_ERR("Failed to get PCIe root complex"); return 0; } return pcie_ctrl_msi_device_setup(dev, priority, vectors, n_vector); } bool arch_pcie_msi_vector_connect(msi_vector_t *vector, void (*routine)(const void *parameter), const void *parameter, uint32_t flags) { if (irq_connect_dynamic(vector->arch.irq, vector->arch.priority, routine, parameter, flags) != vector->arch.irq) { return false; } irq_enable(vector->arch.irq); return true; } #endif