123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- /*
- * Copyright (c) 2021-2024, Arm Limited and Contributors. All rights reserved.
- * Copyright (c) 2024, Mario Bălănică <mariobalanica02@gmail.com>
- *
- * SPDX-License-Identifier: BSD-3-Clause
- *
- * The RPi has a single nonstandard PCI config region. It is broken into two
- * pieces, the root port config registers and a window to a single device's
- * config space which can move between devices. There isn't (yet) an
- * authoritative public document on this since the available BCM2711 reference
- * notes that there is a PCIe root port in the memory map but doesn't describe
- * it. Given that it's not ECAM compliant yet reasonably simple, it makes for
- * an excellent example of the PCI SMCCC interface.
- *
- * The PCI SMCCC interface is described in DEN0115 available from:
- * https://developer.arm.com/documentation/den0115/latest
- */
- #include <assert.h>
- #include <stdint.h>
- #include <common/debug.h>
- #include <common/runtime_svc.h>
- #include <lib/pmf/pmf.h>
- #include <lib/runtime_instr.h>
- #include <services/pci_svc.h>
- #include <services/sdei.h>
- #include <services/std_svc.h>
- #include <smccc_helpers.h>
- #include <lib/mmio.h>
- #define PCIE_MISC_PCIE_STATUS 0x4068
- #define PCIE_EXT_CFG_INDEX 0x9000
- #define PCIE_EXT_CFG_DATA 0x8000
- #define PCIE_EXT_CFG_BDF_SHIFT 12
- #define INVALID_PCI_ADDR 0xFFFFFFFF
- static spinlock_t pci_lock;
- static uint64_t pcie_rc_bases[] = { RPI_PCIE_RC_BASES };
- static uint64_t pci_segment_lib_get_base(uint32_t address, uint32_t offset)
- {
- uint64_t base;
- uint32_t seg, bus, dev, fun;
- seg = PCI_ADDR_SEG(address);
- if (seg >= ARRAY_SIZE(pcie_rc_bases)) {
- return INVALID_PCI_ADDR;
- }
- /* The root port is at the base of the PCIe register space */
- base = pcie_rc_bases[seg];
- bus = PCI_ADDR_BUS(address);
- dev = PCI_ADDR_DEV(address);
- fun = PCI_ADDR_FUN(address);
- /* There can only be the root port on bus 0 */
- if ((bus == 0U) && ((dev > 0U) || (fun > 0U))) {
- return INVALID_PCI_ADDR;
- }
- /* There can only be one device on bus 1 */
- if ((bus == 1U) && (dev > 0U)) {
- return INVALID_PCI_ADDR;
- }
- if (bus > 0) {
- #if RPI_PCIE_ECAM_SERROR_QUIRK
- uint32_t status = mmio_read_32(base + PCIE_MISC_PCIE_STATUS);
- /* Assure link up before accessing downstream of root port */
- if ((status & 0x30) == 0U) {
- return INVALID_PCI_ADDR;
- }
- #endif
- /*
- * Device function is mapped at CFG_DATA, a 4 KB window
- * movable by writing its B/D/F location to CFG_INDEX.
- */
- mmio_write_32(base + PCIE_EXT_CFG_INDEX, address << PCIE_EXT_CFG_BDF_SHIFT);
- base += PCIE_EXT_CFG_DATA;
- }
- return base + (offset & PCI_OFFSET_MASK);
- }
- /**
- * pci_read_config() - Performs a config space read at addr
- * @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
- * @off: register offset of function described by @addr to read
- * @sz: size of read (8,16,32) bits.
- * @val: returned zero extended value read from config space
- *
- * sz bits of PCI config space is read at addr:offset, and the value
- * is returned in val. Invalid segment/offset values return failure.
- * Reads to valid functions that don't exist return INVALID_PCI_ADDR
- * as is specified by PCI for requests that aren't completed by EPs.
- * The boilerplate in pci_svc.c tends to do basic segment, off
- * and sz validation. This routine should avoid duplicating those
- * checks.
- *
- * This function maps directly to the PCI_READ function in DEN0115
- * where detailed requirements may be found.
- *
- * Return: SMC_PCI_CALL_SUCCESS with val set
- * SMC_PCI_CALL_INVAL_PARAM, on parameter error
- */
- uint32_t pci_read_config(uint32_t addr, uint32_t off, uint32_t sz, uint32_t *val)
- {
- uint32_t ret = SMC_PCI_CALL_SUCCESS;
- uint64_t base;
- spin_lock(&pci_lock);
- base = pci_segment_lib_get_base(addr, off);
- if (base == INVALID_PCI_ADDR) {
- *val = base;
- } else {
- switch (sz) {
- case SMC_PCI_SZ_8BIT:
- *val = mmio_read_8(base);
- break;
- case SMC_PCI_SZ_16BIT:
- *val = mmio_read_16(base);
- break;
- case SMC_PCI_SZ_32BIT:
- *val = mmio_read_32(base);
- break;
- default: /* should be unreachable */
- *val = 0U;
- ret = SMC_PCI_CALL_INVAL_PARAM;
- }
- }
- spin_unlock(&pci_lock);
- return ret;
- }
- /**
- * pci_write_config() - Performs a config space write at addr
- * @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
- * @off: register offset of function described by @addr to write
- * @sz: size of write (8,16,32) bits.
- * @val: value to be written
- *
- * sz bits of PCI config space is written at addr:offset. Invalid
- * segment/BDF values return failure. Writes to valid functions
- * without valid EPs are ignored, as is specified by PCI.
- * The boilerplate in pci_svc.c tends to do basic segment, off
- * and sz validation, so it shouldn't need to be repeated here.
- *
- * This function maps directly to the PCI_WRITE function in DEN0115
- * where detailed requirements may be found.
- *
- * Return: SMC_PCI_CALL_SUCCESS
- * SMC_PCI_CALL_INVAL_PARAM, on parameter error
- */
- uint32_t pci_write_config(uint32_t addr, uint32_t off, uint32_t sz, uint32_t val)
- {
- uint32_t ret = SMC_PCI_CALL_SUCCESS;
- uint64_t base;
- spin_lock(&pci_lock);
- base = pci_segment_lib_get_base(addr, off);
- if (base != INVALID_PCI_ADDR) {
- switch (sz) {
- case SMC_PCI_SZ_8BIT:
- mmio_write_8(base, val);
- break;
- case SMC_PCI_SZ_16BIT:
- mmio_write_16(base, val);
- break;
- case SMC_PCI_SZ_32BIT:
- mmio_write_32(base, val);
- break;
- default: /* should be unreachable */
- ret = SMC_PCI_CALL_INVAL_PARAM;
- }
- }
- spin_unlock(&pci_lock);
- return ret;
- }
- /**
- * pci_get_bus_for_seg() - returns the start->end bus range for a segment
- * @seg: segment being queried
- * @bus_range: returned bus begin + (end << 8)
- * @nseg: returns next segment in this machine or 0 for end
- *
- * pci_get_bus_for_seg is called to check if a given segment is
- * valid on this machine. If it is valid, then its bus ranges are
- * returned along with the next valid segment on the machine. If
- * this is the last segment, then nseg must be 0.
- *
- * This function maps directly to the PCI_GET_SEG_INFO function
- * in DEN0115 where detailed requirements may be found.
- *
- * Return: SMC_PCI_CALL_SUCCESS, and appropriate bus_range and nseg
- * SMC_PCI_CALL_NOT_IMPL, if the segment is invalid
- */
- uint32_t pci_get_bus_for_seg(uint32_t seg, uint32_t *bus_range, uint32_t *nseg)
- {
- uint32_t ret = SMC_PCI_CALL_SUCCESS;
- uint32_t rc_count = ARRAY_SIZE(pcie_rc_bases);
- *nseg = (seg < rc_count - 1U) ? seg + 1U : 0U;
- if (seg < rc_count) {
- *bus_range = 0U + (0xFF << 8); /* start 0, end 255 */
- } else {
- *bus_range = 0U;
- ret = SMC_PCI_CALL_NOT_IMPL;
- }
- return ret;
- }
|