diff --git a/components/drivers/include/drivers/pci.h b/components/drivers/include/drivers/pci.h index 414a0abf1ad..ff20ff87c49 100644 --- a/components/drivers/include/drivers/pci.h +++ b/components/drivers/include/drivers/pci.h @@ -8,6 +8,16 @@ * 2022-08-25 GuEe-GUI first version */ +/** + * @file pci.h + * @brief PCI (Peripheral Component Interconnect) bus framework public API + * + * Defines the core PCI subsystem: device, bus, host bridge, driver + * structures, configuration space access, capability discovery, + * INTx/MSI/MSI-X interrupt management, resource allocation, + * device tree integration, and bus enumeration. + */ + #ifndef __PCI_H__ #define __PCI_H__ @@ -22,12 +32,17 @@ #include "../../pci/pci_ids.h" #include "../../pci/pci_regs.h" -#define RT_PCI_INTX_PIN_MAX 4 -#define RT_PCI_BAR_NR_MAX 6 -#define RT_PCI_DEVICE_MAX 32 -#define RT_PCI_FUNCTION_MAX 8 +/** @brief Maximum INTx pin number (INTA=1, INTB=2, INTC=3, INTD=4) */ +#define RT_PCI_INTX_PIN_MAX 4 +/** @brief Maximum number of BARs per PCI device */ +#define RT_PCI_BAR_NR_MAX 6 +/** @brief Maximum number of devices per PCI bus */ +#define RT_PCI_DEVICE_MAX 32 +/** @brief Maximum number of functions per PCI device (without ARI) */ +#define RT_PCI_FUNCTION_MAX 8 -#define RT_PCI_FIND_CAP_TTL 48 +/** @brief Maximum steps when searching the PCI capability linked list */ +#define RT_PCI_FIND_CAP_TTL 48 /* * The PCI interface treats multi-function devices as independent @@ -37,47 +52,71 @@ * 7:3 = slot * 2:0 = function */ -#define RT_PCI_DEVID(bus, devfn) ((((rt_uint16_t)(bus)) << 8) | (devfn)) -#define RT_PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) -#define RT_PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) -#define RT_PCI_FUNC(devfn) ((devfn) & 0x07) - -#define PCIE_LINK_STATE_L0S RT_BIT(0) -#define PCIE_LINK_STATE_L1 RT_BIT(1) -#define PCIE_LINK_STATE_CLKPM RT_BIT(2) -#define PCIE_LINK_STATE_L1_1 RT_BIT(3) -#define PCIE_LINK_STATE_L1_2 RT_BIT(4) -#define PCIE_LINK_STATE_L1_1_PCIPM RT_BIT(5) -#define PCIE_LINK_STATE_L1_2_PCIPM RT_BIT(6) -#define PCIE_LINK_STATE_ALL \ -( \ - PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | \ - PCIE_LINK_STATE_CLKPM | \ - PCIE_LINK_STATE_L1_1 | PCIE_LINK_STATE_L1_2 | \ - PCIE_LINK_STATE_L1_1_PCIPM | PCIE_LINK_STATE_L1_2_PCIPM \ -) +/** @brief Build a 16-bit device ID from bus and devfn */ +#define RT_PCI_DEVID(bus, devfn) ((((rt_uint16_t)(bus)) << 8) | (devfn)) +/** @brief Encode slot and function into a devfn byte */ +#define RT_PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) +/** @brief Extract slot number from a devfn byte */ +#define RT_PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +/** @brief Extract function number from a devfn byte */ +#define RT_PCI_FUNC(devfn) ((devfn) & 0x07) + +/** @brief PCIe L0s link state */ +#define PCIE_LINK_STATE_L0S RT_BIT(0) +/** @brief PCIe L1 link state */ +#define PCIE_LINK_STATE_L1 RT_BIT(1) +/** @brief PCIe clock PM link state */ +#define PCIE_LINK_STATE_CLKPM RT_BIT(2) +/** @brief PCIe L1.1 substate */ +#define PCIE_LINK_STATE_L1_1 RT_BIT(3) +/** @brief PCIe L1.2 substate */ +#define PCIE_LINK_STATE_L1_2 RT_BIT(4) +/** @brief PCIe L1.1 with PCI-PM substate */ +#define PCIE_LINK_STATE_L1_1_PCIPM RT_BIT(5) +/** @brief PCIe L1.2 with PCI-PM substate */ +#define PCIE_LINK_STATE_L1_2_PCIPM RT_BIT(6) +/** @brief All PCIe ASPM link states combined */ +#define PCIE_LINK_STATE_ALL \ + ( \ + PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | \ + PCIE_LINK_STATE_CLKPM | \ + PCIE_LINK_STATE_L1_1 | PCIE_LINK_STATE_L1_2 | \ + PCIE_LINK_STATE_L1_1_PCIPM | PCIE_LINK_STATE_L1_2_PCIPM) + +/** + * @brief PCI bus address region descriptor + * + * Describes a contiguous range of PCI bus address space and its + * mapping to CPU physical memory. Used for BAR allocation. + */ struct rt_pci_bus_region { - rt_uint64_t phy_addr; - rt_uint64_t cpu_addr; - rt_uint64_t size; + rt_uint64_t phy_addr; /**< PCI bus physical address (start of region) */ + rt_uint64_t cpu_addr; /**< CPU physical address mapped to this region */ + rt_uint64_t size; /**< Region size in bytes */ - rt_uint64_t bus_start; + rt_uint64_t bus_start; /**< Current allocation cursor within the region */ -#define PCI_BUS_REGION_F_NONE 0xffffffff /* PCI no memory */ -#define PCI_BUS_REGION_F_MEM 0x00000000 /* PCI memory space */ -#define PCI_BUS_REGION_F_IO 0x00000001 /* PCI IO space */ -#define PCI_BUS_REGION_F_PREFETCH 0x00000008 /* Prefetchable PCI memory */ - rt_ubase_t flags; +#define PCI_BUS_REGION_F_NONE 0xffffffff /**< Region type: no memory (unused) */ +#define PCI_BUS_REGION_F_MEM 0x00000000 /**< Region type: PCI memory space */ +#define PCI_BUS_REGION_F_IO 0x00000001 /**< Region type: PCI I/O space */ +#define PCI_BUS_REGION_F_PREFETCH 0x00000008 /**< Region type: prefetchable PCI memory */ + rt_ubase_t flags; /**< Region flags (PCI_BUS_REGION_F_*) */ }; +/** + * @brief PCI BAR resource descriptor + * + * Describes a single BAR (Base Address Register) or Expansion ROM: + * its CPU-side base address, size, and type flags. + */ struct rt_pci_bus_resource { - rt_ubase_t base; - rt_size_t size; + rt_ubase_t base; /**< CPU-side base address of the BAR */ + rt_size_t size; /**< BAR size in bytes */ - rt_ubase_t flags; + rt_ubase_t flags; /**< Resource flags (PCI_BUS_REGION_F_*) */ }; /* @@ -102,185 +141,256 @@ struct rt_pci_bus_resource struct rt_pci_bus; +/** + * @brief PCI device ID entry for driver matching + * + * Used in a driver's ID table. Supports matching by vendor/device, + * subsystem vendor/device, and class code. PCI_ANY_ID acts as a + * wildcard that matches any value. + */ struct rt_pci_device_id { -#define PCI_ANY_ID (~0) +#define PCI_ANY_ID (~0) +/** @brief Helper: initialize a device ID by vendor and device (subsystem = any) */ #define RT_PCI_DEVICE_ID(vend, dev) \ .vendor = (vend), \ .device = (dev), \ .subsystem_vendor = PCI_ANY_ID, \ .subsystem_device = PCI_ANY_ID -#define RT_PCI_DEVICE_CLASS(dev_class, dev_class_mask) \ - .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \ - .subsystem_vendor = PCI_ANY_ID, \ - .subsystem_device = PCI_ANY_ID, \ +/** @brief Helper: initialize a device ID by class code with mask */ +#define RT_PCI_DEVICE_CLASS(dev_class, dev_class_mask) \ + .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \ + .subsystem_vendor = PCI_ANY_ID, \ + .subsystem_device = PCI_ANY_ID, \ .class = (dev_class), .class_mask = (dev_class_mask), - rt_uint32_t vendor, device; /* Vendor and device ID or PCI_ANY_ID */ - rt_uint32_t subsystem_vendor; /* Subsystem ID's or PCI_ANY_ID */ - rt_uint32_t subsystem_device; /* Subsystem ID's or PCI_ANY_ID */ - rt_uint32_t class, class_mask; /* (class, subclass, prog-if) triplet */ + rt_uint32_t vendor, device; /**< Vendor and device ID (or PCI_ANY_ID for wildcard) */ + rt_uint32_t subsystem_vendor; /**< Subsystem vendor ID (or PCI_ANY_ID) */ + rt_uint32_t subsystem_device; /**< Subsystem device ID (or PCI_ANY_ID) */ + rt_uint32_t class, class_mask; /**< (class, subclass, prog-if) triplet and mask */ - const void *data; + const void *data; /**< Driver-private data associated with this ID */ }; +/** + * @brief PCI device descriptor + * + * Represents a single PCI/PCIe device (endpoint or bridge) on a PCI bus. + * Contains all configuration space information, resource assignments, + * capability offsets, interrupt configuration, and MSI state. + */ struct rt_pci_device { - struct rt_device parent; - const char *name; - - rt_list_t list; - struct rt_pci_bus *bus; - struct rt_pci_bus *subbus; /* In PCI-to-PCI bridge, 'End Point' or 'Port' is NULL */ - - const struct rt_pci_device_id *id; - - rt_uint32_t devfn; /* Encoded device & function index */ - rt_uint16_t vendor; - rt_uint16_t device; - rt_uint16_t subsystem_vendor; - rt_uint16_t subsystem_device; - rt_uint32_t class; /* 3 bytes: (base, sub, prog-if) */ - rt_uint8_t revision; - rt_uint8_t hdr_type; - rt_uint8_t max_latency; - rt_uint8_t min_grantl; - rt_uint8_t int_pin; - rt_uint8_t int_line; - rt_uint16_t exp_flags; - rt_uint32_t cfg_size; - - void *sysdata; - - int irq; - rt_uint8_t pin; - struct rt_pic *intx_pic; - - rt_bool_t pm_enabled; - - struct rt_pci_bus_resource resource[RT_PCI_BAR_NR_MAX]; - struct rt_pci_bus_resource rom; - - rt_uint8_t pme_cap; - rt_uint8_t msi_cap; - rt_uint8_t msix_cap; - rt_uint8_t pcie_cap; - - rt_uint8_t busmaster:1; /* Is the bus master */ - rt_uint8_t multi_function:1; /* Multi-function device */ - rt_uint8_t ari_enabled:1; /* Alternative Routing-ID Interpretation */ - rt_uint8_t no_msi:1; /* May not use MSI */ - rt_uint8_t no_64bit_msi:1; /* May only use 32-bit MSIs */ - rt_uint8_t msi_enabled:1; /* MSI enable */ - rt_uint8_t msix_enabled:1; /* MSIx enable */ - rt_uint8_t broken_intx_masking:1; /* INTx masking can't be used */ - rt_uint8_t pme_support:5; /* Bitmask of states from which PME# can be generated */ + struct rt_device parent; /**< Inherited RT-Thread device */ + const char *name; /**< Device name (e.g., "0000:00:01.0") */ + + rt_list_t list; /**< Node in bus->devices_nodes */ + struct rt_pci_bus *bus; /**< Parent PCI bus */ + struct rt_pci_bus *subbus; /**< Subordinate bus for PCI-to-PCI bridges (NULL for endpoints) */ + + const struct rt_pci_device_id *id; /**< Matched device ID from driver table */ + + rt_uint32_t devfn; /**< Encoded device & function index */ + rt_uint16_t vendor; /**< PCI vendor ID */ + rt_uint16_t device; /**< PCI device ID */ + rt_uint16_t subsystem_vendor; /**< Subsystem vendor ID */ + rt_uint16_t subsystem_device; /**< Subsystem device ID */ + rt_uint32_t class; /**< 3 bytes: (base class, subclass, prog-if) */ + rt_uint8_t revision; /**< Revision ID */ + rt_uint8_t hdr_type; /**< Header type (PCIM_HDRTYPE_*) */ + rt_uint8_t max_latency; /**< Max latency timer value */ + rt_uint8_t min_grantl; /**< Min grant value for burst transfers */ + rt_uint8_t int_pin; /**< Interrupt pin (0=none, 1=INTA..4=INTD) */ + rt_uint8_t int_line; /**< Interrupt line (IRQ number assigned by BIOS/fw) */ + rt_uint16_t exp_flags; /**< PCI Express flags from PCIER_FLAGS register */ + rt_uint32_t cfg_size; /**< Configuration space size (256 or 4096 bytes) */ + + void *sysdata; /**< System-private data */ + + int irq; /**< Assigned IRQ number */ + rt_uint8_t pin; /**< INTx pin used for routing */ + struct rt_pic *intx_pic; /**< Interrupt controller for INTx */ + + rt_bool_t pm_enabled; /**< PME# is currently enabled */ + + struct rt_pci_bus_resource resource[RT_PCI_BAR_NR_MAX]; /**< BAR resources (6 standard BARs) */ + struct rt_pci_bus_resource rom; /**< Expansion ROM resource */ + + rt_uint8_t pme_cap; /**< PM capability offset */ + rt_uint8_t msi_cap; /**< MSI capability offset */ + rt_uint8_t msix_cap; /**< MSI-X capability offset */ + rt_uint8_t pcie_cap; /**< PCI Express capability offset */ + + rt_uint8_t busmaster : 1; /**< Bus master (DMA capable) is enabled */ + rt_uint8_t multi_function : 1; /**< Multi-function device (bit 7 of header type) */ + rt_uint8_t ari_enabled : 1; /**< Alternative Routing-ID Interpretation enabled */ + rt_uint8_t no_msi : 1; /**< MSI is not available for this device */ + rt_uint8_t no_64bit_msi : 1; /**< Device only supports 32-bit MSI addresses */ + rt_uint8_t msi_enabled : 1; /**< MSI is currently enabled */ + rt_uint8_t msix_enabled : 1; /**< MSI-X is currently enabled */ + rt_uint8_t broken_intx_masking : 1; /**< INTx masking does not work on this device */ + rt_uint8_t pme_support : 5; /**< Bitmask of power states from which PME# can be generated */ #ifdef RT_PCI_MSI - void *msix_base; - struct rt_pic *msi_pic; - rt_list_t msi_desc_nodes; - struct rt_spinlock msi_lock; + void *msix_base; /**< Virtual address of the MSI-X table */ + struct rt_pic *msi_pic; /**< MSI interrupt controller for this device */ + rt_list_t msi_desc_nodes; /**< List of MSI/MSI-X descriptors */ + struct rt_spinlock msi_lock; /**< Spinlock for MSI descriptor operations */ #endif }; +/** + * @brief PCI host bridge descriptor + * + * Represents the PCI host bridge (root complex). Manages bus address + * regions, DMA regions, IRQ routing, and the root PCI bus. + */ struct rt_pci_host_bridge { - struct rt_device parent; + struct rt_device parent; /**< Inherited RT-Thread device */ - rt_uint32_t domain; + rt_uint32_t domain; /**< PCI domain number */ - struct rt_pci_bus *root_bus; - const struct rt_pci_ops *ops; - const struct rt_pci_ops *child_ops; + struct rt_pci_bus *root_bus; /**< Root PCI bus */ + const struct rt_pci_ops *ops; /**< Root bus operations */ + const struct rt_pci_ops *child_ops; /**< Child bus operations */ - rt_uint32_t bus_range[2]; - rt_size_t bus_regions_nr; - struct rt_pci_bus_region *bus_regions; - rt_size_t dma_regions_nr; - struct rt_pci_bus_region *dma_regions; + rt_uint32_t bus_range[2]; /**< [start_bus, end_bus] range from device tree */ + rt_size_t bus_regions_nr; /**< Number of bus address regions */ + struct rt_pci_bus_region *bus_regions; /**< Bus address regions (CPU → PCI) */ + rt_size_t dma_regions_nr; /**< Number of DMA address regions */ + struct rt_pci_bus_region *dma_regions; /**< DMA address regions (PCI → CPU) */ - rt_uint8_t (*irq_slot)(struct rt_pci_device *pdev, rt_uint8_t *pinp); - int (*irq_map)(struct rt_pci_device *pdev, rt_uint8_t slot, rt_uint8_t pin); + rt_uint8_t (*irq_slot)(struct rt_pci_device *pdev, rt_uint8_t *pinp); /**< INTx swizzling callback */ + int (*irq_map)(struct rt_pci_device *pdev, rt_uint8_t slot, rt_uint8_t pin); /**< IRQ mapping callback */ - void *sysdata; - rt_uint8_t priv[0]; + void *sysdata; /**< System-private data */ + rt_uint8_t priv[0]; /**< Variable-length private data */ }; +/** @brief Cast a rt_device to its containing rt_pci_host_bridge */ #define rt_device_to_pci_host_bridge(dev) rt_container_of(dev, struct rt_pci_host_bridge, parent) +/** + * @brief PCI bus operations vtable + * + * Each PCI bus (root bus or child bus) has its own set of operations + * for configuration space access and bus lifecycle management. + */ struct rt_pci_ops { - rt_err_t (*add)(struct rt_pci_bus *bus); - rt_err_t (*remove)(struct rt_pci_bus *bus); + rt_err_t (*add)(struct rt_pci_bus *bus); /**< Called when a new bus is added */ + rt_err_t (*remove)(struct rt_pci_bus *bus); /**< Called when a bus is being removed */ - void *(*map)(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg); + void *(*map)(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg); /**< Map config space register to virtual addr */ rt_err_t (*read)(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); /**< Read from config space */ rt_err_t (*write)(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t value); /**< Write to config space */ }; +/** + * @brief PCI bus descriptor + * + * Each PCI bus in the topology is represented by this structure. + * Buses form a tree: root bus → bridge bus → child bus. + * The union self/host_bridge disambiguates root buses from + * PCI-to-PCI bridge buses. + */ struct rt_pci_bus { - rt_list_t list; - rt_list_t children_nodes; - rt_list_t devices_nodes; - struct rt_pci_bus *parent; + rt_list_t list; /**< Node in parent's children_nodes */ + rt_list_t children_nodes; /**< List of child buses */ + rt_list_t devices_nodes; /**< List of devices on this bus */ + struct rt_pci_bus *parent; /**< Parent bus (NULL for root bus) */ union { /* In PCI-to-PCI bridge, parent is not NULL */ - struct rt_pci_device *self; + struct rt_pci_device *self; /**< Bridge device that created this bus */ /* In Host bridge, this is Root bus ('PCI Bus 0') */ - struct rt_pci_host_bridge *host_bridge; + struct rt_pci_host_bridge *host_bridge; /**< Host bridge for root bus */ }; - const struct rt_pci_ops *ops; + const struct rt_pci_ops *ops; /**< Bus-level PCI operations */ - char name[48]; - char number; - struct rt_spinlock lock; + char name[48]; /**< Bus name (e.g., "0000:00") */ + char number; /**< Bus number (0-255) */ + struct rt_spinlock lock; /**< Spinlock for bus-level operations */ - void *sysdata; + void *sysdata; /**< System-private data (e.g., ECAM config window) */ }; +/** + * @brief PCI driver descriptor + * + * Each PCI device driver provides this structure, including a device ID + * table for matching and probe/remove/shutdown callbacks. + */ struct rt_pci_driver { - struct rt_driver parent; + struct rt_driver parent; /**< Inherited RT-Thread driver */ - const char *name; - const struct rt_pci_device_id *ids; + const char *name; /**< Driver name */ + const struct rt_pci_device_id *ids; /**< Device ID table (terminated by sentinel) */ - rt_err_t (*probe)(struct rt_pci_device *pdev); - rt_err_t (*remove)(struct rt_pci_device *pdev); - rt_err_t (*shutdown)(struct rt_pci_device *pdev); + rt_err_t (*probe)(struct rt_pci_device *pdev); /**< Probe callback: initialize a matched device */ + rt_err_t (*remove)(struct rt_pci_device *pdev); /**< Remove callback: deinitialize a device */ + rt_err_t (*shutdown)(struct rt_pci_device *pdev); /**< Shutdown callback: prepare for power-off */ }; +/** + * @brief MSI-X entry specification for vector allocation + * + * Used when allocating MSI-X vectors to specify which table entries + * to use. On return, irq is filled with the assigned IRQ number. + */ struct rt_pci_msix_entry { - int irq; - int index; + int irq; /**< Assigned IRQ number (filled on return) */ + int index; /**< Requested MSI-X table entry index */ }; +/** + * @brief PCI device power states (D0-D3) + * + * Standard PCI power management states. D0 is fully operational; + * D3cold is the deepest sleep with no auxiliary power. + */ enum rt_pci_power { - RT_PCI_D0, - RT_PCI_D1, - RT_PCI_D2, - RT_PCI_D3HOT, - RT_PCI_D3COLD, + RT_PCI_D0, /**< Fully operational */ + RT_PCI_D1, /**< Light sleep */ + RT_PCI_D2, /**< Medium sleep */ + RT_PCI_D3HOT, /**< Deep sleep, auxiliary power available */ + RT_PCI_D3COLD, /**< Deep sleep, no auxiliary power (Vcc removed) */ - RT_PCI_PME_MAX, + RT_PCI_PME_MAX, /**< Sentinel: number of power states */ }; +/* === PME (Power Management Event) API === */ + +/** @brief Initialize PME capability for a device */ void rt_pci_pme_init(struct rt_pci_device *pdev); + +/** @brief Activate or deactivate PME# signal and manage power domain */ void rt_pci_pme_active(struct rt_pci_device *pdev, rt_bool_t enable); + +/** @brief Enable or disable device wake-up from a given power state */ rt_err_t rt_pci_enable_wake(struct rt_pci_device *pci_dev, - enum rt_pci_power state, rt_bool_t enable); + enum rt_pci_power state, rt_bool_t enable); + +/** + * @brief Check if a device can generate PME# from a given power state + * + * @param[in] pdev PCI device + * @param[in] state Power state to check + * + * @return RT_TRUE if PME# can be generated from this state + */ rt_inline rt_bool_t rt_pci_pme_capable(struct rt_pci_device *pdev, - enum rt_pci_power state) + enum rt_pci_power state) { if (!pdev->pme_cap) { @@ -290,145 +400,275 @@ rt_inline rt_bool_t rt_pci_pme_capable(struct rt_pci_device *pdev, return !!(pdev->pme_support & (1 << state)); } +/* === MSI Initialization === */ + +/** @brief Initialize MSI capability (disables MSI if enabled) */ void rt_pci_msi_init(struct rt_pci_device *pdev); + +/** @brief Initialize MSI-X capability (disables MSI-X if enabled) */ void rt_pci_msix_init(struct rt_pci_device *pdev); +/* === Bus Master Control === */ + +/** @brief Enable bus mastering (DMA capability) */ void rt_pci_set_master(struct rt_pci_device *pdev); + +/** @brief Disable bus mastering */ void rt_pci_clear_master(struct rt_pci_device *pdev); +/* === Host Bridge Lifecycle === */ + +/** @brief Allocate a PCI host bridge structure with private data space */ struct rt_pci_host_bridge *rt_pci_host_bridge_alloc(rt_size_t priv_size); + +/** @brief Free a host bridge and its associated bus/DMA regions */ rt_err_t rt_pci_host_bridge_free(struct rt_pci_host_bridge *); + +/** @brief Initialize a host bridge from device tree data */ rt_err_t rt_pci_host_bridge_init(struct rt_pci_host_bridge *host_bridge); + +/** @brief Probe a host bridge: register root bus and scan for devices */ rt_err_t rt_pci_host_bridge_probe(struct rt_pci_host_bridge *host_bridge); +/* === Device Probing === */ + +/** @brief Allocate and initialize a new PCI device on a bus */ struct rt_pci_device *rt_pci_alloc_device(struct rt_pci_bus *bus); + +/** @brief Scan and probe a single PCI device at a given devfn */ struct rt_pci_device *rt_pci_scan_single_device(struct rt_pci_bus *bus, rt_uint32_t devfn); + +/** @brief Complete PCI device setup: read config, allocate BARs, init capabilities */ rt_err_t rt_pci_setup_device(struct rt_pci_device *pdev); + +/** @brief Scan all functions in a PCI slot */ rt_size_t rt_pci_scan_slot(struct rt_pci_bus *bus, rt_uint32_t devfn); + +/** @brief Recursively scan child buses with a bus count limit */ rt_uint32_t rt_pci_scan_child_buses(struct rt_pci_bus *bus, rt_size_t buses); + +/** @brief Scan all child buses of a PCI bus (unlimited buses) */ rt_uint32_t rt_pci_scan_child_bus(struct rt_pci_bus *bus); +/* === Bus Management === */ + +/** @brief Register a host bridge's root bus */ rt_err_t rt_pci_host_bridge_register(struct rt_pci_host_bridge *host_bridge); + +/** @brief Register a host bridge and scan its root bus */ rt_err_t rt_pci_scan_root_bus_bridge(struct rt_pci_host_bridge *host_bridge); +/* === Teardown === */ + +/** @brief Remove a host bridge and all its devices */ rt_err_t rt_pci_host_bridge_remove(struct rt_pci_host_bridge *host_bridge); + +/** @brief Remove a PCI bus (only if it has no children or devices) */ rt_err_t rt_pci_bus_remove(struct rt_pci_bus *bus); + +/** @brief Remove a PCI device from its bus */ rt_err_t rt_pci_device_remove(struct rt_pci_device *pdev); +/* === Domain and Bus Access === */ + +/** @brief Get the PCI domain number for a device */ rt_uint32_t rt_pci_domain(struct rt_pci_device *pdev); +/* === Capability Discovery === */ + +/** @brief Find a standard PCI capability by ID on a bus/devfn */ rt_uint8_t rt_pci_bus_find_capability(struct rt_pci_bus *bus, rt_uint32_t devfn, int cap); + +/** @brief Find a standard PCI capability on a device */ rt_uint8_t rt_pci_find_capability(struct rt_pci_device *pdev, int cap); + +/** @brief Find the next instance of a capability (continue from a previous find) */ rt_uint8_t rt_pci_find_next_capability(struct rt_pci_device *pdev, rt_uint8_t pos, int cap); +/** @brief Find a PCIe extended capability on a device */ rt_uint16_t rt_pci_find_ext_capability(struct rt_pci_device *pdev, int cap); + +/** @brief Find the next PCIe extended capability starting from a given position */ rt_uint16_t rt_pci_find_ext_next_capability(struct rt_pci_device *pdev, rt_uint16_t pos, int cap); +/* === Bus Hierarchy === */ + +/** @brief Find the root PCI bus by walking up the parent chain */ struct rt_pci_bus *rt_pci_find_root_bus(struct rt_pci_bus *bus); + +/** @brief Find the host bridge for a given PCI bus */ struct rt_pci_host_bridge *rt_pci_find_host_bridge(struct rt_pci_bus *bus); +/** + * @brief Get the 16-bit Requester ID for a PCI device + * + * RID = (bus << 8) | devfn, used for MSI mapping and DMA routing. + * + * @param[in] pdev PCI device + * + * @return Requester ID + */ rt_inline rt_uint16_t rt_pci_dev_id(struct rt_pci_device *pdev) { return RT_PCI_DEVID(pdev->bus->number, pdev->devfn); } +/** + * @brief Check if a PCI bus is the root bus + * + * @param[in] bus PCI bus + * + * @return RT_TRUE if this is the root bus (no parent) + */ rt_inline rt_bool_t rt_pci_is_root_bus(struct rt_pci_bus *bus) { return bus->parent ? RT_FALSE : RT_TRUE; } +/** + * @brief Check if a PCI device is a bridge (PCI-to-PCI or CardBus) + * + * @param[in] pdev PCI device + * + * @return RT_TRUE if the device is a bridge + */ rt_inline rt_bool_t rt_pci_is_bridge(struct rt_pci_device *pdev) { return pdev->hdr_type == PCIM_HDRTYPE_BRIDGE || - pdev->hdr_type == PCIM_HDRTYPE_CARDBUS; + pdev->hdr_type == PCIM_HDRTYPE_CARDBUS; } +/** + * @brief Check if a PCI device supports PCI Express + * + * @param[in] pdev PCI device + * + * @return RT_TRUE if the PCIe capability was found + */ rt_inline rt_bool_t rt_pci_is_pcie(struct rt_pci_device *pdev) { return !!pdev->pcie_cap; } +/** @brief Iterate over all bridge devices on a PCI bus */ #define rt_pci_foreach_bridge(pdev, bus) \ - rt_list_for_each_entry(pdev, &bus->devices_nodes, list) \ - if (rt_pci_is_bridge(pdev)) + rt_list_for_each_entry(pdev, &bus->devices_nodes, list) if (rt_pci_is_bridge(pdev)) + +/* === Configuration Space Access === */ +/** @brief Read a byte from PCI config space via bus operations */ rt_err_t rt_pci_bus_read_config_u8(struct rt_pci_bus *bus, - rt_uint32_t devfn, int pos, rt_uint8_t *value); + rt_uint32_t devfn, int pos, rt_uint8_t *value); + +/** @brief Read a halfword from PCI config space via bus operations */ rt_err_t rt_pci_bus_read_config_u16(struct rt_pci_bus *bus, - rt_uint32_t devfn, int pos, rt_uint16_t *value); + rt_uint32_t devfn, int pos, rt_uint16_t *value); + +/** @brief Read a word from PCI config space via bus operations */ rt_err_t rt_pci_bus_read_config_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int pos, rt_uint32_t *value); + rt_uint32_t devfn, int pos, rt_uint32_t *value); +/** @brief Write a byte to PCI config space via bus operations */ rt_err_t rt_pci_bus_write_config_u8(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, rt_uint8_t value); + rt_uint32_t devfn, int reg, rt_uint8_t value); + +/** @brief Write a halfword to PCI config space via bus operations */ rt_err_t rt_pci_bus_write_config_u16(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, rt_uint16_t value); + rt_uint32_t devfn, int reg, rt_uint16_t value); + +/** @brief Write a word to PCI config space via bus operations */ rt_err_t rt_pci_bus_write_config_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, rt_uint32_t value); + rt_uint32_t devfn, int reg, rt_uint32_t value); +/** @brief Read from config space with arbitrary width (memory-mapped access) */ rt_err_t rt_pci_bus_read_config_uxx(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); + +/** @brief Write to config space with arbitrary width (memory-mapped access) */ rt_err_t rt_pci_bus_write_config_uxx(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t value); +/** @brief Read with 32-bit access and sub-word extraction (for 32-bit-only controllers) */ rt_err_t rt_pci_bus_read_config_generic_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value); + +/** @brief Write with read-modify-write for sub-32-bit access (for 32-bit-only controllers) */ rt_err_t rt_pci_bus_write_config_generic_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value); + rt_uint32_t devfn, int reg, int width, rt_uint32_t value); + +/* === Configuration Space Access (Device-Level Convenience) === */ +/** @brief Read a byte from a PCI device's config space */ rt_inline rt_err_t rt_pci_read_config_u8(const struct rt_pci_device *pdev, - int reg, rt_uint8_t *value) + int reg, rt_uint8_t *value) { return rt_pci_bus_read_config_u8(pdev->bus, pdev->devfn, reg, value); } +/** @brief Read a halfword from a PCI device's config space */ rt_inline rt_err_t rt_pci_read_config_u16(const struct rt_pci_device *pdev, - int reg, rt_uint16_t *value) + int reg, rt_uint16_t *value) { return rt_pci_bus_read_config_u16(pdev->bus, pdev->devfn, reg, value); } +/** @brief Read a word from a PCI device's config space */ rt_inline rt_err_t rt_pci_read_config_u32(const struct rt_pci_device *pdev, - int reg, rt_uint32_t *value) + int reg, rt_uint32_t *value) { return rt_pci_bus_read_config_u32(pdev->bus, pdev->devfn, reg, value); } +/** @brief Write a byte to a PCI device's config space */ rt_inline rt_err_t rt_pci_write_config_u8(const struct rt_pci_device *pdev, - int reg, rt_uint8_t value) + int reg, rt_uint8_t value) { return rt_pci_bus_write_config_u8(pdev->bus, pdev->devfn, reg, value); } +/** @brief Write a halfword to a PCI device's config space */ rt_inline rt_err_t rt_pci_write_config_u16(const struct rt_pci_device *pdev, - int reg, rt_uint16_t value) + int reg, rt_uint16_t value) { return rt_pci_bus_write_config_u16(pdev->bus, pdev->devfn, reg, value); } +/** @brief Write a word to a PCI device's config space */ rt_inline rt_err_t rt_pci_write_config_u32(const struct rt_pci_device *pdev, - int reg, rt_uint32_t value) + int reg, rt_uint32_t value) { return rt_pci_bus_write_config_u32(pdev->bus, pdev->devfn, reg, value); } +/* === Device Tree Integration === */ + #ifdef RT_USING_OFW +/** @brief Parse and map an IRQ for a PCI device from device tree */ int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev, - rt_uint8_t slot, rt_uint8_t pin); + rt_uint8_t slot, rt_uint8_t pin); +/** @brief Parse PCI bus address ranges from device tree */ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge); + struct rt_pci_host_bridge *host_bridge); +/** @brief Initialize a host bridge from device tree data */ rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge); + struct rt_pci_host_bridge *host_bridge); +/** @brief Initialize OFW data for a PCI bus */ rt_err_t rt_pci_ofw_bus_init(struct rt_pci_bus *bus); + +/** @brief Free OFW data for a PCI bus */ rt_err_t rt_pci_ofw_bus_free(struct rt_pci_bus *bus); + +/** @brief Associate a device tree node with an enumerated PCI device */ rt_err_t rt_pci_ofw_device_init(struct rt_pci_device *pdev); + +/** @brief Release the device tree node reference for a PCI device */ rt_err_t rt_pci_ofw_device_free(struct rt_pci_device *pdev); #else rt_inline rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge) + struct rt_pci_host_bridge *host_bridge) { return RT_EOK; } @@ -449,17 +689,27 @@ rt_inline rt_err_t rt_pci_ofw_device_free(struct rt_pci_device *pdev) return RT_EOK; } rt_inline int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev, - rt_uint8_t slot, rt_uint8_t pin) + rt_uint8_t slot, rt_uint8_t pin) { return -1; } rt_inline rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge) + struct rt_pci_host_bridge *host_bridge) { return -RT_ENOSYS; } #endif /* RT_USING_OFW */ +/** + * @brief I/O-map a PCI device's BAR for CPU access + * + * Maps the physical BAR address range into kernel virtual address space. + * + * @param[in] pdev PCI device + * @param[in] bar_idx BAR index (0-5) + * + * @return Kernel virtual address of the mapped BAR + */ rt_inline void *rt_pci_iomap(struct rt_pci_device *pdev, int bar_idx) { struct rt_pci_bus_resource *res = &pdev->resource[bar_idx]; @@ -469,42 +719,80 @@ rt_inline void *rt_pci_iomap(struct rt_pci_device *pdev, int bar_idx) return rt_ioremap((void *)res->base, res->size); } +/* === INTx Interrupt Routing === */ + +/** @brief Perform INTx swizzling for a single bridge level */ rt_uint8_t rt_pci_irq_intx(struct rt_pci_device *pdev, rt_uint8_t pin); + +/** @brief Perform INTx swizzling through all bridge levels */ rt_uint8_t rt_pci_irq_slot(struct rt_pci_device *pdev, rt_uint8_t *pinp); +/* === IRQ Management === */ + +/** @brief Assign an IRQ to a PCI device (reads pin, swizzles, maps, writes int_line) */ void rt_pci_assign_irq(struct rt_pci_device *pdev); +/** @brief Enable or disable legacy INTx interrupts */ void rt_pci_intx(struct rt_pci_device *pdev, rt_bool_t enable); + +/** @brief Check if device generated an INTx and mask it (for interrupt handlers) */ rt_bool_t rt_pci_check_and_mask_intx(struct rt_pci_device *pdev); + +/** @brief Check interrupt status and unmask INTx */ rt_bool_t rt_pci_check_and_unmask_intx(struct rt_pci_device *pdev); +/** @brief Mask a device's interrupt at the controller level */ void rt_pci_irq_mask(struct rt_pci_device *pdev); + +/** @brief Unmask a device's interrupt at the controller level */ void rt_pci_irq_unmask(struct rt_pci_device *pdev); -#define RT_PCI_IRQ_F_LEGACY RT_BIT(0) /* Allow legacy interrupts */ -#define RT_PCI_IRQ_F_MSI RT_BIT(1) /* Allow MSI interrupts */ -#define RT_PCI_IRQ_F_MSIX RT_BIT(2) /* Allow MSI-X interrupts */ -#define RT_PCI_IRQ_F_AFFINITY RT_BIT(3) /* Auto-assign affinity */ -#define RT_PCI_IRQ_F_ALL_TYPES (RT_PCI_IRQ_F_LEGACY | RT_PCI_IRQ_F_MSI | RT_PCI_IRQ_F_MSIX) +/* === IRQ Allocation Flags === */ + +/** @brief Allow legacy INTx interrupts */ +#define RT_PCI_IRQ_F_LEGACY RT_BIT(0) +/** @brief Allow MSI interrupts */ +#define RT_PCI_IRQ_F_MSI RT_BIT(1) +/** @brief Allow MSI-X interrupts */ +#define RT_PCI_IRQ_F_MSIX RT_BIT(2) +/** @brief Auto-assign CPU affinity */ +#define RT_PCI_IRQ_F_AFFINITY RT_BIT(3) +/** @brief All interrupt types combined */ +#define RT_PCI_IRQ_F_ALL_TYPES (RT_PCI_IRQ_F_LEGACY | RT_PCI_IRQ_F_MSI | RT_PCI_IRQ_F_MSIX) + +/* === MSI/MSI-X Vector Allocation === */ #ifdef RT_PCI_MSI +/** @brief Allocate interrupt vectors for a PCI device (MSI-X > MSI > INTx fallback) */ rt_ssize_t rt_pci_alloc_vector(struct rt_pci_device *pdev, int min, int max, - rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))); + rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))); + +/** @brief Free all vectors allocated for a PCI device */ void rt_pci_free_vector(struct rt_pci_device *pdev); +/** @brief Get the number of MSI vectors the device supports */ rt_ssize_t rt_pci_msi_vector_count(struct rt_pci_device *pdev); + +/** @brief Disable MSI on a device */ rt_err_t rt_pci_msi_disable(struct rt_pci_device *pdev); + +/** @brief Enable MSI with a range of vectors and CPU affinity */ rt_ssize_t rt_pci_msi_enable_range_affinity(struct rt_pci_device *pdev, - int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))); + int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))); +/** @brief Get the number of MSI-X table entries the device supports */ rt_ssize_t rt_pci_msix_vector_count(struct rt_pci_device *pdev); + +/** @brief Disable MSI-X on a device */ rt_err_t rt_pci_msix_disable(struct rt_pci_device *pdev); + +/** @brief Enable MSI-X with a range of vectors and CPU affinity */ rt_ssize_t rt_pci_msix_enable_range_affinity(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int min, int max, - RT_IRQ_AFFINITY_DECLARE((*affinities))); + struct rt_pci_msix_entry *entries, int min, int max, + RT_IRQ_AFFINITY_DECLARE((*affinities))); #else rt_inline rt_ssize_t rt_pci_alloc_vector(struct rt_pci_device *pdev, int min, int max, - rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))) + rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))) { return -RT_ENOSYS; } @@ -525,7 +813,7 @@ rt_inline rt_err_t rt_pci_msi_disable(struct rt_pci_device *pdev) } rt_inline rt_ssize_t rt_pci_msi_enable_range_affinity(struct rt_pci_device *pdev, - int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))) + int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))) { return -RT_ENOSYS; } @@ -541,15 +829,21 @@ rt_inline rt_err_t rt_pci_msix_disable(struct rt_pci_device *pdev) } rt_inline rt_ssize_t rt_pci_msix_enable_range_affinity(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int min, int max, - RT_IRQ_AFFINITY_DECLARE((*affinities))) + struct rt_pci_msix_entry *entries, int min, int max, + RT_IRQ_AFFINITY_DECLARE((*affinities))) { return -RT_ENOSYS; } #endif /* RT_PCI_MSI */ +/** + * @brief Initialize MSI-X entries with linear (sequential) indexing + * + * @param[in] entries MSI-X entry array + * @param[in] nvectors Number of entries + */ rt_inline void rt_pci_msix_entry_index_linear(struct rt_pci_msix_entry *entries, - rt_size_t nvectors) + rt_size_t nvectors) { for (int i = 0; i < nvectors; ++i) { @@ -557,51 +851,109 @@ rt_inline void rt_pci_msix_entry_index_linear(struct rt_pci_msix_entry *entries, } } +/** + * @brief Enable MSI with a range of vectors (no affinity) + * + * @param[in] pdev PCI device + * @param[in] min Minimum vectors + * @param[in] max Maximum vectors + * + * @return Number of vectors on success, negative error otherwise + */ rt_inline rt_ssize_t rt_pci_msi_enable_range(struct rt_pci_device *pdev, - int min, int max) + int min, int max) { return rt_pci_msi_enable_range_affinity(pdev, min, max, RT_NULL); } +/** + * @brief Enable a single MSI vector + * + * @param[in] pdev PCI device + * + * @return RT_EOK on success + */ rt_inline rt_err_t rt_pci_msi_enable(struct rt_pci_device *pdev) { rt_ssize_t res = rt_pci_msi_enable_range(pdev, 1, 1); return res == 1 ? res : RT_EOK; } +/** + * @brief Enable MSI-X with a range of vectors (no affinity) + * + * @param[in] pdev PCI device + * @param[in] entries MSI-X entry specifications + * @param[in] min Minimum vectors + * @param[in] max Maximum vectors + * + * @return Number of vectors on success, negative error otherwise + */ rt_inline rt_ssize_t rt_pci_msix_enable_range(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int min, int max) + struct rt_pci_msix_entry *entries, int min, int max) { return rt_pci_msix_enable_range_affinity(pdev, entries, min, max, RT_NULL); } +/** + * @brief Enable MSI-X with exactly count vectors (no affinity) + * + * @param[in] pdev PCI device + * @param[in] entries MSI-X entry specifications + * @param[in] count Exact number of vectors + * + * @return Count on success, negative error otherwise + */ rt_inline rt_ssize_t rt_pci_msix_enable(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int count) + struct rt_pci_msix_entry *entries, int count) { return rt_pci_msix_enable_range(pdev, entries, count, count); } +/* === Resource Management === */ + +/** @brief Initialize bus resource regions with safe starting addresses */ rt_err_t rt_pci_region_setup(struct rt_pci_host_bridge *host_bridge); + +/** @brief Allocate bus address space from the host bridge's regions */ struct rt_pci_bus_region *rt_pci_region_alloc(struct rt_pci_host_bridge *host_bridge, - void **out_addr, rt_size_t size, rt_ubase_t flags, rt_bool_t mem64); + void **out_addr, rt_size_t size, rt_ubase_t flags, rt_bool_t mem64); +/** @brief Allocate BAR and ROM resources for a device */ rt_err_t rt_pci_device_alloc_resource(struct rt_pci_host_bridge *host_bridge, - struct rt_pci_device *pdev); + struct rt_pci_device *pdev); + +/* === Bus Enumeration === */ +/** @brief Enumerate all PCI devices in a bus hierarchy with a callback */ void rt_pci_enum_device(struct rt_pci_bus *bus, - rt_bool_t (callback(struct rt_pci_device *, void *)), void *data); + rt_bool_t(callback(struct rt_pci_device *, void *)), void *data); +/* === Driver Matching === */ + +/** @brief Match a PCI device against a single device ID entry */ const struct rt_pci_device_id *rt_pci_match_id(struct rt_pci_device *pdev, - const struct rt_pci_device_id *id); + const struct rt_pci_device_id *id); +/** @brief Match a PCI device against an array of device IDs */ const struct rt_pci_device_id *rt_pci_match_ids(struct rt_pci_device *pdev, - const struct rt_pci_device_id *ids); + const struct rt_pci_device_id *ids); + +/* === Registration === */ +/** @brief Register a PCI driver */ rt_err_t rt_pci_driver_register(struct rt_pci_driver *pdrv); + +/** @brief Register a PCI device on the PCI bus */ rt_err_t rt_pci_device_register(struct rt_pci_device *pdev); -struct rt_pci_bus_resource *rt_pci_find_bar(struct rt_pci_device* pdev,rt_ubase_t flags,int index); -#define RT_PCI_DRIVER_EXPORT(driver) RT_DRIVER_EXPORT(driver, pci, BUILIN) +/** @brief Find a BAR resource by type flags and index */ +struct rt_pci_bus_resource *rt_pci_find_bar(struct rt_pci_device *pdev, rt_ubase_t flags, int index); + +/** @brief Export a PCI driver (built-in only) */ +#define RT_PCI_DRIVER_EXPORT(driver) RT_DRIVER_EXPORT(driver, pci, BUILIN) + +/** @brief Global PCI configuration space spinlock */ extern struct rt_spinlock rt_pci_lock; #endif /* __PCI_H__ */ diff --git a/components/drivers/include/drivers/pci_endpoint.h b/components/drivers/include/drivers/pci_endpoint.h index c43c182d958..9a36b71d937 100644 --- a/components/drivers/include/drivers/pci_endpoint.h +++ b/components/drivers/include/drivers/pci_endpoint.h @@ -8,196 +8,434 @@ * 2022-08-25 GuEe-GUI first version */ +/** + * @file pci_endpoint.h + * @brief PCI Endpoint (EP) framework public API + * + * The PCI Endpoint framework allows an RT-Thread device to operate as + * a PCIe endpoint (rather than a root complex). This is essential for + * embedded systems that need to appear as PCIe devices to a host CPU. + * + * Provides APIs for: + * - Writing the PCI configuration space header (vendor/device IDs, BARs) + * - Setting up BARs for exposing local memory to the host + * - Address mapping (CPU → PCI bus address space) + * - MSI/MSI-X configuration and interrupt raising to the host + * - Starting and stopping endpoint operation + * - Managing endpoint functions (EPFs) + * - Memory allocation from the endpoint's local pool + */ + #ifndef __PCI_ENDPOINT_H__ #define __PCI_ENDPOINT_H__ #include +/** + * @brief PCI endpoint INTx pin selection + * + * Selects which legacy INTx line (INTA through INTD) the endpoint + * function drives when raising a legacy interrupt. + */ enum rt_pci_ep_pin { - RT_PCI_EP_PIN_UNKNOWN, - RT_PCI_EP_PIN_INTA, - RT_PCI_EP_PIN_INTB, - RT_PCI_EP_PIN_INTC, - RT_PCI_EP_PIN_INTD, + RT_PCI_EP_PIN_UNKNOWN, /**< Unknown / not configured */ + RT_PCI_EP_PIN_INTA, /**< INTA# (pin 1) */ + RT_PCI_EP_PIN_INTB, /**< INTB# (pin 2) */ + RT_PCI_EP_PIN_INTC, /**< INTC# (pin 3) */ + RT_PCI_EP_PIN_INTD, /**< INTD# (pin 4) */ }; +/** + * @brief PCI endpoint interrupt type + * + * Specifies which interrupt mechanism to use when raising an + * interrupt from the endpoint to the PCIe host. + */ enum rt_pci_ep_irq { - RT_PCI_EP_IRQ_UNKNOWN, - RT_PCI_EP_IRQ_LEGACY, - RT_PCI_EP_IRQ_MSI, - RT_PCI_EP_IRQ_MSIX, + RT_PCI_EP_IRQ_UNKNOWN, /**< Unknown type */ + RT_PCI_EP_IRQ_LEGACY, /**< Legacy INTx (level-triggered, shared) */ + RT_PCI_EP_IRQ_MSI, /**< MSI (Message Signaled Interrupt) */ + RT_PCI_EP_IRQ_MSIX, /**< MSI-X (extended message signaled interrupt) */ }; +/** + * @brief PCI endpoint configuration header + * + * Contains the standard PCI configuration space header fields that + * identify the endpoint to the host system. Written via + * rt_pci_ep_write_header() before starting the endpoint. + */ struct rt_pci_ep_header { - rt_uint16_t vendor; - rt_uint16_t device; - rt_uint8_t revision; - rt_uint8_t progif; - rt_uint8_t subclass; - rt_uint8_t class_code; - rt_uint8_t cache_line_size; - rt_uint16_t subsystem_vendor; - rt_uint16_t subsystem_device; - - enum rt_pci_ep_pin intx; + rt_uint16_t vendor; /**< PCI vendor ID */ + rt_uint16_t device; /**< PCI device ID */ + rt_uint8_t revision; /**< Revision ID */ + rt_uint8_t progif; /**< Programming interface */ + rt_uint8_t subclass; /**< Subclass code */ + rt_uint8_t class_code; /**< Base class code */ + rt_uint8_t cache_line_size; /**< Cache line size in DWORDs */ + rt_uint16_t subsystem_vendor; /**< Subsystem vendor ID */ + rt_uint16_t subsystem_device; /**< Subsystem device ID */ + + enum rt_pci_ep_pin intx; /**< INTx pin to assert for legacy interrupts */ }; +/** + * @brief PCI endpoint BAR descriptor + * + * Describes a Base Address Register on the endpoint. bus contains + * the PCI-side resource (flags, base, size); cpu_addr is the + * local CPU physical address that backs the BAR. + */ struct rt_pci_ep_bar { /* To PCI Bus */ - struct rt_pci_bus_resource bus; + struct rt_pci_bus_resource bus; /**< PCI-side resource (flags, base, size) */ /* To CPU */ - rt_ubase_t cpu_addr; + rt_ubase_t cpu_addr; /**< Local CPU physical address backing the BAR */ }; -/* - * Type of MSI-X table, For more format detail, - * please read `components/drivers/include/drivers/pci_msi.h` +/** + * @brief MSI-X table entry for endpoints + * + * Each MSI-X table entry contains a 64-bit message address, + * 32-bit message data, and a 32-bit vector control field. + * For more format detail, see pci_msi.h. */ struct rt_pci_ep_msix_tbl { union { - rt_uint64_t msg_addr; + rt_uint64_t msg_addr; /**< Full 64-bit message address */ struct { - rt_uint32_t msg_addr_upper; - rt_uint32_t msg_addr_lower; + rt_uint32_t msg_addr_upper; /**< Upper 32 bits of message address */ + rt_uint32_t msg_addr_lower; /**< Lower 32 bits of message address */ }; }; - rt_uint32_t msg_data; - rt_uint32_t vector_ctrl; + rt_uint32_t msg_data; /**< Message data (identifies the interrupt) */ + rt_uint32_t vector_ctrl; /**< Vector control (bit 0 = mask) */ }; struct rt_pci_ep_ops; struct rt_pci_ep_mem; +/** + * @brief PCI endpoint controller descriptor + * + * Represents a single PCIe endpoint controller instance. Each controller + * can support multiple functions, each with its own configuration space. + * Reference counting (ref) ensures safe concurrent access. + */ struct rt_pci_ep { - rt_list_t list; - const char *name; + rt_list_t list; /**< Node in the global endpoint list */ + const char *name; /**< Controller name (matches platform device) */ - struct rt_ref ref; + struct rt_ref ref; /**< Reference count for safe lifecycle management */ - const struct rt_device *rc_dev; - const struct rt_pci_ep_ops *ops; + const struct rt_device *rc_dev; /**< Parent root complex device */ + const struct rt_pci_ep_ops *ops; /**< Controller operations vtable */ - rt_size_t mems_nr; - struct rt_pci_ep_mem *mems; + rt_size_t mems_nr; /**< Number of memory regions */ + struct rt_pci_ep_mem *mems; /**< Array of memory region descriptors */ - rt_uint8_t max_functions; - RT_BITMAP_DECLARE(functions_map, 8); - rt_list_t epf_nodes; - struct rt_mutex lock; + rt_uint8_t max_functions; /**< Maximum number of functions supported */ + RT_BITMAP_DECLARE(functions_map, 8); /**< Bitmap of allocated function numbers */ + rt_list_t epf_nodes; /**< List of registered endpoint functions */ + struct rt_mutex lock; /**< Mutex for thread-safe operations */ - void *priv; + void *priv; /**< Controller driver private data */ }; +/** + * @brief PCI endpoint memory region descriptor + * + * Describes a contiguous physical memory region that the endpoint + * controller can use for BAR backing, MSI/MSI-X memory, and DMA buffers. + * A bitmap tracks free/used pages within the region. + */ struct rt_pci_ep_mem { - rt_ubase_t cpu_addr; - rt_size_t size; - rt_size_t page_size; + rt_ubase_t cpu_addr; /**< CPU physical base address of the region */ + rt_size_t size; /**< Total size of the region in bytes */ + rt_size_t page_size; /**< Allocation granularity (page size) in bytes */ - rt_bitmap_t *map; - rt_size_t bits; + rt_bitmap_t *map; /**< Page allocation bitmap (1 bit per page) */ + rt_size_t bits; /**< Total number of pages in this region */ }; +/** + * @brief PCI endpoint function descriptor + * + * An endpoint function represents one logical PCI function on the + * endpoint controller. Each function has its own configuration header, + * BARs, and MSI/MSI-X settings. + */ struct rt_pci_epf { - rt_list_t list; - const char *name; + rt_list_t list; /**< Node in the endpoint's epf_nodes list */ + const char *name; /**< Function name (from device tree) */ - struct rt_pci_ep_header *header; - struct rt_pci_ep_bar bar[PCI_STD_NUM_BARS]; + struct rt_pci_ep_header *header; /**< Configuration header to present to the host */ + struct rt_pci_ep_bar bar[PCI_STD_NUM_BARS]; /**< BAR descriptors (6 standard BARs) */ - rt_uint8_t msi_interrupts; - rt_uint16_t msix_interrupts; - rt_uint8_t func_no; + rt_uint8_t msi_interrupts; /**< Number of MSI vectors requested */ + rt_uint16_t msix_interrupts; /**< Number of MSI-X vectors requested */ + rt_uint8_t func_no; /**< Function number (0 to max_functions-1) */ - struct rt_pci_ep *ep; + struct rt_pci_ep *ep; /**< Parent endpoint controller */ }; +/** + * @brief PCI endpoint controller operations vtable + * + * Each endpoint controller driver provides implementations of these + * operations. The framework calls them under the endpoint's mutex lock. + */ struct rt_pci_ep_ops { + /** + * @brief Write the PCI configuration header for an endpoint function + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] hdr Header data to program + * + * @return RT_EOK on success + */ rt_err_t (*write_header)(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_header *hdr); - + struct rt_pci_ep_header *hdr); + + /** + * @brief Set a BAR on an endpoint function + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] bar BAR configuration (flags, base, size) + * @param[in] bar_idx BAR index (0-5) + * + * @return RT_EOK on success + */ rt_err_t (*set_bar)(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx); + struct rt_pci_ep_bar *bar, int bar_idx); + + /** + * @brief Clear a BAR on an endpoint function + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] bar BAR to clear + * @param[in] bar_idx BAR index + * + * @return RT_EOK on success + */ rt_err_t (*clear_bar)(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx); - + struct rt_pci_ep_bar *bar, int bar_idx); + + /** + * @brief Map a CPU address to PCI bus address space + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Mapping size in bytes + * + * @return RT_EOK on success + */ rt_err_t (*map_addr)(struct rt_pci_ep *ep, rt_uint8_t func_no, - rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size); + rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size); + + /** + * @brief Unmap a previously mapped address + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address to unmap + * + * @return RT_EOK on success + */ rt_err_t (*unmap_addr)(struct rt_pci_ep *ep, rt_uint8_t func_no, rt_ubase_t addr); + /** + * @brief Configure MSI Multi-Message Enable for an endpoint function + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] irq_nr Log2 of number of MSI vectors + * + * @return RT_EOK on success + */ rt_err_t (*set_msi)(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr); + unsigned irq_nr); + + /** + * @brief Get the current MSI configuration + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI vectors enabled + * + * @return RT_EOK on success + */ rt_err_t (*get_msi)(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr); - + unsigned *out_irq_nr); + + /** + * @brief Configure MSI-X for an endpoint function + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] irq_nr Number of MSI-X vectors + * @param[in] bar_idx BAR index for the MSI-X table + * @param[in] offset Offset within the BAR for the table + * + * @return RT_EOK on success + */ rt_err_t (*set_msix)(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr, int bar_idx, rt_off_t offset); + unsigned irq_nr, int bar_idx, rt_off_t offset); + + /** + * @brief Get the current MSI-X configuration + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI-X vectors enabled + * + * @return RT_EOK on success + */ rt_err_t (*get_msix)(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr); - + unsigned *out_irq_nr); + + /** + * @brief Raise an interrupt from the endpoint to the host + * + * @param[in] ep Endpoint controller + * @param[in] func_no Function number + * @param[in] type Interrupt type (LEGACY, MSI, or MSIX) + * @param[in] irq Vector number (1-based for MSI/MSI-X) + * + * @return RT_EOK on success + */ rt_err_t (*raise_irq)(struct rt_pci_ep *ep, rt_uint8_t func_no, - enum rt_pci_ep_irq type, unsigned irq); - + enum rt_pci_ep_irq type, unsigned irq); + + /** + * @brief Start the endpoint — make it visible on the PCIe bus + * + * @param[in] ep Endpoint controller + * + * @return RT_EOK on success + */ rt_err_t (*start)(struct rt_pci_ep *ep); + + /** + * @brief Stop the endpoint — remove it from the PCIe bus + * + * @param[in] ep Endpoint controller + * + * @return RT_EOK on success + */ rt_err_t (*stop)(struct rt_pci_ep *ep); }; +/* === Endpoint Configuration API === */ + +/** @brief Write PCI configuration header for an endpoint function */ rt_err_t rt_pci_ep_write_header(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_header *hdr); + struct rt_pci_ep_header *hdr); +/** @brief Set a BAR on an endpoint function */ rt_err_t rt_pci_ep_set_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx); + struct rt_pci_ep_bar *bar, int bar_idx); + +/** @brief Clear a BAR on an endpoint function */ rt_err_t rt_pci_ep_clear_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx); + struct rt_pci_ep_bar *bar, int bar_idx); + +/* === Address Mapping API === */ +/** @brief Map CPU address to PCI bus address space for an endpoint */ rt_err_t rt_pci_ep_map_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, - rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size); + rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size); + +/** @brief Unmap a previously mapped address */ rt_err_t rt_pci_ep_unmap_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, - rt_ubase_t addr); + rt_ubase_t addr); +/* === MSI/MSI-X API === */ + +/** @brief Configure MSI for an endpoint function */ rt_err_t rt_pci_ep_set_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr); + unsigned irq_nr); + +/** @brief Get current MSI configuration */ rt_err_t rt_pci_ep_get_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr); + unsigned *out_irq_nr); +/** @brief Configure MSI-X for an endpoint function */ rt_err_t rt_pci_ep_set_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr, int bar_idx, rt_off_t offset); + unsigned irq_nr, int bar_idx, rt_off_t offset); + +/** @brief Get current MSI-X configuration */ rt_err_t rt_pci_ep_get_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr); + unsigned *out_irq_nr); +/* === Interrupt and Control API === */ + +/** @brief Raise an interrupt from the endpoint to the host */ rt_err_t rt_pci_ep_raise_irq(struct rt_pci_ep *ep, rt_uint8_t func_no, - enum rt_pci_ep_irq type, unsigned irq); + enum rt_pci_ep_irq type, unsigned irq); +/** @brief Start the endpoint — enable link and respond to config requests */ rt_err_t rt_pci_ep_start(struct rt_pci_ep *ep); + +/** @brief Stop the endpoint — disable link */ rt_err_t rt_pci_ep_stop(struct rt_pci_ep *ep); +/* === Lifecycle API === */ + +/** @brief Register an endpoint controller with the framework */ rt_err_t rt_pci_ep_register(struct rt_pci_ep *ep); + +/** @brief Unregister an endpoint controller (fails if in use) */ rt_err_t rt_pci_ep_unregister(struct rt_pci_ep *ep); +/* === Memory API === */ + +/** @brief Initialize an array of memory regions for the endpoint */ rt_err_t rt_pci_ep_mem_array_init(struct rt_pci_ep *ep, - struct rt_pci_ep_mem *mems, rt_size_t mems_nr); + struct rt_pci_ep_mem *mems, rt_size_t mems_nr); + +/** @brief Initialize a single memory region (convenience wrapper) */ rt_err_t rt_pci_ep_mem_init(struct rt_pci_ep *ep, - rt_ubase_t cpu_addr, rt_size_t size, rt_size_t page_size); + rt_ubase_t cpu_addr, rt_size_t size, rt_size_t page_size); +/** @brief Allocate memory from the endpoint's pool */ void *rt_pci_ep_mem_alloc(struct rt_pci_ep *ep, - rt_ubase_t *out_cpu_addr, rt_size_t size); + rt_ubase_t *out_cpu_addr, rt_size_t size); + +/** @brief Free memory back to the endpoint's pool */ void rt_pci_ep_mem_free(struct rt_pci_ep *ep, - void *vaddr, rt_ubase_t cpu_addr, rt_size_t size); + void *vaddr, rt_ubase_t cpu_addr, rt_size_t size); + +/* === Endpoint Function (EPF) API === */ +/** @brief Add an endpoint function to a controller */ rt_err_t rt_pci_ep_add_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf); + +/** @brief Remove an endpoint function from a controller */ rt_err_t rt_pci_ep_remove_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf); +/* === Lookup API === */ + +/** @brief Get a reference to an endpoint controller by name */ struct rt_pci_ep *rt_pci_ep_get(const char *name); + +/** @brief Release a reference to an endpoint controller */ void rt_pci_ep_put(struct rt_pci_ep *ep); #endif /* __PCI_ENDPOINT_H__ */ diff --git a/components/drivers/include/drivers/pci_msi.h b/components/drivers/include/drivers/pci_msi.h index 4fe03ca07a0..26e0963eed7 100644 --- a/components/drivers/include/drivers/pci_msi.h +++ b/components/drivers/include/drivers/pci_msi.h @@ -8,6 +8,15 @@ * 2022-08-25 GuEe-GUI first version */ +/** + * @file pci_msi.h + * @brief PCI MSI (Message Signaled Interrupts) and MSI-X data structures + * + * Defines the configuration descriptors and message format for MSI + * and MSI-X interrupt mechanisms. MSI uses config-space registers; + * MSI-X uses a memory-mapped table in a device BAR. + */ + #ifndef __PCI_MSI_H__ #define __PCI_MSI_H__ @@ -51,19 +60,26 @@ * +--------------------------------------- Per-Vector Masking Capable */ +/** + * @brief MSI capability configuration + * + * Holds the per-vector mask state, the mask register offset, the + * default INTx IRQ (for fallback when MSI is disabled), and the + * capability flags parsed from the MSI Message Control register. + */ struct rt_pci_msi_conf { - rt_uint32_t mask; - rt_uint8_t mask_pos; - int default_irq; + rt_uint32_t mask; /**< Current vector mask bits */ + rt_uint8_t mask_pos; /**< Mask register offset in config space */ + int default_irq; /**< Default INTx IRQ for fallback on MSI shutdown */ struct { - rt_uint8_t is_masking:1; - rt_uint8_t is_64bit:1; - rt_uint8_t multi_msg_max:3; /* log2 num of messages allocated */ - rt_uint8_t multi_msg_use:3; /* log2 num of messages supported */ - } cap; + rt_uint8_t is_masking : 1; /**< Per-Vector Masking Capable (bit 8) */ + rt_uint8_t is_64bit : 1; /**< 64-bit Address Capable (bit 7) */ + rt_uint8_t multi_msg_max : 3; /**< Multiple Message Capable — log2 of max vectors (bits 3:1) */ + rt_uint8_t multi_msg_use : 3; /**< Multiple Message Enable — log2 of vectors in use (bits 6:4) */ + } cap; /**< Capability flags parsed from Message Control register */ }; /* @@ -120,71 +136,105 @@ struct rt_pci_msi_conf * +-------------------------------+ */ +/** + * @brief MSI-X per-vector configuration + * + * Tracks a single MSI-X table entry's index, vector control value, + * and the virtual address of the table in MMIO space. + */ struct rt_pci_msix_conf { - int index; - - rt_uint32_t msg_ctrl; - void *table_base; + int index; /**< Table entry index (0-based) */ + rt_uint32_t msg_ctrl; /**< Saved vector control register value */ + void *table_base; /**< Virtual address of the MSI-X table */ }; +/** + * @brief MSI/MSI-X message descriptor + * + * An MSI interrupt is essentially a posted memory write transaction. + * The address (32 or 64 bits) and data values are programmed by the + * interrupt controller (MSI PIC) and written to the device. + */ struct rt_pci_msi_msg { - rt_uint32_t address_lo; - rt_uint32_t address_hi; - rt_uint32_t data; + rt_uint32_t address_lo; /**< Lower 32 bits of the message address */ + rt_uint32_t address_hi; /**< Upper 32 bits of the message address (0 for 32-bit) */ + rt_uint32_t data; /**< Message data (identifies the interrupt vector) */ }; +/** + * @brief MSI/MSI-X interrupt descriptor + * + * One descriptor per MSI group or per MSI-X table entry. + * Stores the allocated IRQ number(s), CPU affinity information, + * the composed MSI message, and capability-specific configuration. + */ struct rt_pci_msi_desc { - rt_list_t list; + rt_list_t list; /**< Node in the device's msi_desc_nodes list */ - int irq; - rt_size_t vector_used; - rt_size_t vector_count; + int irq; /**< Base IRQ number for MSI, or per-entry IRQ for MSI-X */ + rt_size_t vector_used; /**< Number of vectors currently in use */ + rt_size_t vector_count; /**< Total number of vectors this descriptor can handle */ union { - /* For MSI-X */ + /* For MSI-X: single CPU affinity bitmap */ rt_bitmap_t *affinity; - /* For MSI */ + /* For MSI: array of per-vector affinity bitmaps */ rt_bitmap_t **affinities; }; - struct rt_pci_device *pdev; - struct rt_pci_msi_msg msg; + struct rt_pci_device *pdev; /**< Owning PCI device */ + struct rt_pci_msi_msg msg; /**< Current MSI message (address + data) */ - void *write_msi_msg_data; - void (*write_msi_msg)(struct rt_pci_msi_desc *, void *); + void *write_msi_msg_data; /**< User data for write_msi_msg callback */ + void (*write_msi_msg)(struct rt_pci_msi_desc *, void *); /**< Custom MSI message write callback */ - rt_bool_t is_msix; + rt_bool_t is_msix; /**< RT_TRUE for MSI-X, RT_FALSE for MSI */ union { - struct rt_pci_msi_conf msi; - struct rt_pci_msix_conf msix; + struct rt_pci_msi_conf msi; /**< MSI-specific config (when !is_msix) */ + struct rt_pci_msix_conf msix; /**< MSI-X-specific config (when is_msix) */ }; - void *priv; + void *priv; /**< PIC-private data */ }; +/** @brief Get the first MSI descriptor for a device, or RT_NULL if none */ #define rt_pci_msi_first_desc(pdev) \ - (rt_list_isempty(&(pdev)->msi_desc_nodes) ? RT_NULL : \ - rt_list_first_entry(&(pdev)->msi_desc_nodes, struct rt_pci_msi_desc, list)) + (rt_list_isempty(&(pdev)->msi_desc_nodes) ? RT_NULL : rt_list_first_entry(&(pdev)->msi_desc_nodes, struct rt_pci_msi_desc, list)) +/** @brief Iterate over all MSI descriptors for a device */ #define rt_pci_msi_for_each_desc(pdev, desc) \ rt_list_for_each_entry(desc, &(pdev)->msi_desc_nodes, list) +/** @brief Compute MSI-X table size from the Table Size field (N-1 encoding) */ #define rt_pci_msix_table_size(flags) ((flags & PCIM_MSIXCTRL_TABLE_SIZE) + 1) +/** @brief Set up MSI/MSI-X IRQs for a device */ rt_err_t rt_pci_msi_setup_irqs(struct rt_pci_device *pdev, int nvec, int type); + +/** @brief Clean up (free) all MSI/MSI-X IRQs for a device */ rt_err_t rt_pci_msi_cleanup_irqs(struct rt_pci_device *pdev); +/** @brief Shutdown MSI: disable MSI, re-enable INTx */ void rt_pci_msi_shutdown(struct rt_pci_device *pdev); + +/** @brief Shutdown MSI-X: mask all vectors, disable MSI-X, re-enable INTx */ void rt_pci_msix_shutdown(struct rt_pci_device *pdev); + +/** @brief Free all IRQ resources for MSI/MSI-X on a device */ void rt_pci_msi_free_irqs(struct rt_pci_device *pdev); + +/** @brief Write an MSI/MSI-X message to the device (address + data registers) */ void rt_pci_msi_write_msg(struct rt_pci_msi_desc *desc, struct rt_pci_msi_msg *msg); +/** @brief Mask an MSI/MSI-X IRQ at the device level */ void rt_pci_msi_mask_irq(struct rt_pic_irq *pirq); + +/** @brief Unmask an MSI/MSI-X IRQ at the device level */ void rt_pci_msi_unmask_irq(struct rt_pic_irq *pirq); #endif /* __PCI_MSI_H__ */ diff --git a/components/drivers/pci/access.c b/components/drivers/pci/access.c old mode 100755 new mode 100644 index 7b026c8da96..350db51a681 --- a/components/drivers/pci/access.c +++ b/components/drivers/pci/access.c @@ -8,44 +8,94 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file access.c + * @brief PCI Configuration Space Access Layer + * + * Provides standardized read/write access to PCI configuration space + * through bus ops (ECAM, DW PCIe, etc.). Includes lock-protected + * config access, memory-mapped access helpers, and generic 32-bit + * access with sub-word extraction/merging. + * + * Configuration space locking can be disabled at compile time with + * RT_PCI_LOCKLESS for single-CPU or carefully managed systems. + */ + #include #include #include +/** @brief Global PCI configuration space spinlock */ struct rt_spinlock rt_pci_lock = { 0 }; #ifdef RT_PCI_LOCKLESS -#define pci_lock_config(l) do { (void)(l); } while (0) -#define pci_unlock_config(l) do { (void)(l); } while (0) +#define pci_lock_config(l) \ + do \ + { \ + (void)(l); \ + } while (0) +#define pci_unlock_config(l) \ + do \ + { \ + (void)(l); \ + } while (0) #else -#define pci_lock_config(l) l = rt_spin_lock_irqsave(&rt_pci_lock) -#define pci_unlock_config(l) rt_spin_unlock_irqrestore(&rt_pci_lock, l) +/** @brief Acquire the PCI config space lock with IRQ save */ +#define pci_lock_config(l) l = rt_spin_lock_irqsave(&rt_pci_lock) +/** @brief Release the PCI config space lock with IRQ restore */ +#define pci_unlock_config(l) rt_spin_unlock_irqrestore(&rt_pci_lock, l) #endif -#define PCI_OPS_READ(name, type) \ -rt_err_t rt_pci_bus_read_config_##name(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg, type *value) \ -{ \ - rt_err_t err; \ - rt_ubase_t level; \ - rt_uint32_t data = 0; \ - pci_lock_config(level); \ - err = bus->ops->read(bus, devfn, reg, sizeof(type), &data); \ - *value = err ? (type)(~0) : (type)data; \ - pci_unlock_config(level); \ - return err; \ -} +/** + * @def PCI_OPS_READ(name, type) + * @brief Generate a locked config-space read function + * + * Reads a value of the specified width from PCI config space + * through the bus operations, protected by the PCI config lock. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[out] value Read value (all-ones on error) + * @return RT_EOK on success, error code otherwise + */ +#define PCI_OPS_READ(name, type) \ + rt_err_t rt_pci_bus_read_config_##name(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg, type *value) \ + { \ + rt_err_t err; \ + rt_ubase_t level; \ + rt_uint32_t data = 0; \ + pci_lock_config(level); \ + err = bus->ops->read(bus, devfn, reg, sizeof(type), &data); \ + *value = err ? (type)(~0) : (type)data; \ + pci_unlock_config(level); \ + return err; \ + } -#define PCI_OPS_WRITE(name, type) \ -rt_err_t rt_pci_bus_write_config_##name(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg, type value) \ -{ \ - rt_err_t err; \ - rt_ubase_t level; \ - pci_lock_config(level); \ - err = bus->ops->write(bus, devfn, reg, sizeof(type), value); \ - pci_unlock_config(level); \ - return err; \ -} +/** + * @def PCI_OPS_WRITE(name, type) + * @brief Generate a locked config-space write function + * + * Writes a value of the specified width to PCI config space + * through the bus operations, protected by the PCI config lock. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[in] value Value to write + * @return RT_EOK on success, error code otherwise + */ +#define PCI_OPS_WRITE(name, type) \ + rt_err_t rt_pci_bus_write_config_##name(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg, type value) \ + { \ + rt_err_t err; \ + rt_ubase_t level; \ + pci_lock_config(level); \ + err = bus->ops->write(bus, devfn, reg, sizeof(type), value); \ + pci_unlock_config(level); \ + return err; \ + } #define PCI_OPS(name, type) \ PCI_OPS_READ(name, type) \ @@ -59,8 +109,21 @@ PCI_OPS(u32, rt_uint32_t) #undef PCI_OP_READ #undef PCI_OPS +/** + * @brief Read from PCI config space using memory-mapped access (any width) + * + * Uses bus->ops->map() to get a direct MMIO pointer to the config + * register, then reads the appropriate width (1/2/4 bytes). + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[in] width Access width (1, 2, or 4 bytes) + * @param[out] value Read value + * @return RT_EOK on success, -RT_ERROR if mapping failed + */ rt_err_t rt_pci_bus_read_config_uxx(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) { void *base; @@ -85,8 +148,18 @@ rt_err_t rt_pci_bus_read_config_uxx(struct rt_pci_bus *bus, return -RT_ERROR; } +/** + * @brief Write to PCI config space using memory-mapped access (any width) + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[in] width Access width (1, 2, or 4 bytes) + * @param[in] value Value to write + * @return RT_EOK on success, -RT_ERROR if mapping failed + */ rt_err_t rt_pci_bus_write_config_uxx(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t value) { void *base; @@ -111,8 +184,21 @@ rt_err_t rt_pci_bus_write_config_uxx(struct rt_pci_bus *bus, return -RT_ERROR; } +/** + * @brief Read from PCI config space using generic 32-bit access with sub-word extraction + * + * Always reads a full 32-bit word and extracts the requested bytes. + * Required for platforms where the bus ops only support 32-bit access. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[in] width Access width (1 or 2 bytes; 4 also works) + * @param[out] value Read value (correctly shifted and masked) + * @return RT_EOK on success, -RT_ERROR if mapping failed + */ rt_err_t rt_pci_bus_read_config_generic_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) { void *base; @@ -131,8 +217,22 @@ rt_err_t rt_pci_bus_read_config_generic_u32(struct rt_pci_bus *bus, return -RT_ERROR; } +/** + * @brief Write to PCI config space using generic 32-bit access with sub-word merging + * + * Uses a read-modify-write cycle for sub-32-bit writes to preserve + * surrounding bytes. Required for platforms that only support 32-bit + * config space access. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Configuration register offset + * @param[in] width Access width (1, 2, or 4 bytes) + * @param[in] value Value to write + * @return RT_EOK on success, -RT_ERROR if mapping failed + */ rt_err_t rt_pci_bus_write_config_generic_u32(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t value) { void *base; diff --git a/components/drivers/pci/ecam.c b/components/drivers/pci/ecam.c index f5813f3409b..67095634018 100644 --- a/components/drivers/pci/ecam.c +++ b/components/drivers/pci/ecam.c @@ -8,6 +8,18 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file ecam.c + * @brief PCI Express Enhanced Configuration Access Mechanism (ECAM) implementation + * + * ECAM maps PCIe configuration space directly into memory-mapped I/O. + * Each bus gets a 1MB window, each device gets 4KB within that window. + * + * Key formula: offset = bus << 20 | device << 15 | function << 12 | register + * + * This is the standard access method for PCI Express root complexes. + */ + #include #include @@ -17,8 +29,18 @@ #include "ecam.h" +/** + * @brief Create an ECAM configuration window for a host bridge + * + * Associates the ECAM operations with the host bridge and sets up + * the bus range and bus shift for address calculation. + * + * @param[in] host_bridge PCI host bridge to attach ECAM to + * @param[in] ops ECAM operations descriptor (bus_shift + pci_ops) + * @return New ECAM config window, or RT_NULL on allocation failure + */ struct pci_ecam_config_window *pci_ecam_create(struct rt_pci_host_bridge *host_bridge, - const struct pci_ecam_ops *ops) + const struct pci_ecam_ops *ops) { struct pci_ecam_config_window *conf_win = rt_calloc(1, sizeof(*conf_win)); @@ -36,6 +58,20 @@ struct pci_ecam_config_window *pci_ecam_create(struct rt_pci_host_bridge *host_b return conf_win; } +/** + * @brief Map a PCI config space register to a virtual address via ECAM + * + * Calculates the MMIO address for a given (bus, devfn, register) tuple. + * Supports both standard ECAM (20-bit bus shift) and CAM (16-bit bus shift) + * addressing modes. + * + * The bus number is offset relative to the starting bus in the bus_range. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] where Configuration register offset + * @return Virtual address for direct MMIO access + */ void *pci_ecam_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int where) { struct pci_ecam_config_window *conf_win = bus->sysdata; @@ -61,10 +97,9 @@ void *pci_ecam_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int where) return map; } -const struct pci_ecam_ops pci_generic_ecam_ops = -{ - .pci_ops = - { +/** @brief Standard ECAM operations using direct MMIO read/write */ +const struct pci_ecam_ops pci_generic_ecam_ops = { + .pci_ops = { .map = pci_ecam_map, .read = rt_pci_bus_read_config_uxx, .write = rt_pci_bus_write_config_uxx, diff --git a/components/drivers/pci/ecam.h b/components/drivers/pci/ecam.h index 98fa3eb4fc7..fa63661b8ed 100644 --- a/components/drivers/pci/ecam.h +++ b/components/drivers/pci/ecam.h @@ -8,6 +8,21 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file ecam.h + * @brief PCI Express ECAM (Enhanced Configuration Access Mechanism) definitions + * + * PCI Express Base Specification, Revision 5.0, Section 7.2.2 defines + * the ECAM addressing scheme for mapping configuration space into + * memory-mapped I/O regions. + * + * Standard ECAM layout: + * - Bus Number: bits [27:20] (256 buses) + * - Device Num: bits [19:15] (32 devices) + * - Function: bits [14:12] (8 functions) + * - Register: bits [11:0] (4KB config space) + */ + #ifndef __RT_PCI_ECAM_H__ #define __RT_PCI_ECAM_H__ @@ -16,54 +31,73 @@ #include #include -/* - * Memory address shift values for the byte-level address that - * can be used when accessing the PCI Express Configuration Space. - */ +/** @brief Bus number shift in ECAM address (20 bits for PCIe) */ +#define PCIE_ECAM_BUS_SHIFT 20 +/** @brief Device/Function number shift in ECAM address */ +#define PCIE_ECAM_DEVFN_SHIFT 12 -/* - * Enhanced Configuration Access Mechanism (ECAM) - * - * See PCI Express Base Specification, Revision 5.0, Version 1.0, - * Section 7.2.2, Table 7-1, p. 677. - */ -#define PCIE_ECAM_BUS_SHIFT 20 /* Bus number */ -#define PCIE_ECAM_DEVFN_SHIFT 12 /* Device and Function number */ +/** @brief Mask for bus number field (8 bits, 256 buses) */ +#define PCIE_ECAM_BUS_MASK 0xff +/** @brief Mask for device/function field (8 bits, 32 dev × 8 func) */ +#define PCIE_ECAM_DEVFN_MASK 0xff +/** @brief Mask for register offset (12 bits, 4KB max) */ +#define PCIE_ECAM_REG_MASK 0xfff -#define PCIE_ECAM_BUS_MASK 0xff -#define PCIE_ECAM_DEVFN_MASK 0xff -#define PCIE_ECAM_REG_MASK 0xfff /* Limit offset to a maximum of 4K */ - -#define PCIE_ECAM_BUS(x) (((x) & PCIE_ECAM_BUS_MASK) << PCIE_ECAM_BUS_SHIFT) -#define PCIE_ECAM_DEVFN(x) (((x) & PCIE_ECAM_DEVFN_MASK) << PCIE_ECAM_DEVFN_SHIFT) -#define PCIE_ECAM_REG(x) ((x) & PCIE_ECAM_REG_MASK) +/** @brief Extract bus portion of ECAM address */ +#define PCIE_ECAM_BUS(x) (((x) & PCIE_ECAM_BUS_MASK) << PCIE_ECAM_BUS_SHIFT) +/** @brief Extract devfn portion of ECAM address */ +#define PCIE_ECAM_DEVFN(x) (((x) & PCIE_ECAM_DEVFN_MASK) << PCIE_ECAM_DEVFN_SHIFT) +/** @brief Extract register portion of ECAM address */ +#define PCIE_ECAM_REG(x) ((x) & PCIE_ECAM_REG_MASK) +/** @brief Compute full ECAM MMIO offset from bus, devfn, and register */ #define PCIE_ECAM_OFFSET(bus, devfn, where) \ - (PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEVFN(devfn) | PCIE_ECAM_REG(where)) + (PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEVFN(devfn) | PCIE_ECAM_REG(where)) +/** + * @brief ECAM operations descriptor + * + * Each ECAM variant may have a different bus_shift. + * - Standard ECAM: bus_shift = 20 + * - CAM (legacy PCI): bus_shift = 16 + */ struct pci_ecam_ops { - rt_uint32_t bus_shift; - const struct rt_pci_ops pci_ops; + rt_uint32_t bus_shift; /**< Bus number shift for address calculation */ + const struct rt_pci_ops pci_ops; /**< PCI bus operations (map, read, write) */ }; +/** + * @brief ECAM configuration window instance + * + * Represents one ECAM memory window covering a bus range. + * The window maps a contiguous MMIO region to the configuration + * space of all devices on the buses in bus_range. + */ struct pci_ecam_config_window { - rt_uint32_t *bus_range; - rt_uint32_t bus_shift; + rt_uint32_t *bus_range; /**< [start_bus, end_bus] for this window */ + rt_uint32_t bus_shift; /**< Bus number shift (20 for ECAM, 16 for CAM) */ - void *win; - void *priv; - const struct pci_ecam_ops *ops; + void *win; /**< Virtual base address of the ECAM MMIO window */ + void *priv; /**< Private data (typically the host bridge) */ + const struct pci_ecam_ops *ops; /**< ECAM operations (bus_shift + pci_ops) */ }; -/* Default ECAM ops */ +/** @brief Standard ECAM operations (20-bit bus shift, direct MMIO) */ extern const struct pci_ecam_ops pci_generic_ecam_ops; +/** @brief Map a PCI config space register to a virtual address via ECAM */ void *pci_ecam_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int where); + +/** @brief Create an ECAM configuration window and attach it to a host bridge */ struct pci_ecam_config_window *pci_ecam_create(struct rt_pci_host_bridge *host_bridge, - const struct pci_ecam_ops *ops); + const struct pci_ecam_ops *ops); + +/** @brief Common probe function for ECAM-based PCI host controllers */ rt_err_t pci_host_common_probe(struct rt_platform_device *pdev); + +/** @brief Common remove function for ECAM-based PCI host controllers */ rt_err_t pci_host_common_remove(struct rt_platform_device *pdev); #endif /* __RT_PCI_ECAM_H__ */ diff --git a/components/drivers/pci/endpoint/endpoint.c b/components/drivers/pci/endpoint/endpoint.c index 70dbe2ad27e..d36c228c416 100644 --- a/components/drivers/pci/endpoint/endpoint.c +++ b/components/drivers/pci/endpoint/endpoint.c @@ -8,17 +8,47 @@ * 2022-08-25 GuEe-GUI first version */ +/** + * @file endpoint.c + * @brief PCI Endpoint (EP) framework core + * + * The PCI Endpoint framework allows RT-Thread devices to operate as + * PCIe endpoints rather than root complexes. This is essential for + * embedded systems that need to appear as PCIe devices to a host CPU. + * + * Provides APIs for: + * - Writing the PCI configuration space header (vendor/device IDs, BARs, etc.) + * - Setting up BARs (Base Address Registers) + * - Address mapping (CPU address → PCI bus address) + * - MSI/MSI-X configuration and IRQ raising + * - Starting and stopping the endpoint + * - Managing endpoint functions (EPF) + */ + #include #define DBG_TAG "pci.ep" #define DBG_LVL DBG_INFO #include +/** @brief Global list of registered PCI endpoints */ static rt_list_t _ep_nodes = RT_LIST_OBJECT_INIT(_ep_nodes); static RT_DEFINE_SPINLOCK(_ep_lock); +/** + * @brief Write the PCI configuration header for an endpoint function + * + * Sets vendor ID, device ID, revision, class code, subsystem IDs, + * and other standard PCI header fields that identify the endpoint + * to the host system. + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number (0 to max_functions-1) + * @param[in] hdr Header data to write + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_write_header(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_header *hdr) + struct rt_pci_ep_header *hdr) { rt_err_t err; @@ -43,8 +73,23 @@ rt_err_t rt_pci_ep_write_header(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Set a BAR for an endpoint function + * + * Configures a Base Address Register on the endpoint. The BAR exposes + * a region of local memory to the PCIe host. Validates that: + * - The last BAR (index 5) cannot be 64-bit + * - Size exceeding 32 bits requires 64-bit BAR type + * - I/O BARs have valid flag settings + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] bar BAR configuration (flags, base, size) + * @param[in] bar_idx BAR index (0-5) + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_set_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx) + struct rt_pci_ep_bar *bar, int bar_idx) { rt_err_t err = RT_EOK; @@ -96,8 +141,17 @@ rt_err_t rt_pci_ep_set_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Clear a BAR on an endpoint function + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] bar BAR configuration to clear + * @param[in] bar_idx BAR index + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_clear_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx) + struct rt_pci_ep_bar *bar, int bar_idx) { rt_err_t err; @@ -123,8 +177,18 @@ rt_err_t rt_pci_ep_clear_bar(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Map a CPU physical address to PCI bus address space for an endpoint + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Mapping size + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_map_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, - rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size) + rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size) { rt_err_t err; @@ -149,8 +213,16 @@ rt_err_t rt_pci_ep_map_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Unmap a previously mapped address + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address to unmap + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_unmap_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, - rt_ubase_t addr) + rt_ubase_t addr) { rt_err_t err; @@ -175,8 +247,19 @@ rt_err_t rt_pci_ep_unmap_addr(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Configure MSI for an endpoint function + * + * Sets the number of MSI vectors and the Multi-Message Enable value. + * The endpoint uses this to request MSI interrupts from the host. + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] irq_nr Number of MSI vectors + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_set_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr) + unsigned irq_nr) { rt_err_t err; @@ -209,8 +292,16 @@ rt_err_t rt_pci_ep_set_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Get the current MSI configuration + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI vectors configured + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_get_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr) + unsigned *out_irq_nr) { rt_err_t err; @@ -235,8 +326,21 @@ rt_err_t rt_pci_ep_get_msi(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Configure MSI-X for an endpoint function + * + * Sets up the MSI-X capability with the specified number of vectors + * and table location (BAR index + offset). + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] irq_nr Number of MSI-X vectors (max 2048) + * @param[in] bar_idx BAR index for the MSI-X table + * @param[in] offset Offset within the BAR for the table + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_set_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned irq_nr, int bar_idx, rt_off_t offset) + unsigned irq_nr, int bar_idx, rt_off_t offset) { rt_err_t err; @@ -262,8 +366,16 @@ rt_err_t rt_pci_ep_set_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Get the current MSI-X configuration + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI-X vectors configured + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_get_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, - unsigned *out_irq_nr) + unsigned *out_irq_nr) { rt_err_t err; @@ -288,8 +400,19 @@ rt_err_t rt_pci_ep_get_msix(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Raise an interrupt from the endpoint to the host + * + * Supports legacy INTx, MSI, and MSI-X interrupt types. + * + * @param[in] ep PCI endpoint controller + * @param[in] func_no Function number + * @param[in] type Interrupt type (RT_PCI_EP_IRQ_LEGACY, _MSI, or _MSIX) + * @param[in] irq Vector number (for MSI/MSI-X) + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_raise_irq(struct rt_pci_ep *ep, rt_uint8_t func_no, - enum rt_pci_ep_irq type, unsigned irq) + enum rt_pci_ep_irq type, unsigned irq) { rt_err_t err; @@ -314,6 +437,15 @@ rt_err_t rt_pci_ep_raise_irq(struct rt_pci_ep *ep, rt_uint8_t func_no, return err; } +/** + * @brief Start the PCI endpoint (make it visible on the PCIe bus) + * + * Typically called after all configuration is complete to enable + * the endpoint to respond to configuration requests from the host. + * + * @param[in] ep PCI endpoint controller + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_start(struct rt_pci_ep *ep) { rt_err_t err; @@ -339,6 +471,12 @@ rt_err_t rt_pci_ep_start(struct rt_pci_ep *ep) return err; } +/** + * @brief Stop the PCI endpoint + * + * @param[in] ep PCI endpoint controller + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_stop(struct rt_pci_ep *ep) { rt_err_t err; @@ -364,6 +502,15 @@ rt_err_t rt_pci_ep_stop(struct rt_pci_ep *ep) return err; } +/** + * @brief Register a PCI endpoint controller + * + * Adds the endpoint to the global endpoint list and initializes + * its internal structures (lock, function list, reference count). + * + * @param[in] ep PCI endpoint controller to register + * @return RT_EOK on success, -RT_EINVAL if ep or ep->ops is NULL + */ rt_err_t rt_pci_ep_register(struct rt_pci_ep *ep) { rt_ubase_t level; @@ -386,6 +533,15 @@ rt_err_t rt_pci_ep_register(struct rt_pci_ep *ep) return RT_EOK; } +/** + * @brief Unregister a PCI endpoint controller + * + * Removes the endpoint from the global list. Fails with -RT_EBUSY + * if the endpoint's reference count is > 1 (still in use). + * + * @param[in] ep PCI endpoint controller + * @return RT_EOK on success, -RT_EBUSY if still referenced + */ rt_err_t rt_pci_ep_unregister(struct rt_pci_ep *ep) { rt_ubase_t level; @@ -413,6 +569,17 @@ rt_err_t rt_pci_ep_unregister(struct rt_pci_ep *ep) return err; } +/** + * @brief Add an endpoint function (EPF) to an endpoint + * + * Associates a function with a specific function number. The function + * number must be within the endpoint's max_functions range and not + * already in use. + * + * @param[in] ep PCI endpoint controller + * @param[in] epf Endpoint function to add + * @return RT_EOK on success, -RT_EINVAL if function number is taken + */ rt_err_t rt_pci_ep_add_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf) { rt_err_t err = RT_EOK; @@ -425,7 +592,7 @@ rt_err_t rt_pci_ep_add_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf) if (epf->func_no > ep->max_functions - 1) { LOG_E("%s function No(%d) > %s max function No(%d - 1)", - epf->name, epf->func_no, ep->name, ep->max_functions); + epf->name, epf->func_no, ep->name, ep->max_functions); return -RT_EINVAL; } @@ -451,6 +618,13 @@ rt_err_t rt_pci_ep_add_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf) return err; } +/** + * @brief Remove an endpoint function from an endpoint + * + * @param[in] ep PCI endpoint controller + * @param[in] epf Endpoint function to remove + * @return RT_EOK + */ rt_err_t rt_pci_ep_remove_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf) { if (!ep || !epf) @@ -466,6 +640,15 @@ rt_err_t rt_pci_ep_remove_epf(struct rt_pci_ep *ep, struct rt_pci_epf *epf) return RT_EOK; } +/** + * @brief Get a reference to a PCI endpoint controller by name + * + * Increments the endpoint's reference count. The caller must call + * rt_pci_ep_put() when done. + * + * @param[in] name Endpoint name (or NULL to get the first one) + * @return Pointer to the endpoint, or RT_NULL if not found + */ struct rt_pci_ep *rt_pci_ep_get(const char *name) { rt_ubase_t level; @@ -488,6 +671,11 @@ struct rt_pci_ep *rt_pci_ep_get(const char *name) return ep; } +/** + * @brief Release callback: unregister the endpoint when refcount reaches zero + * + * @param[in] ref Reference counter + */ static void pci_ep_release(struct rt_ref *ref) { struct rt_pci_ep *ep = rt_container_of(ref, struct rt_pci_ep, ref); @@ -495,6 +683,14 @@ static void pci_ep_release(struct rt_ref *ref) rt_pci_ep_unregister(ep); } +/** + * @brief Release a reference to a PCI endpoint controller + * + * Decrements the reference count. If it reaches zero, the endpoint + * is unregistered. + * + * @param[in] ep PCI endpoint controller + */ void rt_pci_ep_put(struct rt_pci_ep *ep) { if (ep) diff --git a/components/drivers/pci/endpoint/mem.c b/components/drivers/pci/endpoint/mem.c index e4cd4d84bfd..62e26e0273a 100644 --- a/components/drivers/pci/endpoint/mem.c +++ b/components/drivers/pci/endpoint/mem.c @@ -8,14 +8,39 @@ * 2022-08-25 GuEe-GUI first version */ +/** + * @file mem.c + * @brief PCI Endpoint memory allocator + * + * Manages the endpoint's local memory pool used for exposing memory + * regions to the PCIe host. Uses a bitmap-based allocator to track + * free/used pages within the endpoint's address space. + * + * This is needed for: + * - BAR memory backing + * - MSI/MSI-X message memory + * - DMA buffer allocation visible to the host + */ + #include #define DBG_TAG "pci.ep.mem" #define DBG_LVL DBG_INFO #include +/** + * @brief Initialize an array of memory regions for an endpoint + * + * Each memory region is described by a CPU physical address, size, + * and page size. A bitmap is allocated for each region to track page usage. + * + * @param[in] ep PCI endpoint controller + * @param[in] mems Array of memory region descriptors + * @param[in] mems_nr Number of regions in the array + * @return RT_EOK on success, -RT_ENOMEM on allocation failure + */ rt_err_t rt_pci_ep_mem_array_init(struct rt_pci_ep *ep, - struct rt_pci_ep_mem *mems, rt_size_t mems_nr) + struct rt_pci_ep_mem *mems, rt_size_t mems_nr) { rt_size_t idx; rt_err_t err = RT_EOK; @@ -55,7 +80,7 @@ rt_err_t rt_pci_ep_mem_array_init(struct rt_pci_ep *ep, _out_lock: if (err) { - while (idx --> 0) + while (idx-- > 0) { rt_free(ep->mems[idx].map); } @@ -70,8 +95,19 @@ rt_err_t rt_pci_ep_mem_array_init(struct rt_pci_ep *ep, return err; } +/** + * @brief Initialize a single memory region for an endpoint + * + * Convenience wrapper around rt_pci_ep_mem_array_init() for a single region. + * + * @param[in] ep PCI endpoint controller + * @param[in] cpu_addr CPU physical base address of the region + * @param[in] size Total size of the region in bytes + * @param[in] page_size Allocation granularity (page size) in bytes + * @return RT_EOK on success + */ rt_err_t rt_pci_ep_mem_init(struct rt_pci_ep *ep, - rt_ubase_t cpu_addr, rt_size_t size, rt_size_t page_size) + rt_ubase_t cpu_addr, rt_size_t size, rt_size_t page_size) { struct rt_pci_ep_mem mem; @@ -87,6 +123,16 @@ rt_err_t rt_pci_ep_mem_init(struct rt_pci_ep *ep, return rt_pci_ep_mem_array_init(ep, &mem, 1); } +/** + * @brief Allocate a contiguous range of pages from a memory region + * + * Uses a first-fit bitmap scan. Each page is represented by one bit + * in the region's bitmap. Allocated pages are marked as used. + * + * @param[in] mem PCI EP memory region to allocate from + * @param[in] size Allocation size in bytes (must be page-aligned) + * @return CPU physical address of the allocated range, or ~0ULL on failure + */ static rt_ubase_t bitmap_region_alloc(struct rt_pci_ep_mem *mem, rt_size_t size) { rt_size_t bit, next_bit, end_bit, max_bits; @@ -109,22 +155,30 @@ static rt_ubase_t bitmap_region_alloc(struct rt_pci_ep_mem *mem, rt_size_t size) if (next_bit == end_bit) { - while (next_bit --> bit) + while (next_bit-- > bit) { rt_bitmap_set_bit(mem->map, next_bit); } return mem->cpu_addr + bit * mem->page_size; } - _next: - ; + _next:; } return ~0ULL; } +/** + * @brief Free a previously allocated range of pages + * + * Clears the bits in the region's bitmap for the freed pages. + * + * @param[in] mem PCI EP memory region + * @param[in] cpu_addr CPU physical address of the allocation + * @param[in] size Size of the allocation in bytes + */ static void bitmap_region_free(struct rt_pci_ep_mem *mem, - rt_ubase_t cpu_addr, rt_size_t size) + rt_ubase_t cpu_addr, rt_size_t size) { rt_size_t bit = (cpu_addr - mem->cpu_addr) / mem->page_size, end_bit; @@ -137,8 +191,20 @@ static void bitmap_region_free(struct rt_pci_ep_mem *mem, } } +/** + * @brief Allocate memory from the endpoint's pool + * + * Iterates through all registered memory regions and tries to + * allocate a contiguous range. On success, also maps the physical + * address to a kernel virtual address via rt_ioremap(). + * + * @param[in] ep PCI endpoint controller + * @param[out] out_cpu_addr CPU physical address of the allocation + * @param[in] size Allocation size in bytes + * @return Kernel virtual address, or RT_NULL on failure + */ void *rt_pci_ep_mem_alloc(struct rt_pci_ep *ep, - rt_ubase_t *out_cpu_addr, rt_size_t size) + rt_ubase_t *out_cpu_addr, rt_size_t size) { void *vaddr = RT_NULL; @@ -178,8 +244,20 @@ void *rt_pci_ep_mem_alloc(struct rt_pci_ep *ep, return vaddr; } +/** + * @brief Free memory from the endpoint's pool + * + * Finds the memory region containing the allocation (by checking + * if the address falls within each region's range), unmaps the + * virtual address, and frees the bitmap pages. + * + * @param[in] ep PCI endpoint controller + * @param[in] vaddr Kernel virtual address to free (unmapped) + * @param[in] cpu_addr CPU physical address of the allocation + * @param[in] size Size of the allocation in bytes + */ void rt_pci_ep_mem_free(struct rt_pci_ep *ep, - void *vaddr, rt_ubase_t cpu_addr, rt_size_t size) + void *vaddr, rt_ubase_t cpu_addr, rt_size_t size) { if (!ep || !vaddr || !size) { diff --git a/components/drivers/pci/host-bridge.c b/components/drivers/pci/host-bridge.c index ada7ef656d8..1e70efc89a0 100644 --- a/components/drivers/pci/host-bridge.c +++ b/components/drivers/pci/host-bridge.c @@ -8,28 +8,62 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file host-bridge.c + * @brief PCI Host Bridge Driver + * + * This driver handles the PCI host bridge itself as a PCI device. + * It manages bus mastering, optional power management for the entire + * PCI hierarchy under this bridge (suspend/resume via PME# signals), + * and matches against standard PCI-to-PCI bridge class codes. + */ + #include #include #include #ifdef RT_USING_PM + +/** + * @brief Runtime PM status passed to enumeration callback + */ struct host_bridge_pm_status { - rt_uint8_t mode; - rt_bool_t enable; + rt_uint8_t mode; /**< Target sleep mode */ + rt_bool_t enable; /**< RT_TRUE for resume (enable wake), RT_FALSE for suspend (disable wake) */ }; -static const enum rt_pci_power system_pci_pm_mode[] = -{ - [PM_SLEEP_MODE_NONE] = RT_PCI_D0, - [PM_SLEEP_MODE_IDLE] = RT_PCI_D3HOT, - [PM_SLEEP_MODE_LIGHT] = RT_PCI_D1, - [PM_SLEEP_MODE_DEEP] = RT_PCI_D1, - [PM_SLEEP_MODE_STANDBY] = RT_PCI_D2, - [PM_SLEEP_MODE_SHUTDOWN] = RT_PCI_D3COLD, +/** + * @brief Mapping from system sleep modes to PCI power states + * + * Determines which PCI power state each system sleep mode targets: + * - NONE → D0 (fully operational) + * - IDLE → D3hot (light sleep) + * - LIGHT → D1 (medium sleep) + * - DEEP → D1 + * - STANDBY → D2 + * - SHUTDOWN→ D3cold + */ +static const enum rt_pci_power system_pci_pm_mode[] = { + [PM_SLEEP_MODE_NONE] = RT_PCI_D0, + [PM_SLEEP_MODE_IDLE] = RT_PCI_D3HOT, + [PM_SLEEP_MODE_LIGHT] = RT_PCI_D1, + [PM_SLEEP_MODE_DEEP] = RT_PCI_D1, + [PM_SLEEP_MODE_STANDBY] = RT_PCI_D2, + [PM_SLEEP_MODE_SHUTDOWN] = RT_PCI_D3COLD, }; +/** + * @brief PM enumeration callback for each PCI device + * + * Enables or disables wake (PME#) on every device in the hierarchy. + * Always returns RT_FALSE to continue enumeration through all devices. + * + * @param[in] pdev PCI device being visited + * @param[in] data Pointer to host_bridge_pm_status struct + * @return Always RT_FALSE (continue enumeration) + */ static rt_bool_t pci_device_pm_ops(struct rt_pci_device *pdev, void *data) { struct host_bridge_pm_status *status = data; @@ -40,6 +74,15 @@ static rt_bool_t pci_device_pm_ops(struct rt_pci_device *pdev, void *data) return RT_FALSE; } +/** + * @brief Suspend all PCI devices under this host bridge + * + * Disables PME# wake for all devices in the hierarchy. + * + * @param[in] device PCI host bridge device + * @param[in] mode Target sleep mode + * @return RT_EOK + */ static rt_err_t host_bridge_pm_suspend(const struct rt_device *device, rt_uint8_t mode) { struct host_bridge_pm_status status; @@ -52,6 +95,14 @@ static rt_err_t host_bridge_pm_suspend(const struct rt_device *device, rt_uint8_ return RT_EOK; } +/** + * @brief Resume all PCI devices under this host bridge + * + * Re-enables PME# wake for all devices in the hierarchy. + * + * @param[in] device PCI host bridge device + * @param[in] mode Sleep mode being exited + */ static void host_bridge_pm_resume(const struct rt_device *device, rt_uint8_t mode) { struct host_bridge_pm_status status; @@ -62,13 +113,18 @@ static void host_bridge_pm_resume(const struct rt_device *device, rt_uint8_t mod rt_pci_enum_device(pdev->bus, pci_device_pm_ops, &status); } -static const struct rt_device_pm_ops host_bridge_pm_ops = -{ +/** @brief PM operations vtable for host bridge */ +static const struct rt_device_pm_ops host_bridge_pm_ops = { .suspend = host_bridge_pm_suspend, .resume = host_bridge_pm_resume, }; #endif /* RT_USING_PM */ +/** + * @brief Free host bridge PM resources + * + * @param[in] pdev PCI host bridge device + */ static void host_bridge_free(struct rt_pci_device *pdev) { #ifdef RT_USING_PM @@ -76,6 +132,15 @@ static void host_bridge_free(struct rt_pci_device *pdev) #endif } +/** + * @brief Probe a host bridge PCI device + * + * Enables bus mastering so the bridge can forward DMA transactions, + * and registers the device with the power management framework. + * + * @param[in] pdev PCI host bridge device + * @return RT_EOK on success + */ static rt_err_t host_bridge_probe(struct rt_pci_device *pdev) { rt_err_t err = RT_EOK; @@ -89,6 +154,14 @@ static rt_err_t host_bridge_probe(struct rt_pci_device *pdev) return err; } +/** + * @brief Remove a host bridge PCI device + * + * Disables bus mastering and frees PM resources. + * + * @param[in] pdev PCI host bridge device + * @return RT_EOK + */ static rt_err_t host_bridge_remove(struct rt_pci_device *pdev) { host_bridge_free(pdev); @@ -97,6 +170,12 @@ static rt_err_t host_bridge_remove(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Shutdown a host bridge PCI device + * + * @param[in] pdev PCI host bridge device + * @return RT_EOK + */ static rt_err_t host_bridge_shutdown(struct rt_pci_device *pdev) { host_bridge_free(pdev); @@ -104,8 +183,16 @@ static rt_err_t host_bridge_shutdown(struct rt_pci_device *pdev) return RT_EOK; } -static const struct rt_pci_device_id host_bridge_pci_ids[] = -{ +/** + * @brief Device ID table for matching host bridges + * + * Matches: + * - Red Hat PCI host bridge (vendor 0x1b36, device 0x0008) + * - Any standard PCI Express port (class PCIS_BRIDGE_PCI_NORMAL) + * - Any subtractive PCI-to-PCI bridge (class PCIS_BRIDGE_PCI_SUBTRACTIVE) + * - Any Root Complex Event Collector (class PCIS_SYSTEM_RCEC) + */ +static const struct rt_pci_device_id host_bridge_pci_ids[] = { /* PCI host bridges */ { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0008) }, /* Any PCI-Express port */ @@ -117,8 +204,8 @@ static const struct rt_pci_device_id host_bridge_pci_ids[] = { /* sentinel */ } }; -static struct rt_pci_driver host_bridge_driver = -{ +/** @brief Host bridge PCI driver descriptor */ +static struct rt_pci_driver host_bridge_driver = { .name = "host-bridge", .ids = host_bridge_pci_ids, diff --git a/components/drivers/pci/host/dw/pcie-dw.c b/components/drivers/pci/host/dw/pcie-dw.c index 2cca6f15eb4..e476b35e10c 100644 --- a/components/drivers/pci/host/dw/pcie-dw.c +++ b/components/drivers/pci/host/dw/pcie-dw.c @@ -8,14 +8,40 @@ * 2023-09-23 GuEe-GUI first version */ +/** + * @file pcie-dw.c + * @brief Synopsys DesignWare PCIe Controller core library + * + * Provides the low-level DBI (Data Bus Interface) access methods, + * capability discovery in the controller's own config space, + * ATU (Address Translation Unit) programming for both inbound + * and outbound address translation, link training, and controller + * setup (lane count, speed, FTS configuration). + * + * The DesignWare PCIe controller is one of the most widely used + * PCIe IP blocks in the embedded industry. + */ + #define DBG_TAG "pcie.dw" #define DBG_LVL DBG_INFO #include #include "pcie-dw.h" +/** + * @brief Recursively find the next capability in DW controller's config space + * + * Walks the PCI capability linked list starting from cap_ptr. + * The DW controller's config space is accessed via DBI (Data Bus Interface) + * rather than ECAM. + * + * @param[in] pci DW PCIe controller + * @param[in] cap_ptr Current capability offset + * @param[in] cap Target capability ID + * @return Capability offset, or 0 if not found + */ static rt_uint8_t __dw_pcie_find_next_cap(struct dw_pcie *pci, - rt_uint8_t cap_ptr, rt_uint8_t cap) + rt_uint8_t cap_ptr, rt_uint8_t cap) { rt_uint16_t reg; rt_uint8_t cap_id, next_cap_ptr; @@ -42,6 +68,13 @@ static rt_uint8_t __dw_pcie_find_next_cap(struct dw_pcie *pci, return __dw_pcie_find_next_cap(pci, next_cap_ptr, cap); } +/** + * @brief Find a standard PCI capability in the DW controller's config space + * + * @param[in] pci DW PCIe controller + * @param[in] cap Capability ID to find + * @return Capability offset, or 0 if not found + */ rt_uint8_t dw_pcie_find_capability(struct dw_pcie *pci, rt_uint8_t cap) { rt_uint16_t reg; @@ -53,8 +86,20 @@ rt_uint8_t dw_pcie_find_capability(struct dw_pcie *pci, rt_uint8_t cap) return __dw_pcie_find_next_cap(pci, next_cap_ptr, cap); } +/** + * @brief Find next PCIe extended capability starting from a given offset + * + * PCIe extended capabilities form a linked list in the extended + * configuration space (offset 0x100-0xFFF). Each entry has an + * ID, version, and next-pointer in a single DWORD. + * + * @param[in] pci DW PCIe controller + * @param[in] start Starting offset (0 to search from 0x100) + * @param[in] cap Extended capability ID + * @return Extended capability offset, or 0 if not found + */ static rt_uint16_t dw_pcie_find_next_ext_capability(struct dw_pcie *pci, - rt_uint16_t start, rt_uint8_t cap) + rt_uint16_t start, rt_uint8_t cap) { rt_uint32_t header; int ttl, pos = PCI_REGMAX + 1; @@ -97,11 +142,26 @@ static rt_uint16_t dw_pcie_find_next_ext_capability(struct dw_pcie *pci, return 0; } +/** + * @brief Find a PCIe extended capability in the DW controller + * + * @param[in] pci DW PCIe controller + * @param[in] cap Extended capability ID + * @return Extended capability offset, or 0 if not found + */ rt_uint16_t dw_pcie_find_ext_capability(struct dw_pcie *pci, rt_uint8_t cap) { return dw_pcie_find_next_ext_capability(pci, 0, cap); } +/** + * @brief Read from a MMIO address with size and alignment checking + * + * @param[in] addr MMIO address (must be naturally aligned) + * @param[in] size Access size (1, 2, or 4) + * @param[out] out_val Read value + * @return RT_EOK on success, -RT_EINVAL if misaligned + */ rt_err_t dw_pcie_read(void *addr, rt_size_t size, rt_uint32_t *out_val) { /* Check aligned */ @@ -132,6 +192,14 @@ rt_err_t dw_pcie_read(void *addr, rt_size_t size, rt_uint32_t *out_val) return RT_EOK; } +/** + * @brief Write to a MMIO address with size and alignment checking + * + * @param[in] addr MMIO address (must be naturally aligned) + * @param[in] size Access size (1, 2, or 4) + * @param[in] val Value to write + * @return RT_EOK on success, -RT_EINVAL if misaligned + */ rt_err_t dw_pcie_write(void *addr, rt_size_t size, rt_uint32_t val) { /* Check aligned */ @@ -160,6 +228,17 @@ rt_err_t dw_pcie_write(void *addr, rt_size_t size, rt_uint32_t val) return RT_EOK; } +/** + * @brief Read from the DW controller's DBI (Data Bus Interface) space + * + * Uses the controller's custom read_dbi callback if available, + * otherwise falls back to direct MMIO access. + * + * @param[in] pci DW PCIe controller + * @param[in] reg Register offset within DBI space + * @param[in] size Access size (1, 2, or 4) + * @return Register value (0 on error) + */ rt_uint32_t dw_pcie_read_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t size) { rt_err_t err; @@ -178,6 +257,14 @@ rt_uint32_t dw_pcie_read_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t siz return val; } +/** + * @brief Write to the DW controller's DBI space + * + * @param[in] pci DW PCIe controller + * @param[in] reg Register offset within DBI space + * @param[in] size Access size (1, 2, or 4) + * @param[in] val Value to write + */ void dw_pcie_write_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t size, rt_uint32_t val) { rt_err_t err; @@ -194,6 +281,17 @@ void dw_pcie_write_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t size, rt_ } } +/** + * @brief Write to the DW controller's secondary DBI space (DBI2) + * + * DBI2 is used for endpoint mode to access the controller's + * own configuration from the EP perspective. + * + * @param[in] pci DW PCIe controller + * @param[in] reg Register offset within DBI2 space + * @param[in] size Access size + * @param[in] val Value to write + */ void dw_pcie_write_dbi2(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t size, rt_uint32_t val) { rt_err_t err; @@ -210,6 +308,13 @@ void dw_pcie_write_dbi2(struct dw_pcie *pci, rt_uint32_t reg, rt_size_t size, rt } } +/** + * @brief Read a 32-bit value from the ATU register space + * + * @param[in] pci DW PCIe controller + * @param[in] reg Register offset within ATU space + * @return Register value + */ rt_uint32_t dw_pcie_readl_atu(struct dw_pcie *pci, rt_uint32_t reg) { rt_err_t err; @@ -228,6 +333,13 @@ rt_uint32_t dw_pcie_readl_atu(struct dw_pcie *pci, rt_uint32_t reg) return val; } +/** + * @brief Write a 32-bit value to the ATU register space + * + * @param[in] pci DW PCIe controller + * @param[in] reg Register offset within ATU space + * @param[in] val Value to write + */ void dw_pcie_writel_atu(struct dw_pcie *pci, rt_uint32_t reg, rt_uint32_t val) { rt_err_t err; @@ -244,32 +356,44 @@ void dw_pcie_writel_atu(struct dw_pcie *pci, rt_uint32_t reg, rt_uint32_t val) } } +/** + * @brief Program an outbound iATU region (unroll mode) + * + * Outbound ATU translates CPU memory addresses to PCI bus addresses. + * This is used for the RC to access EP memory (e.g., config space via ECAM, + * or memory-mapped BAR regions). + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number (0 for RC, 0-N for EP) + * @param[in] index ATU region index + * @param[in] type Transaction type (MEM, IO, CFG0, CFG1) + * @param[in] cpu_addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Region size + */ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, rt_uint8_t func_no, - int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) + int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) { rt_uint64_t limit_addr = cpu_addr + size - 1; dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE, - rt_lower_32_bits(cpu_addr)); + rt_lower_32_bits(cpu_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE, - rt_upper_32_bits(cpu_addr)); + rt_upper_32_bits(cpu_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT, - rt_lower_32_bits(limit_addr)); + rt_lower_32_bits(limit_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_LIMIT, - rt_upper_32_bits(limit_addr)); + rt_upper_32_bits(limit_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, - rt_lower_32_bits(pci_addr)); + rt_lower_32_bits(pci_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, - rt_upper_32_bits(pci_addr)); + rt_upper_32_bits(pci_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, - type | PCIE_ATU_FUNC_NUM(func_no)); + type | PCIE_ATU_FUNC_NUM(func_no)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, - PCIE_ATU_ENABLE); + PCIE_ATU_ENABLE); - /* - * Make sure ATU enable takes effect before any subsequent config - * and I/O accesses. - */ + /* Wait for ATU enable to take effect */ for (int retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) { if (dw_pcie_readl_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2) & PCIE_ATU_ENABLE) @@ -283,8 +407,19 @@ static void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, rt_uint8_t fun LOG_E("Outbound iATU is not being enabled"); } +/** + * @brief Program an outbound iATU region (with cpu_addr fixup if needed) + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number + * @param[in] index ATU region index + * @param[in] type Transaction type + * @param[in] cpu_addr CPU physical address (may be fixed up) + * @param[in] pci_addr PCI bus address + * @param[in] size Region size + */ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, - int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) + int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) { if (pci->ops->cpu_addr_fixup) { @@ -294,7 +429,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, if (pci->iatu_unroll_enabled & DWC_IATU_UNROLL_EN) { dw_pcie_prog_outbound_atu_unroll(pci, func_no, - index, type, cpu_addr, pci_addr, size); + index, type, cpu_addr, pci_addr, size); return; } @@ -308,10 +443,7 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type | PCIE_ATU_FUNC_NUM(func_no)); dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE); - /* - * Make sure ATU enable takes effect before any subsequent config - * and I/O accesses. - */ + /* Wait for ATU enable to take effect */ for (int retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) { if (dw_pcie_readl_dbi(pci, PCIE_ATU_CR2) & PCIE_ATU_ENABLE) @@ -325,28 +457,63 @@ static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, LOG_E("Outbound iATU is not being enabled"); } +/** + * @brief Program an outbound ATU region (RC mode, function 0) + * + * @param[in] pci DW PCIe controller + * @param[in] index ATU region index + * @param[in] type Transaction type (MEM, IO, CFG0, CFG1) + * @param[in] cpu_addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Region size + */ void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, - int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) + int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) { __dw_pcie_prog_outbound_atu(pci, 0, index, type, cpu_addr, pci_addr, size); } +/** + * @brief Program an outbound ATU region (EP mode, with function number) + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number + * @param[in] index ATU region index + * @param[in] type Transaction type + * @param[in] cpu_addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Region size + */ void dw_pcie_prog_ep_outbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, - int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) + int index, int type, rt_uint64_t cpu_addr, rt_uint64_t pci_addr, rt_size_t size) { __dw_pcie_prog_outbound_atu(pci, func_no, index, type, cpu_addr, pci_addr, size); } +/** + * @brief Program an inbound iATU region (unroll mode) + * + * Inbound ATU translates PCI bus addresses to CPU memory addresses. + * This is used in EP mode so the host can access EP local memory. + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number + * @param[in] index ATU region index + * @param[in] bar BAR number this ATU entry is associated with + * @param[in] cpu_addr Target CPU physical address + * @param[in] aspace_type Address space type (MEM or IO) + * @return RT_EOK on success, -RT_EBUSY if ATU enable times out + */ static rt_err_t dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, - rt_uint8_t func_no, int index, int bar, rt_uint64_t cpu_addr, - enum dw_pcie_aspace_type aspace_type) + rt_uint8_t func_no, int index, int bar, rt_uint64_t cpu_addr, + enum dw_pcie_aspace_type aspace_type) { int type; dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, - rt_lower_32_bits(cpu_addr)); + rt_lower_32_bits(cpu_addr)); dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, - rt_upper_32_bits(cpu_addr)); + rt_upper_32_bits(cpu_addr)); switch (aspace_type) { @@ -363,15 +530,11 @@ static rt_err_t dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, } dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, - type | PCIE_ATU_FUNC_NUM(func_no)); + type | PCIE_ATU_FUNC_NUM(func_no)); dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, - PCIE_ATU_FUNC_NUM_MATCH_EN | PCIE_ATU_ENABLE | - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); + PCIE_ATU_FUNC_NUM_MATCH_EN | PCIE_ATU_ENABLE | + PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); - /* - * Make sure ATU enable takes effect before any subsequent config - * and I/O accesses. - */ for (int retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) { if (dw_pcie_readl_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2) & PCIE_ATU_ENABLE) @@ -387,16 +550,27 @@ static rt_err_t dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, return -RT_EBUSY; } +/** + * @brief Program an inbound iATU region + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number + * @param[in] index ATU region index + * @param[in] bar BAR number + * @param[in] cpu_addr Target CPU physical address + * @param[in] aspace_type Address space type (MEM or IO) + * @return RT_EOK on success + */ rt_err_t dw_pcie_prog_inbound_atu(struct dw_pcie *pci, - rt_uint8_t func_no, int index, int bar, rt_uint64_t cpu_addr, - enum dw_pcie_aspace_type aspace_type) + rt_uint8_t func_no, int index, int bar, rt_uint64_t cpu_addr, + enum dw_pcie_aspace_type aspace_type) { int type; if (pci->iatu_unroll_enabled & DWC_IATU_UNROLL_EN) { return dw_pcie_prog_inbound_atu_unroll(pci, func_no, - index, bar, cpu_addr, aspace_type); + index, bar, cpu_addr, aspace_type); } dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND | index); @@ -418,13 +592,8 @@ rt_err_t dw_pcie_prog_inbound_atu(struct dw_pcie *pci, } dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type | PCIE_ATU_FUNC_NUM(func_no)); - dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE | - PCIE_ATU_FUNC_NUM_MATCH_EN | PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); + dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN | PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); - /* - * Make sure ATU enable takes effect before any subsequent config - * and I/O accesses. - */ for (int retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; ++retries) { if (dw_pcie_readl_dbi(pci, PCIE_ATU_CR2) & PCIE_ATU_ENABLE) @@ -440,6 +609,13 @@ rt_err_t dw_pcie_prog_inbound_atu(struct dw_pcie *pci, return -RT_EBUSY; } +/** + * @brief Disable an ATU region + * + * @param[in] pci DW PCIe controller + * @param[in] index ATU region index + * @param[in] type Region direction (INBOUND or OUTBOUND) + */ void dw_pcie_disable_atu(struct dw_pcie *pci, int index, enum dw_pcie_region_type type) { rt_uint32_t region; @@ -463,12 +639,12 @@ void dw_pcie_disable_atu(struct dw_pcie *pci, int index, enum dw_pcie_region_typ if (region == PCIE_ATU_REGION_INBOUND) { dw_pcie_writel_ib_unroll(pci, index, - PCIE_ATU_UNR_REGION_CTRL2, ~(rt_uint32_t)PCIE_ATU_ENABLE); + PCIE_ATU_UNR_REGION_CTRL2, ~(rt_uint32_t)PCIE_ATU_ENABLE); } else { dw_pcie_writel_ob_unroll(pci, index, - PCIE_ATU_UNR_REGION_CTRL2, ~(rt_uint32_t)PCIE_ATU_ENABLE); + PCIE_ATU_UNR_REGION_CTRL2, ~(rt_uint32_t)PCIE_ATU_ENABLE); } } else @@ -478,6 +654,14 @@ void dw_pcie_disable_atu(struct dw_pcie *pci, int index, enum dw_pcie_region_typ } } +/** + * @brief Wait for the PCIe link to come up + * + * Polls the link status register with a timeout. + * + * @param[in] pci DW PCIe controller + * @return RT_EOK on link up, -RT_ETIMEOUT on timeout + */ rt_err_t dw_pcie_wait_for_link(struct dw_pcie *pci) { /* Check if the link is up or not */ @@ -498,6 +682,14 @@ rt_err_t dw_pcie_wait_for_link(struct dw_pcie *pci) return -RT_ETIMEOUT; } +/** + * @brief Check if the PCIe link is currently up + * + * Reads PORT_DEBUG1 register, checking LINK_UP and LINK_IN_TRAINING bits. + * + * @param[in] pci DW PCIe controller + * @return RT_TRUE if link is up + */ rt_bool_t dw_pcie_link_up(struct dw_pcie *pci) { rt_uint32_t val; @@ -512,6 +704,11 @@ rt_bool_t dw_pcie_link_up(struct dw_pcie *pci) return (val & PCIE_PORT_DEBUG1_LINK_UP) && (!(val & PCIE_PORT_DEBUG1_LINK_IN_TRAINING)); } +/** + * @brief Enable upstream configuration support + * + * Sets the UPCFG_SUPPORT bit in the multi-lane control register. + */ void dw_pcie_upconfig_setup(struct dw_pcie *pci) { rt_uint32_t val; @@ -521,6 +718,15 @@ void dw_pcie_upconfig_setup(struct dw_pcie *pci) dw_pcie_writel_dbi(pci, PCIE_PORT_MULTI_LANE_CTRL, val); } +/** + * @brief Set the maximum PCIe link speed + * + * Configures both the Link Capability and Link Control 2 registers + * for the desired generation. + * + * @param[in] pci DW PCIe controller + * @param[in] link_gen Target link generation (1-4, or 0 for hardware default) + */ static void dw_pcie_link_set_max_speed(struct dw_pcie *pci, rt_uint32_t link_gen) { rt_uint32_t cap, ctrl2, link_speed; @@ -532,10 +738,18 @@ static void dw_pcie_link_set_max_speed(struct dw_pcie *pci, rt_uint32_t link_gen switch (link_gen) { - case 1: link_speed = PCIEM_LNKCTL2_TLS_2_5GT; break; - case 2: link_speed = PCIEM_LNKCTL2_TLS_5_0GT; break; - case 3: link_speed = PCIEM_LNKCTL2_TLS_8_0GT; break; - case 4: link_speed = PCIEM_LNKCTL2_TLS_16_0GT; break; + case 1: + link_speed = PCIEM_LNKCTL2_TLS_2_5GT; + break; + case 2: + link_speed = PCIEM_LNKCTL2_TLS_5_0GT; + break; + case 3: + link_speed = PCIEM_LNKCTL2_TLS_8_0GT; + break; + case 4: + link_speed = PCIEM_LNKCTL2_TLS_16_0GT; + break; default: /* Use hardware capability */ link_speed = RT_FIELD_GET(PCIEM_LINK_CAP_MAX_SPEED, cap); @@ -549,6 +763,20 @@ static void dw_pcie_link_set_max_speed(struct dw_pcie *pci, rt_uint32_t link_gen dw_pcie_writel_dbi(pci, offset + PCIER_LINK_CAP, cap | link_speed); } +/** + * @brief Perform full DesignWare PCIe controller setup + * + * Handles: + * - iATU unroll detection (version >= 4.80a) + * - Link speed configuration + * - FTS (Fast Training Sequence) configuration for Gen1 and Gen2+ + * - DLL link enable + * - CDM (Configuration Dependent Module) check + * - Lane count configuration + * - Link width speed control + * + * @param[in] pci DW PCIe controller + */ void dw_pcie_setup(struct dw_pcie *pci) { rt_uint32_t val; @@ -620,10 +848,18 @@ void dw_pcie_setup(struct dw_pcie *pci) val &= ~PORT_LINK_MODE_MASK; switch (pci->num_lanes) { - case 1: val |= PORT_LINK_MODE_1_LANES; break; - case 2: val |= PORT_LINK_MODE_2_LANES; break; - case 4: val |= PORT_LINK_MODE_4_LANES; break; - case 8: val |= PORT_LINK_MODE_8_LANES; break; + case 1: + val |= PORT_LINK_MODE_1_LANES; + break; + case 2: + val |= PORT_LINK_MODE_2_LANES; + break; + case 4: + val |= PORT_LINK_MODE_4_LANES; + break; + case 8: + val |= PORT_LINK_MODE_8_LANES; + break; default: LOG_E("Invail num-lanes = %d", pci->num_lanes); return; @@ -635,10 +871,18 @@ void dw_pcie_setup(struct dw_pcie *pci) val &= ~PORT_LOGIC_LINK_WIDTH_MASK; switch (pci->num_lanes) { - case 1: val |= PORT_LOGIC_LINK_WIDTH_1_LANES; break; - case 2: val |= PORT_LOGIC_LINK_WIDTH_2_LANES; break; - case 4: val |= PORT_LOGIC_LINK_WIDTH_4_LANES; break; - case 8: val |= PORT_LOGIC_LINK_WIDTH_8_LANES; break; + case 1: + val |= PORT_LOGIC_LINK_WIDTH_1_LANES; + break; + case 2: + val |= PORT_LOGIC_LINK_WIDTH_2_LANES; + break; + case 4: + val |= PORT_LOGIC_LINK_WIDTH_4_LANES; + break; + case 8: + val |= PORT_LOGIC_LINK_WIDTH_8_LANES; + break; } val |= pci->user_speed; dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); diff --git a/components/drivers/pci/host/dw/pcie-dw.h b/components/drivers/pci/host/dw/pcie-dw.h index 0a1183f2d51..189249abf56 100644 --- a/components/drivers/pci/host/dw/pcie-dw.h +++ b/components/drivers/pci/host/dw/pcie-dw.h @@ -15,112 +15,112 @@ #include /* Parameters for the waiting for link up routine */ -#define LINK_WAIT_MAX_RETRIES 10 -#define LINK_WAIT_USLEEP_MIN 90000 -#define LINK_WAIT_USLEEP_MAX 100000 +#define LINK_WAIT_MAX_RETRIES 10 +#define LINK_WAIT_USLEEP_MIN 90000 +#define LINK_WAIT_USLEEP_MAX 100000 /* Parameters for the waiting for iATU enabled routine */ -#define LINK_WAIT_MAX_IATU_RETRIES 5 -#define LINK_WAIT_IATU 9 +#define LINK_WAIT_MAX_IATU_RETRIES 5 +#define LINK_WAIT_IATU 9 /* Synopsys-specific PCIe configuration registers */ -#define PCIE_PORT_AFR 0x70c -#define PORT_AFR_N_FTS_MASK RT_GENMASK(15, 8) -#define PORT_AFR_N_FTS(n) RT_FIELD_PREP(PORT_AFR_N_FTS_MASK, n) -#define PORT_AFR_CC_N_FTS_MASK RT_GENMASK(23, 16) -#define PORT_AFR_CC_N_FTS(n) RT_FIELD_PREP(PORT_AFR_CC_N_FTS_MASK, n) -#define PORT_AFR_ENTER_ASPM RT_BIT(30) -#define PORT_AFR_L0S_ENTRANCE_LAT_SHIFT 24 -#define PORT_AFR_L0S_ENTRANCE_LAT_MASK RT_GENMASK(26, 24) -#define PORT_AFR_L1_ENTRANCE_LAT_SHIFT 27 -#define PORT_AFR_L1_ENTRANCE_LAT_MASK RT_GENMASK(29, 27) - -#define PCIE_PORT_LINK_CONTROL 0x710 -#define PORT_LINK_LPBK_ENABLE RT_BIT(2) -#define PORT_LINK_DLL_LINK_EN RT_BIT(5) -#define PORT_LINK_FAST_LINK_MODE RT_BIT(7) -#define PORT_LINK_MODE_MASK RT_GENMASK(21, 16) -#define PORT_LINK_MODE(n) RT_FIELD_PREP(PORT_LINK_MODE_MASK, n) -#define PORT_LINK_MODE_1_LANES PORT_LINK_MODE(0x1) -#define PORT_LINK_MODE_2_LANES PORT_LINK_MODE(0x3) -#define PORT_LINK_MODE_4_LANES PORT_LINK_MODE(0x7) -#define PORT_LINK_MODE_8_LANES PORT_LINK_MODE(0xf) - -#define PCIE_PORT_DEBUG0 0x728 -#define PORT_LOGIC_LTSSM_STATE_MASK 0x1f -#define PORT_LOGIC_LTSSM_STATE_L0 0x11 -#define PCIE_PORT_DEBUG1 0x72c -#define PCIE_PORT_DEBUG1_LINK_UP RT_BIT(4) -#define PCIE_PORT_DEBUG1_LINK_IN_TRAINING RT_BIT(29) - -#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80c -#define PORT_LOGIC_N_FTS_MASK RT_GENMASK(7, 0) -#define PORT_LOGIC_SPEED_CHANGE RT_BIT(17) -#define PORT_LOGIC_LINK_WIDTH_MASK RT_GENMASK(12, 8) -#define PORT_LOGIC_LINK_WIDTH(n) RT_FIELD_PREP(PORT_LOGIC_LINK_WIDTH_MASK, n) -#define PORT_LOGIC_LINK_WIDTH_1_LANES PORT_LOGIC_LINK_WIDTH(0x1) -#define PORT_LOGIC_LINK_WIDTH_2_LANES PORT_LOGIC_LINK_WIDTH(0x2) -#define PORT_LOGIC_LINK_WIDTH_4_LANES PORT_LOGIC_LINK_WIDTH(0x4) -#define PORT_LOGIC_LINK_WIDTH_8_LANES PORT_LOGIC_LINK_WIDTH(0x8) - -#define PCIE_MSI_ADDR_LO 0x820 -#define PCIE_MSI_ADDR_HI 0x824 -#define PCIE_MSI_INTR0_ENABLE 0x828 -#define PCIE_MSI_INTR0_MASK 0x82c -#define PCIE_MSI_INTR0_STATUS 0x830 - -#define PCIE_PORT_MULTI_LANE_CTRL 0x8c0 -#define PORT_MLTI_UPCFG_SUPPORT RT_BIT(7) - -#define PCIE_ATU_VIEWPORT 0x900 -#define PCIE_ATU_REGION_INBOUND RT_BIT(31) -#define PCIE_ATU_REGION_OUTBOUND 0 -#define PCIE_ATU_CR1 0x904 -#define PCIE_ATU_TYPE_MEM 0x0 -#define PCIE_ATU_TYPE_IO 0x2 -#define PCIE_ATU_TYPE_CFG0 0x4 -#define PCIE_ATU_TYPE_CFG1 0x5 -#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) -#define PCIE_ATU_CR2 0x908 -#define PCIE_ATU_ENABLE RT_BIT(31) -#define PCIE_ATU_BAR_MODE_ENABLE RT_BIT(30) -#define PCIE_ATU_FUNC_NUM_MATCH_EN RT_BIT(19) -#define PCIE_ATU_LOWER_BASE 0x90c -#define PCIE_ATU_UPPER_BASE 0x910 -#define PCIE_ATU_LIMIT 0x914 -#define PCIE_ATU_LOWER_TARGET 0x918 -#define PCIE_ATU_BUS(x) RT_FIELD_PREP(RT_GENMASK(31, 24), x) -#define PCIE_ATU_DEV(x) RT_FIELD_PREP(RT_GENMASK(23, 19), x) -#define PCIE_ATU_FUNC(x) RT_FIELD_PREP(RT_GENMASK(18, 16), x) -#define PCIE_ATU_UPPER_TARGET 0x91c - -#define PCIE_MISC_CONTROL_1_OFF 0x8bc -#define PCIE_DBI_RO_WR_EN RT_BIT(0) - -#define PCIE_MSIX_DOORBELL 0x948 -#define PCIE_MSIX_DOORBELL_PF_SHIFT 24 - -#define PCIE_PL_CHK_REG_CONTROL_STATUS 0xb20 -#define PCIE_PL_CHK_REG_CHK_REG_START RT_BIT(0) -#define PCIE_PL_CHK_REG_CHK_REG_CONTINUOUS RT_BIT(1) -#define PCIE_PL_CHK_REG_CHK_REG_COMPARISON_ERROR RT_BIT(16) -#define PCIE_PL_CHK_REG_CHK_REG_LOGIC_ERROR RT_BIT(17) -#define PCIE_PL_CHK_REG_CHK_REG_COMPLETE RT_BIT(18) - -#define PCIE_PL_CHK_REG_ERR_ADDR 0xb28 +#define PCIE_PORT_AFR 0x70c +#define PORT_AFR_N_FTS_MASK RT_GENMASK(15, 8) +#define PORT_AFR_N_FTS(n) RT_FIELD_PREP(PORT_AFR_N_FTS_MASK, n) +#define PORT_AFR_CC_N_FTS_MASK RT_GENMASK(23, 16) +#define PORT_AFR_CC_N_FTS(n) RT_FIELD_PREP(PORT_AFR_CC_N_FTS_MASK, n) +#define PORT_AFR_ENTER_ASPM RT_BIT(30) +#define PORT_AFR_L0S_ENTRANCE_LAT_SHIFT 24 +#define PORT_AFR_L0S_ENTRANCE_LAT_MASK RT_GENMASK(26, 24) +#define PORT_AFR_L1_ENTRANCE_LAT_SHIFT 27 +#define PORT_AFR_L1_ENTRANCE_LAT_MASK RT_GENMASK(29, 27) + +#define PCIE_PORT_LINK_CONTROL 0x710 +#define PORT_LINK_LPBK_ENABLE RT_BIT(2) +#define PORT_LINK_DLL_LINK_EN RT_BIT(5) +#define PORT_LINK_FAST_LINK_MODE RT_BIT(7) +#define PORT_LINK_MODE_MASK RT_GENMASK(21, 16) +#define PORT_LINK_MODE(n) RT_FIELD_PREP(PORT_LINK_MODE_MASK, n) +#define PORT_LINK_MODE_1_LANES PORT_LINK_MODE(0x1) +#define PORT_LINK_MODE_2_LANES PORT_LINK_MODE(0x3) +#define PORT_LINK_MODE_4_LANES PORT_LINK_MODE(0x7) +#define PORT_LINK_MODE_8_LANES PORT_LINK_MODE(0xf) + +#define PCIE_PORT_DEBUG0 0x728 +#define PORT_LOGIC_LTSSM_STATE_MASK 0x1f +#define PORT_LOGIC_LTSSM_STATE_L0 0x11 +#define PCIE_PORT_DEBUG1 0x72c +#define PCIE_PORT_DEBUG1_LINK_UP RT_BIT(4) +#define PCIE_PORT_DEBUG1_LINK_IN_TRAINING RT_BIT(29) + +#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80c +#define PORT_LOGIC_N_FTS_MASK RT_GENMASK(7, 0) +#define PORT_LOGIC_SPEED_CHANGE RT_BIT(17) +#define PORT_LOGIC_LINK_WIDTH_MASK RT_GENMASK(12, 8) +#define PORT_LOGIC_LINK_WIDTH(n) RT_FIELD_PREP(PORT_LOGIC_LINK_WIDTH_MASK, n) +#define PORT_LOGIC_LINK_WIDTH_1_LANES PORT_LOGIC_LINK_WIDTH(0x1) +#define PORT_LOGIC_LINK_WIDTH_2_LANES PORT_LOGIC_LINK_WIDTH(0x2) +#define PORT_LOGIC_LINK_WIDTH_4_LANES PORT_LOGIC_LINK_WIDTH(0x4) +#define PORT_LOGIC_LINK_WIDTH_8_LANES PORT_LOGIC_LINK_WIDTH(0x8) + +#define PCIE_MSI_ADDR_LO 0x820 +#define PCIE_MSI_ADDR_HI 0x824 +#define PCIE_MSI_INTR0_ENABLE 0x828 +#define PCIE_MSI_INTR0_MASK 0x82c +#define PCIE_MSI_INTR0_STATUS 0x830 + +#define PCIE_PORT_MULTI_LANE_CTRL 0x8c0 +#define PORT_MLTI_UPCFG_SUPPORT RT_BIT(7) + +#define PCIE_ATU_VIEWPORT 0x900 +#define PCIE_ATU_REGION_INBOUND RT_BIT(31) +#define PCIE_ATU_REGION_OUTBOUND 0 +#define PCIE_ATU_CR1 0x904 +#define PCIE_ATU_TYPE_MEM 0x0 +#define PCIE_ATU_TYPE_IO 0x2 +#define PCIE_ATU_TYPE_CFG0 0x4 +#define PCIE_ATU_TYPE_CFG1 0x5 +#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) +#define PCIE_ATU_CR2 0x908 +#define PCIE_ATU_ENABLE RT_BIT(31) +#define PCIE_ATU_BAR_MODE_ENABLE RT_BIT(30) +#define PCIE_ATU_FUNC_NUM_MATCH_EN RT_BIT(19) +#define PCIE_ATU_LOWER_BASE 0x90c +#define PCIE_ATU_UPPER_BASE 0x910 +#define PCIE_ATU_LIMIT 0x914 +#define PCIE_ATU_LOWER_TARGET 0x918 +#define PCIE_ATU_BUS(x) RT_FIELD_PREP(RT_GENMASK(31, 24), x) +#define PCIE_ATU_DEV(x) RT_FIELD_PREP(RT_GENMASK(23, 19), x) +#define PCIE_ATU_FUNC(x) RT_FIELD_PREP(RT_GENMASK(18, 16), x) +#define PCIE_ATU_UPPER_TARGET 0x91c + +#define PCIE_MISC_CONTROL_1_OFF 0x8bc +#define PCIE_DBI_RO_WR_EN RT_BIT(0) + +#define PCIE_MSIX_DOORBELL 0x948 +#define PCIE_MSIX_DOORBELL_PF_SHIFT 24 + +#define PCIE_PL_CHK_REG_CONTROL_STATUS 0xb20 +#define PCIE_PL_CHK_REG_CHK_REG_START RT_BIT(0) +#define PCIE_PL_CHK_REG_CHK_REG_CONTINUOUS RT_BIT(1) +#define PCIE_PL_CHK_REG_CHK_REG_COMPARISON_ERROR RT_BIT(16) +#define PCIE_PL_CHK_REG_CHK_REG_LOGIC_ERROR RT_BIT(17) +#define PCIE_PL_CHK_REG_CHK_REG_COMPLETE RT_BIT(18) + +#define PCIE_PL_CHK_REG_ERR_ADDR 0xb28 /* * iATU Unroll-specific register definitions * From 4.80 core version the address translation will be made by unroll */ -#define PCIE_ATU_UNR_REGION_CTRL1 0x00 -#define PCIE_ATU_UNR_REGION_CTRL2 0x04 -#define PCIE_ATU_UNR_LOWER_BASE 0x08 -#define PCIE_ATU_UNR_UPPER_BASE 0x0C -#define PCIE_ATU_UNR_LOWER_LIMIT 0x10 -#define PCIE_ATU_UNR_LOWER_TARGET 0x14 -#define PCIE_ATU_UNR_UPPER_TARGET 0x18 -#define PCIE_ATU_UNR_UPPER_LIMIT 0x20 +#define PCIE_ATU_UNR_REGION_CTRL1 0x00 +#define PCIE_ATU_UNR_REGION_CTRL2 0x04 +#define PCIE_ATU_UNR_LOWER_BASE 0x08 +#define PCIE_ATU_UNR_UPPER_BASE 0x0C +#define PCIE_ATU_UNR_LOWER_LIMIT 0x10 +#define PCIE_ATU_UNR_LOWER_TARGET 0x14 +#define PCIE_ATU_UNR_UPPER_TARGET 0x18 +#define PCIE_ATU_UNR_UPPER_LIMIT 0x20 /* * The default address offset between dbi_base and atu_base. Root controller @@ -128,184 +128,243 @@ * default; the driver core automatically derives atu_base from dbi_base using * this offset, if atu_base not set. */ -#define DEFAULT_DBI_ATU_OFFSET (0x3 << 20) +#define DEFAULT_DBI_ATU_OFFSET (0x3 << 20) /* Register address builder */ -#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) ((region) << 9) -#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) (((region) << 9) | RT_BIT(8)) +#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) ((region) << 9) +#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) (((region) << 9) | RT_BIT(8)) -#define MAX_MSI_IRQS 256 -#define MAX_MSI_IRQS_PER_CTRL 32 -#define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL) -#define MSI_REG_CTRL_BLOCK_SIZE 12 -#define MSI_DEF_NUM_VECTORS 32 +#define MAX_MSI_IRQS 256 +#define MAX_MSI_IRQS_PER_CTRL 32 +#define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL) +#define MSI_REG_CTRL_BLOCK_SIZE 12 +#define MSI_DEF_NUM_VECTORS 32 /* Maximum number of inbound/outbound iATUs */ -#define MAX_IATU_IN 256 -#define MAX_IATU_OUT 256 +#define MAX_IATU_IN 256 +#define MAX_IATU_OUT 256 -#define DWC_IATU_UNROLL_EN RT_BIT(0) -#define DWC_IATU_IOCFG_SHARED RT_BIT(1) +#define DWC_IATU_UNROLL_EN RT_BIT(0) +#define DWC_IATU_IOCFG_SHARED RT_BIT(1) struct dw_pcie_host_ops; struct dw_pcie_ep_ops; struct dw_pcie_ops; +/** + * @brief iATU region direction type + */ enum dw_pcie_region_type { - DW_PCIE_REGION_UNKNOWN, - DW_PCIE_REGION_INBOUND, - DW_PCIE_REGION_OUTBOUND, + DW_PCIE_REGION_UNKNOWN, /**< Unknown / not specified */ + DW_PCIE_REGION_INBOUND, /**< Inbound: PCI bus address → CPU address (EP BAR backing) */ + DW_PCIE_REGION_OUTBOUND, /**< Outbound: CPU address → PCI bus address (RC memory access) */ }; +/** + * @brief DesignWare PCIe controller device mode + * + * The controller can operate as a Root Complex (RC) or + * Endpoint (EP). The mode is determined by the device tree + * compatible string: + * - "snps,dw-pcie" → RC mode + * - "snps,dw-pcie-ep" → EP mode + */ enum dw_pcie_device_mode { - DW_PCIE_UNKNOWN_TYPE, - DW_PCIE_EP_TYPE, - DW_PCIE_LEG_EP_TYPE, - DW_PCIE_RC_TYPE, + DW_PCIE_UNKNOWN_TYPE, /**< Unknown / not configured */ + DW_PCIE_EP_TYPE, /**< Endpoint mode */ + DW_PCIE_LEG_EP_TYPE, /**< Legacy endpoint mode */ + DW_PCIE_RC_TYPE, /**< Root Complex mode */ }; +/** + * @brief iATU address space type for inbound/outbound translation + */ enum dw_pcie_aspace_type { - DW_PCIE_ASPACE_UNKNOWN, - DW_PCIE_ASPACE_MEM, - DW_PCIE_ASPACE_IO, + DW_PCIE_ASPACE_UNKNOWN, /**< Unknown type */ + DW_PCIE_ASPACE_MEM, /**< Memory space */ + DW_PCIE_ASPACE_IO, /**< I/O space */ }; +/** + * @brief DesignWare PCIe port descriptor (RC mode) + * + * Holds the configuration space window, I/O space, MSI controller + * state, and host bridge reference for the root complex port. + */ struct dw_pcie_port { - void *cfg0_base; - rt_uint64_t cfg0_addr; - rt_uint64_t cfg0_size; + void *cfg0_base; /**< Virtual address of config space window */ + rt_uint64_t cfg0_addr; /**< CPU physical address of config space */ + rt_uint64_t cfg0_size; /**< Config space window size */ - rt_ubase_t io_addr; - rt_ubase_t io_bus_addr; - rt_size_t io_size; + rt_ubase_t io_addr; /**< CPU physical address of I/O space */ + rt_ubase_t io_bus_addr; /**< PCI bus address of I/O space */ + rt_size_t io_size; /**< I/O space size */ - const struct dw_pcie_host_ops *ops; + const struct dw_pcie_host_ops *ops; /**< Platform-specific host operations */ - int sys_irq; - int msi_irq; - struct rt_pic *irq_pic; - struct rt_pic *msi_pic; + int sys_irq; /**< System IRQ number */ + int msi_irq; /**< MSI IRQ number */ + struct rt_pic *irq_pic; /**< System interrupt controller */ + struct rt_pic *msi_pic; /**< MSI interrupt controller */ - void *msi_data; - rt_ubase_t msi_data_phy; + void *msi_data; /**< DMA-coherent MSI data buffer (virtual) */ + rt_ubase_t msi_data_phy; /**< DMA-coherent MSI data buffer (physical) */ - rt_uint32_t irq_count; - rt_uint32_t irq_mask[MAX_MSI_CTRLS]; + rt_uint32_t irq_count; /**< Number of MSI IRQs supported */ + rt_uint32_t irq_mask[MAX_MSI_CTRLS]; /**< Per-controller MSI mask values */ - struct rt_pci_host_bridge *bridge; - const struct rt_pci_ops *bridge_child_ops; + struct rt_pci_host_bridge *bridge; /**< Associated PCI host bridge */ + const struct rt_pci_ops *bridge_child_ops; /**< Child bus PCI ops (NULL = use dw_child_pcie_ops) */ - struct rt_spinlock lock; - RT_BITMAP_DECLARE(msi_map, MAX_MSI_IRQS); + struct rt_spinlock lock; /**< Spinlock for MSI bitmap operations */ + RT_BITMAP_DECLARE(msi_map, MAX_MSI_IRQS); /**< MSI IRQ allocation bitmap */ }; +/** + * @brief DesignWare PCIe host operations (platform-specific callbacks) + */ struct dw_pcie_host_ops { - rt_err_t (*host_init)(struct dw_pcie_port *port); - rt_err_t (*msi_host_init)(struct dw_pcie_port *port); - void (*set_irq_count)(struct dw_pcie_port *port); + rt_err_t (*host_init)(struct dw_pcie_port *port); /**< RC initialization callback */ + rt_err_t (*msi_host_init)(struct dw_pcie_port *port); /**< Custom MSI initialization (optional) */ + void (*set_irq_count)(struct dw_pcie_port *port); /**< Override default MSI IRQ count */ }; +/** + * @brief DesignWare PCIe endpoint function descriptor + * + * A per-function descriptor tracking the MSI and MSI-X capability + * offsets for each function on a multi-function endpoint. + */ struct dw_pcie_ep_func { - rt_list_t list; + rt_list_t list; /**< Node in ep->func_nodes */ - rt_uint8_t func_no; - rt_uint8_t msi_cap; /* MSI capability offset */ - rt_uint8_t msix_cap; /* MSI-X capability offset */ + rt_uint8_t func_no; /**< Function number (0 to max_functions-1) */ + rt_uint8_t msi_cap; /**< MSI capability offset in DBI space */ + rt_uint8_t msix_cap; /**< MSI-X capability offset in DBI space */ }; +/** + * @brief DesignWare PCIe endpoint descriptor + * + * Manages the EP mode state: BAR-to-ATU mapping, inbound/outbound + * ATU window bitmaps, function list, and MSI memory. + */ struct dw_pcie_ep { - struct rt_pci_ep *epc; - struct rt_pci_ep_bar *epc_bar[PCI_STD_NUM_BARS]; + struct rt_pci_ep *epc; /**< EP framework controller reference */ + struct rt_pci_ep_bar *epc_bar[PCI_STD_NUM_BARS]; /**< EP BAR backing descriptors */ - rt_list_t func_nodes; + rt_list_t func_nodes; /**< List of dw_pcie_ep_func nodes */ - const struct dw_pcie_ep_ops *ops; + const struct dw_pcie_ep_ops *ops; /**< Platform-specific EP operations */ - rt_uint64_t aspace; - rt_uint64_t aspace_size; - rt_size_t page_size; + rt_uint64_t aspace; /**< Address space base (from DT addr_space) */ + rt_uint64_t aspace_size; /**< Address space size */ + rt_size_t page_size; /**< Allocation page size */ - rt_uint8_t bar_to_atu[PCI_STD_NUM_BARS]; - rt_ubase_t *outbound_addr; + rt_uint8_t bar_to_atu[PCI_STD_NUM_BARS]; /**< BAR index → ATU region index mapping */ + rt_ubase_t *outbound_addr; /**< Per-window outbound CPU address array */ - rt_bitmap_t *ib_window_map; - rt_bitmap_t *ob_window_map; - rt_uint32_t num_ib_windows; - rt_uint32_t num_ob_windows; + rt_bitmap_t *ib_window_map; /**< Inbound ATU window allocation bitmap */ + rt_bitmap_t *ob_window_map; /**< Outbound ATU window allocation bitmap */ + rt_uint32_t num_ib_windows; /**< Total inbound ATU windows */ + rt_uint32_t num_ob_windows; /**< Total outbound ATU windows */ - void *msi_mem; - rt_ubase_t msi_mem_phy; + void *msi_mem; /**< MSI/MSI-X posted-write memory (virtual) */ + rt_ubase_t msi_mem_phy; /**< MSI/MSI-X posted-write memory (physical) */ }; +/** + * @brief DesignWare PCIe endpoint operations (platform-specific) + */ struct dw_pcie_ep_ops { - rt_err_t (*ep_init)(struct dw_pcie_ep *ep); - rt_err_t (*raise_irq)(struct dw_pcie_ep *ep, rt_uint8_t func_no, enum rt_pci_ep_irq type, unsigned irq); - rt_off_t (*func_select)(struct dw_pcie_ep *ep, rt_uint8_t func_no); + rt_err_t (*ep_init)(struct dw_pcie_ep *ep); /**< EP initialization callback */ + rt_err_t (*raise_irq)(struct dw_pcie_ep *ep, rt_uint8_t func_no, + enum rt_pci_ep_irq type, unsigned irq); /**< Raise an interrupt to the host */ + rt_off_t (*func_select)(struct dw_pcie_ep *ep, rt_uint8_t func_no); /**< Select function register offset */ }; +/** + * @brief DesignWare PCIe controller descriptor + * + * Top-level structure shared between RC and EP modes. + * Contains DBI register pointers, ATU base, version info, + * lane/link configuration, and the dual-mode port/endpoint union. + */ struct dw_pcie { - struct rt_device *dev; + struct rt_device *dev; /**< Parent platform device */ - void *dbi_base; - void *dbi_base2; - void *atu_base; + void *dbi_base; /**< DBI (Data Bus Interface) virtual base for own config */ + void *dbi_base2; /**< DBI2 base for EP-mode config space */ + void *atu_base; /**< iATU registers virtual base */ - rt_uint32_t version; - rt_uint32_t num_viewport; - rt_uint32_t num_lanes; - rt_uint32_t link_gen; - rt_uint32_t user_speed; - rt_uint8_t iatu_unroll_enabled; /* Internal Address Translation Unit */ - rt_uint8_t fts_number[2]; /* Fast Training Sequences */ + rt_uint32_t version; /**< Controller version (e.g., 0x480a for 4.80a) */ + rt_uint32_t num_viewport; /**< Number of ATU viewport registers */ + rt_uint32_t num_lanes; /**< Number of PCIe lanes (1/2/4/8) */ + rt_uint32_t link_gen; /**< Target link generation (1-4) */ + rt_uint32_t user_speed; /**< User-specified speed override */ + rt_uint8_t iatu_unroll_enabled; /**< iATU unroll mode flags (DWC_IATU_*) */ + rt_uint8_t fts_number[2]; /**< Fast Training Sequence values (gen1, gen2+) */ - struct dw_pcie_port port; - struct dw_pcie_ep endpoint; - const struct dw_pcie_ops *ops; + struct dw_pcie_port port; /**< RC mode port descriptor */ + struct dw_pcie_ep endpoint; /**< EP mode endpoint descriptor */ + const struct dw_pcie_ops *ops; /**< Platform-specific operations */ - void *priv; + void *priv; /**< Private data for the platform driver */ }; +/** + * @brief DesignWare PCIe platform operations vtable + * + * Optional hooks for platform-specific controller behavior: + * address fixup, custom DBI access, link status, and power control. + */ struct dw_pcie_ops { - rt_uint64_t (*cpu_addr_fixup)(struct dw_pcie *pcie, rt_uint64_t cpu_addr); - rt_uint32_t (*read_dbi)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size); - void (*write_dbi)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size, rt_uint32_t val); - void (*write_dbi2)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size, rt_uint32_t val); - rt_bool_t (*link_up)(struct dw_pcie *pcie); - rt_err_t (*start_link)(struct dw_pcie *pcie); - void (*stop_link)(struct dw_pcie *pcie); + rt_uint64_t (*cpu_addr_fixup)(struct dw_pcie *pcie, rt_uint64_t cpu_addr); /**< Fix up CPU address before ATU programming */ + rt_uint32_t (*read_dbi)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size); /**< Custom DBI read */ + void (*write_dbi)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size, rt_uint32_t val); /**< Custom DBI write */ + void (*write_dbi2)(struct dw_pcie *pcie, void *base, rt_uint32_t reg, rt_size_t size, rt_uint32_t val); /**< Custom DBI2 write */ + rt_bool_t (*link_up)(struct dw_pcie *pcie); /**< Platform-specific link status check */ + rt_err_t (*start_link)(struct dw_pcie *pcie); /**< Start link training */ + void (*stop_link)(struct dw_pcie *pcie); /**< Stop link / disable PHY */ }; -#define to_dw_pcie_from_port(ptr) rt_container_of((ptr), struct dw_pcie, port) -#define to_dw_pcie_from_endpoint(ptr) rt_container_of((ptr), struct dw_pcie, endpoint) +#define to_dw_pcie_from_port(ptr) rt_container_of((ptr), struct dw_pcie, port) +#define to_dw_pcie_from_endpoint(ptr) rt_container_of((ptr), struct dw_pcie, endpoint) #ifdef RT_PCI_DW_HOST #undef RT_PCI_DW_HOST -#define RT_PCI_DW_HOST 1 +#define RT_PCI_DW_HOST 1 #define HOST_API -#define HOST_RET(...) ; +#define HOST_RET(...) ; #else -#define HOST_API rt_inline -#define HOST_RET(...) { return __VA_ARGS__; } +#define HOST_API rt_inline +#define HOST_RET(...) \ + { \ + return __VA_ARGS__; \ + } #endif #ifdef RT_PCI_DW_EP #undef RT_PCI_DW_EP -#define RT_PCI_DW_EP 1 +#define RT_PCI_DW_EP 1 #define EP_API -#define EP_RET(...) ; +#define EP_RET(...) ; #else -#define EP_API rt_inline -#define EP_RET(...) { return __VA_ARGS__; } +#define EP_API rt_inline +#define EP_RET(...) \ + { \ + return __VA_ARGS__; \ + } #endif rt_uint8_t dw_pcie_find_capability(struct dw_pcie *pci, rt_uint8_t cap); @@ -328,41 +387,54 @@ rt_err_t dw_pcie_prog_inbound_atu(struct dw_pcie *pci, rt_uint8_t func_no, int i void dw_pcie_disable_atu(struct dw_pcie *pci, int index, enum dw_pcie_region_type type); void dw_pcie_setup(struct dw_pcie *pci); +/** @brief Write a 32-bit DBI register (convenience wrapper) */ rt_inline void dw_pcie_writel_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_uint32_t val) { dw_pcie_write_dbi(pci, reg, 0x4, val); } +/** @brief Read a 32-bit DBI register (convenience wrapper) */ rt_inline rt_uint32_t dw_pcie_readl_dbi(struct dw_pcie *pci, rt_uint32_t reg) { return dw_pcie_read_dbi(pci, reg, 0x4); } +/** @brief Write a 16-bit DBI register (convenience wrapper) */ rt_inline void dw_pcie_writew_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_uint16_t val) { dw_pcie_write_dbi(pci, reg, 0x2, val); } +/** @brief Read a 16-bit DBI register (convenience wrapper) */ rt_inline rt_uint16_t dw_pcie_readw_dbi(struct dw_pcie *pci, rt_uint32_t reg) { return dw_pcie_read_dbi(pci, reg, 0x2); } +/** @brief Write an 8-bit DBI register (convenience wrapper) */ rt_inline void dw_pcie_writeb_dbi(struct dw_pcie *pci, rt_uint32_t reg, rt_uint8_t val) { dw_pcie_write_dbi(pci, reg, 0x1, val); } +/** @brief Read an 8-bit DBI register (convenience wrapper) */ rt_inline rt_uint8_t dw_pcie_readb_dbi(struct dw_pcie *pci, rt_uint32_t reg) { return dw_pcie_read_dbi(pci, reg, 0x1); } +/** @brief Write a 32-bit DBI2 register (convenience wrapper) */ rt_inline void dw_pcie_writel_dbi2(struct dw_pcie *pci, rt_uint32_t reg, rt_uint32_t val) { dw_pcie_write_dbi2(pci, reg, 0x4, val); } +/** + * @brief Enable or disable DBI read-only register write access + * + * @param[in] pci DW PCIe controller + * @param[in] enable RT_TRUE to allow writes to RO registers + */ rt_inline void dw_pcie_dbi_ro_writable_enable(struct dw_pcie *pci, rt_bool_t enable) { const rt_uint32_t reg = PCIE_MISC_CONTROL_1_OFF; @@ -377,64 +449,83 @@ rt_inline void dw_pcie_dbi_ro_writable_enable(struct dw_pcie *pci, rt_bool_t ena } } +/** @brief Detect if iATU supports unroll mode (version >= 4.80a, VIEWPORT reads all-ones) */ rt_inline rt_uint8_t dw_pcie_iatu_unroll_enabled(struct dw_pcie *pci) { return dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) == 0xffffffff ? 1 : 0; } rt_inline rt_uint32_t dw_pcie_readl_ob_unroll(struct dw_pcie *pci, - rt_uint32_t index, rt_uint32_t reg) + rt_uint32_t index, rt_uint32_t reg) { return dw_pcie_readl_atu(pci, PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index) + reg); } rt_inline void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, - rt_uint32_t index, rt_uint32_t reg, rt_uint32_t val) + rt_uint32_t index, rt_uint32_t reg, rt_uint32_t val) { dw_pcie_writel_atu(pci, PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index) + reg, val); } rt_inline rt_uint32_t dw_pcie_readl_ib_unroll(struct dw_pcie *pci, - rt_uint32_t index, rt_uint32_t reg) + rt_uint32_t index, rt_uint32_t reg) { return dw_pcie_readl_atu(pci, PCIE_GET_ATU_INB_UNR_REG_OFFSET(index) + reg); } rt_inline void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, - rt_uint32_t index, rt_uint32_t reg, rt_uint32_t val) + rt_uint32_t index, rt_uint32_t reg, rt_uint32_t val) { dw_pcie_writel_atu(pci, reg + PCIE_GET_ATU_INB_UNR_REG_OFFSET(index), val); } +/** @brief Handle all pending MSI interrupts (RC mode) */ HOST_API rt_err_t dw_handle_msi_irq(struct dw_pcie_port *port) HOST_RET(-RT_ENOSYS) -HOST_API void dw_pcie_msi_init(struct dw_pcie_port *port) HOST_RET() -HOST_API void dw_pcie_free_msi(struct dw_pcie_port *port) HOST_RET() - -HOST_API void dw_pcie_setup_rc(struct dw_pcie_port *port) HOST_RET() - -HOST_API rt_err_t dw_pcie_host_init(struct dw_pcie_port *port) HOST_RET(-RT_ENOSYS) -HOST_API void dw_pcie_host_deinit(struct dw_pcie_port *port) HOST_RET() - -HOST_API void dw_pcie_host_free(struct dw_pcie_port *port) HOST_RET() - -HOST_API void *dw_pcie_own_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg) HOST_RET(RT_NULL) - -EP_API rt_err_t dw_pcie_ep_init(struct dw_pcie_ep *ep) EP_RET(-RT_ENOSYS) -EP_API rt_err_t dw_pcie_ep_init_complete(struct dw_pcie_ep *ep) EP_RET(-RT_ENOSYS) -EP_API void dw_pcie_ep_exit(struct dw_pcie_ep *ep) EP_RET() - -EP_API rt_err_t dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no) EP_RET(-RT_ENOSYS) -EP_API rt_err_t dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) -EP_API rt_err_t dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) -EP_API rt_err_t dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) - -EP_API void dw_pcie_ep_reset_bar(struct dw_pcie *pci, int bar_idx) EP_RET() - -EP_API rt_err_t dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, - int bar_idx, rt_ubase_t cpu_addr, enum dw_pcie_aspace_type aspace_type) EP_RET(-RT_ENOSYS) -EP_API rt_err_t dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, - rt_ubase_t phys_addr, rt_uint64_t pci_addr, rt_size_t size) EP_RET(-RT_ENOSYS) - -EP_API struct dw_pcie_ep_func *dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, rt_uint8_t func_no) EP_RET(RT_NULL) +/** @brief Initialize MSI and program MSI address registers */ + HOST_API void dw_pcie_msi_init(struct dw_pcie_port *port) HOST_RET() +/** @brief Free MSI resources */ + HOST_API void dw_pcie_free_msi(struct dw_pcie_port *port) HOST_RET() + +/** @brief Set up root complex configuration (RC mode) */ + HOST_API void dw_pcie_setup_rc(struct dw_pcie_port *port) HOST_RET() + +/** @brief Initialize DW PCIe host controller (full RC init) */ + HOST_API rt_err_t dw_pcie_host_init(struct dw_pcie_port *port) HOST_RET(-RT_ENOSYS) +/** @brief Deinitialize host mode */ + HOST_API void dw_pcie_host_deinit(struct dw_pcie_port *port) HOST_RET() +/** @brief Fully free host mode resources */ + HOST_API void dw_pcie_host_free(struct dw_pcie_port *port) HOST_RET() + +/** @brief Map config space for the root bus (DBI direct access) */ + HOST_API void *dw_pcie_own_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg) HOST_RET(RT_NULL) + +/** @brief Initialize DW PCIe endpoint */ + EP_API rt_err_t dw_pcie_ep_init(struct dw_pcie_ep *ep) EP_RET(-RT_ENOSYS) +/** @brief Complete EP initialization after dw_pcie_ep_init() */ + EP_API rt_err_t dw_pcie_ep_init_complete(struct dw_pcie_ep *ep) EP_RET(-RT_ENOSYS) +/** @brief Clean up and free all EP resources */ + EP_API void dw_pcie_ep_exit(struct dw_pcie_ep *ep) EP_RET() + +/** @brief Raise legacy INTx from EP (not supported on DW) */ + EP_API rt_err_t dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no) EP_RET(-RT_ENOSYS) +/** @brief Raise MSI interrupt from EP via posted write */ + EP_API rt_err_t dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) +/** @brief Raise MSI-X interrupt via table entry write */ + EP_API rt_err_t dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) +/** @brief Raise MSI-X interrupt via doorbell register (faster) */ + EP_API rt_err_t dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, rt_uint8_t func_no, unsigned irq) EP_RET(-RT_ENOSYS) + +/** @brief Reset a BAR to default state for all EP functions */ + EP_API void dw_pcie_ep_reset_bar(struct dw_pcie *pci, int bar_idx) EP_RET() + +/** @brief Program an inbound iATU region for an EP BAR */ + EP_API rt_err_t dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, + int bar_idx, rt_ubase_t cpu_addr, enum dw_pcie_aspace_type aspace_type) EP_RET(-RT_ENOSYS) +/** @brief Program an outbound iATU region for EP→host access */ + EP_API rt_err_t dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, + rt_ubase_t phys_addr, rt_uint64_t pci_addr, rt_size_t size) EP_RET(-RT_ENOSYS) + +/** @brief Get EP function descriptor by function number */ + EP_API struct dw_pcie_ep_func *dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, rt_uint8_t func_no) EP_RET(RT_NULL) #endif /* __PCIE_DESIGNWARE_H__ */ diff --git a/components/drivers/pci/host/dw/pcie-dw_ep.c b/components/drivers/pci/host/dw/pcie-dw_ep.c index b52f6b5324a..edf51cdbe75 100644 --- a/components/drivers/pci/host/dw/pcie-dw_ep.c +++ b/components/drivers/pci/host/dw/pcie-dw_ep.c @@ -8,12 +8,31 @@ * 2023-09-23 GuEe-GUI first version */ +/** + * @file pcie-dw_ep.c + * @brief Synopsys DesignWare PCIe Endpoint (EP) mode implementation + * + * Implements EP-mode operations for the DesignWare PCIe controller: + * - Configuration header writing (vendor/device IDs, BARs, etc.) + * - BAR setup and teardown with inbound iATU mapping + * - Address space mapping (CPU addr → PCI addr via outbound ATU) + * - MSI and MSI-X configuration and IRQ raising + * - EP initialization (DBI2 mapping, function discovery, MSI memory) + */ + #define DBG_TAG "pcie.dw-ep" #define DBG_LVL DBG_INFO #include #include "pcie-dw.h" +/** + * @brief Get the endpoint function descriptor for a given function number + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @return Function descriptor, or RT_NULL if not found + */ struct dw_pcie_ep_func *dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, rt_uint8_t func_no) { struct dw_pcie_ep_func *ep_func; @@ -29,6 +48,16 @@ struct dw_pcie_ep_func *dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, rt_ui return RT_NULL; } +/** + * @brief Select a function by applying the func_select offset + * + * Multi-function endpoints use a register offset to switch between + * function-specific register views. + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @return Function offset in DBI space + */ static rt_uint8_t dw_pcie_ep_func_select(struct dw_pcie_ep *ep, rt_uint8_t func_no) { rt_uint8_t func_offset = 0; @@ -41,8 +70,19 @@ static rt_uint8_t dw_pcie_ep_func_select(struct dw_pcie_ep *ep, rt_uint8_t func_ return func_offset; } +/** + * @brief Reset a BAR to its default state (zeroed, disabled) + * + * Clears the BAR value in both DBI and DBI2 spaces. + * For 64-bit BARs, also clears the upper 32 bits. + * + * @param[in] pci DW PCIe controller + * @param[in] func_no Function number + * @param[in] bar_idx BAR index (0-5) + * @param[in] flags BAR flags (PCIM_BAR_MEM_TYPE_64 for 64-bit) + */ static void __dw_pcie_ep_reset_bar(struct dw_pcie *pci, rt_uint8_t func_no, - int bar_idx, int flags) + int bar_idx, int flags) { rt_uint32_t reg; rt_uint8_t func_offset = 0; @@ -65,6 +105,12 @@ static void __dw_pcie_ep_reset_bar(struct dw_pcie *pci, rt_uint8_t func_no, dw_pcie_dbi_ro_writable_enable(pci, RT_FALSE); } +/** + * @brief Reset a BAR for all functions + * + * @param[in] pci DW PCIe controller + * @param[in] bar_idx BAR index to reset + */ void dw_pcie_ep_reset_bar(struct dw_pcie *pci, int bar_idx) { rt_uint8_t func_no, funcs = pci->endpoint.epc->max_functions; @@ -75,8 +121,17 @@ void dw_pcie_ep_reset_bar(struct dw_pcie *pci, int bar_idx) } } +/** + * @brief Recursively find next capability in EP DBI2 space for a specific function + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] cap_ptr Current capability offset + * @param[in] cap Target capability ID + * @return Capability offset, or 0 if not found + */ static rt_uint8_t __dw_pcie_ep_find_next_cap(struct dw_pcie_ep *ep, rt_uint8_t func_no, - rt_uint8_t cap_ptr, rt_uint8_t cap) + rt_uint8_t cap_ptr, rt_uint8_t cap) { rt_uint16_t reg; rt_uint8_t func_offset = 0, cap_id, next_cap_ptr; @@ -105,8 +160,16 @@ static rt_uint8_t __dw_pcie_ep_find_next_cap(struct dw_pcie_ep *ep, rt_uint8_t f return __dw_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); } +/** + * @brief Find a standard PCI capability for a specific EP function + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] cap Capability ID to find + * @return Capability offset, or 0 if not found + */ static rt_uint8_t dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, rt_uint8_t func_no, - rt_uint8_t cap) + rt_uint8_t cap) { rt_uint16_t reg; rt_uint8_t func_offset = 0, next_cap_ptr; @@ -119,8 +182,22 @@ static rt_uint8_t dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, rt_uint8_t f return __dw_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); } +/** + * @brief Program an inbound iATU region for an EP BAR + * + * Maps a host→device address translation: when the host reads/writes + * to the PCI address space covered by this BAR, the transaction is + * translated to the specified CPU physical address. + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] bar_idx BAR index + * @param[in] cpu_addr Target CPU physical address + * @param[in] aspace_type Address space type (MEM or IO) + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, - int bar_idx, rt_ubase_t cpu_addr, enum dw_pcie_aspace_type aspace_type) + int bar_idx, rt_ubase_t cpu_addr, enum dw_pcie_aspace_type aspace_type) { rt_err_t err; rt_uint32_t free_win; @@ -146,8 +223,21 @@ rt_err_t dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Program an outbound iATU region for EP → host access + * + * Maps a local CPU address to a PCI bus address, allowing the + * EP to access host memory or raise MSI/MSI-X interrupts. + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] phys_addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Region size + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, - rt_ubase_t phys_addr, rt_uint64_t pci_addr, rt_size_t size) + rt_ubase_t phys_addr, rt_uint64_t pci_addr, rt_size_t size) { rt_uint32_t free_win; struct dw_pcie *pci = to_dw_pcie_from_endpoint(ep); @@ -160,7 +250,7 @@ rt_err_t dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, } dw_pcie_prog_ep_outbound_atu(pci, func_no, free_win, PCIE_ATU_TYPE_MEM, - phys_addr, pci_addr, size); + phys_addr, pci_addr, size); ep->outbound_addr[free_win] = phys_addr; rt_bitmap_set_bit(ep->ob_window_map, free_win); @@ -168,8 +258,19 @@ rt_err_t dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Write the PCI configuration header for an EP function + * + * Programs vendor ID, device ID, revision, class code, subsystem IDs, + * and interrupt pin through the DBI (DBI2 for EP-visible config space). + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] hdr Header data to write + * @return 0 on success + */ static rt_err_t dw_pcie_ep_write_header(struct rt_pci_ep *epc, rt_uint8_t func_no, - struct rt_pci_ep_header *hdr) + struct rt_pci_ep_header *hdr) { rt_uint8_t func_offset = 0; struct dw_pcie_ep *ep = epc->priv; @@ -194,8 +295,20 @@ static rt_err_t dw_pcie_ep_write_header(struct rt_pci_ep *epc, rt_uint8_t func_n return 0; } +/** + * @brief Clear a BAR on an EP function + * + * Resets the BAR to zero, disables the associated inbound ATU region, + * and frees the ATU window. + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] bar BAR descriptor (unused) + * @param[in] bar_idx BAR index + * @return RT_EOK + */ static rt_err_t dw_pcie_ep_clear_bar(struct rt_pci_ep *epc, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx) + struct rt_pci_ep_bar *bar, int bar_idx) { rt_uint32_t atu_index; struct dw_pcie_ep *ep = epc->priv; @@ -211,8 +324,21 @@ static rt_err_t dw_pcie_ep_clear_bar(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Set a BAR on an EP function + * + * Configures the BAR size (written to DBI2 for the host to read), + * sets the BAR flags (memory/IO, 32/64-bit, prefetchable), + * and programs an inbound ATU to translate host accesses. + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] bar BAR configuration + * @param[in] bar_idx BAR index + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_set_bar(struct rt_pci_ep *epc, rt_uint8_t func_no, - struct rt_pci_ep_bar *bar, int bar_idx) + struct rt_pci_ep_bar *bar, int bar_idx) { rt_err_t err; rt_uint32_t reg; @@ -258,8 +384,16 @@ static rt_err_t dw_pcie_ep_set_bar(struct rt_pci_ep *epc, rt_uint8_t func_no, return 0; } +/** + * @brief Find the ATU index for a given outbound CPU address + * + * @param[in] ep DW PCIe EP + * @param[in] addr CPU physical address + * @param[out] atu_index Found ATU region index + * @return RT_EOK on success, -RT_EINVAL if not found + */ static rt_err_t dw_pcie_find_index(struct dw_pcie_ep *ep, - rt_ubase_t addr, rt_uint32_t *atu_index) + rt_ubase_t addr, rt_uint32_t *atu_index) { for (rt_uint32_t index = 0; index < ep->num_ob_windows; ++index) { @@ -276,8 +410,16 @@ static rt_err_t dw_pcie_find_index(struct dw_pcie_ep *ep, return -RT_EINVAL; } +/** + * @brief Unmap a previously mapped outbound address + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address to unmap + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_unmap_addr(struct rt_pci_ep *epc, rt_uint8_t func_no, - rt_ubase_t addr) + rt_ubase_t addr) { rt_err_t err; rt_uint32_t atu_index; @@ -295,8 +437,21 @@ static rt_err_t dw_pcie_ep_unmap_addr(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Map a CPU address to PCI address space (EP → host access) + * + * Programs an outbound ATU entry to translate local CPU addresses + * to PCI bus addresses. + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] addr CPU physical address + * @param[in] pci_addr Target PCI bus address + * @param[in] size Mapping size + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_map_addr(struct rt_pci_ep *epc, rt_uint8_t func_no, - rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size) + rt_ubase_t addr, rt_uint64_t pci_addr, rt_size_t size) { rt_err_t err; struct dw_pcie_ep *ep = epc->priv; @@ -311,8 +466,19 @@ static rt_err_t dw_pcie_ep_map_addr(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Configure MSI Multi-Message Enable for an EP function + * + * Writes the number of MSI vectors (log2 encoded) to the MSI + * Message Control register. + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] irq_nr Log2 of number of requested vectors + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_set_msi(struct rt_pci_ep *epc, rt_uint8_t func_no, - unsigned irq_nr) + unsigned irq_nr) { rt_uint32_t val, reg; rt_uint8_t func_offset = 0; @@ -340,8 +506,16 @@ static rt_err_t dw_pcie_ep_set_msi(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Get the current MSI configuration from an EP function + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI vectors enabled + * @return RT_EOK on success, -RT_EINVAL if MSI is not enabled + */ static rt_err_t dw_pcie_ep_get_msi(struct rt_pci_ep *epc, rt_uint8_t func_no, - unsigned *out_irq_nr) + unsigned *out_irq_nr) { rt_uint32_t val, reg; rt_uint8_t func_offset = 0; @@ -369,8 +543,21 @@ static rt_err_t dw_pcie_ep_get_msi(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Configure MSI-X for an EP function + * + * Sets the number of MSI-X vectors and the table/PBA location + * (BAR index + offset). + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] irq_nr Number of MSI-X vectors + * @param[in] bar_idx BAR index for the MSI-X table + * @param[in] offset Offset within the BAR + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_set_msix(struct rt_pci_ep *epc, rt_uint8_t func_no, - unsigned irq_nr, int bar_idx, rt_off_t offset) + unsigned irq_nr, int bar_idx, rt_off_t offset) { rt_uint32_t val, reg; rt_uint8_t func_offset = 0; @@ -407,8 +594,16 @@ static rt_err_t dw_pcie_ep_set_msix(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Get the current MSI-X configuration from an EP function + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[out] out_irq_nr Number of MSI-X vectors + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_get_msix(struct rt_pci_ep *epc, rt_uint8_t func_no, - unsigned *out_irq_nr) + unsigned *out_irq_nr) { rt_uint32_t val, reg; rt_uint8_t func_offset = 0; @@ -436,8 +631,17 @@ static rt_err_t dw_pcie_ep_get_msix(struct rt_pci_ep *epc, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Raise an interrupt from the EP to the host (delegates to platform ops) + * + * @param[in] epc PCI EP controller + * @param[in] func_no Function number + * @param[in] type Interrupt type + * @param[in] irq Vector number + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_raise_irq(struct rt_pci_ep *epc, rt_uint8_t func_no, - enum rt_pci_ep_irq type, unsigned irq) + enum rt_pci_ep_irq type, unsigned irq) { struct dw_pcie_ep *ep = epc->priv; @@ -449,6 +653,12 @@ static rt_err_t dw_pcie_ep_raise_irq(struct rt_pci_ep *epc, rt_uint8_t func_no, return ep->ops->raise_irq(ep, func_no, type, irq); } +/** + * @brief Stop the EP (disable link) + * + * @param[in] epc PCI EP controller + * @return RT_EOK + */ static rt_err_t dw_pcie_ep_stop(struct rt_pci_ep *epc) { struct dw_pcie_ep *ep = epc->priv; @@ -462,6 +672,12 @@ static rt_err_t dw_pcie_ep_stop(struct rt_pci_ep *epc) return RT_EOK; } +/** + * @brief Start the EP (enable link) + * + * @param[in] epc PCI EP controller + * @return RT_EOK on success + */ static rt_err_t dw_pcie_ep_start(struct rt_pci_ep *epc) { struct dw_pcie_ep *ep = epc->priv; @@ -475,22 +691,29 @@ static rt_err_t dw_pcie_ep_start(struct rt_pci_ep *epc) return RT_EOK; } -static const struct rt_pci_ep_ops dw_pcie_ep_ops = -{ - .write_header = dw_pcie_ep_write_header, - .set_bar = dw_pcie_ep_set_bar, - .clear_bar = dw_pcie_ep_clear_bar, - .map_addr = dw_pcie_ep_map_addr, - .unmap_addr = dw_pcie_ep_unmap_addr, - .set_msi = dw_pcie_ep_set_msi, - .get_msi = dw_pcie_ep_get_msi, - .set_msix = dw_pcie_ep_set_msix, - .get_msix = dw_pcie_ep_get_msix, - .raise_irq = dw_pcie_ep_raise_irq, - .start = dw_pcie_ep_start, - .stop = dw_pcie_ep_stop, +/** @brief DW PCIe EP operations vtable */ +static const struct rt_pci_ep_ops dw_pcie_ep_ops = { + .write_header = dw_pcie_ep_write_header, + .set_bar = dw_pcie_ep_set_bar, + .clear_bar = dw_pcie_ep_clear_bar, + .map_addr = dw_pcie_ep_map_addr, + .unmap_addr = dw_pcie_ep_unmap_addr, + .set_msi = dw_pcie_ep_set_msi, + .get_msi = dw_pcie_ep_get_msi, + .set_msix = dw_pcie_ep_set_msix, + .get_msix = dw_pcie_ep_get_msix, + .raise_irq = dw_pcie_ep_raise_irq, + .start = dw_pcie_ep_start, + .stop = dw_pcie_ep_stop, }; +/** + * @brief Raise a legacy INTx interrupt (not supported on DW EP) + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @return Always -RT_EINVAL (not supported) + */ rt_err_t dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no) { LOG_E("EP cannot trigger legacy IRQs"); @@ -498,8 +721,20 @@ rt_err_t dw_pcie_ep_raise_legacy_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no) return -RT_EINVAL; } +/** + * @brief Raise an MSI interrupt from the EP + * + * Reads the MSI address and data from the MSI capability registers + * (configured by the host), maps the MSI target address via outbound + * ATU, and performs a posted memory write with the message data. + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] irq Vector number (1-based) + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, - unsigned irq) + unsigned irq) { rt_err_t err; rt_off_t aligned_offset; @@ -553,8 +788,19 @@ rt_err_t dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Raise an MSI-X interrupt via the doorbell mechanism + * + * Uses the DW controller's MSI-X doorbell register for faster + * interrupt delivery (avoids mapping the MSI-X table). + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] irq Vector number (1-based) + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, rt_uint8_t func_no, - unsigned irq) + unsigned irq) { rt_uint32_t msg_data; struct dw_pcie_ep_func *ep_func; @@ -572,8 +818,19 @@ rt_err_t dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, rt_uint8_t fu return RT_EOK; } +/** + * @brief Raise an MSI-X interrupt by performing a memory write to the MSI-X table entry + * + * Reads the MSI-X table entry (address + data + vector control), + * maps the target address via outbound ATU, and performs the write. + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] irq Vector number (1-based) + * @return RT_EOK on success, -RT_EINVAL if vector is masked + */ rt_err_t dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, - unsigned irq) + unsigned irq) { rt_err_t err; int bar_idx; @@ -623,6 +880,14 @@ rt_err_t dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, rt_uint8_t func_no, return RT_EOK; } +/** + * @brief Clean up and free all EP resources + * + * Frees MSI memory, function descriptors, iATU window bitmaps, + * outbound address array, and the EPC structure. + * + * @param[in] ep DW PCIe EP + */ void dw_pcie_ep_exit(struct dw_pcie_ep *ep) { struct rt_pci_ep *epc = ep->epc; @@ -664,6 +929,13 @@ void dw_pcie_ep_exit(struct dw_pcie_ep *ep) } } +/** + * @brief Find an extended capability in the controller's DBI space + * + * @param[in] pci DW PCIe controller + * @param[in] cap Extended capability ID + * @return Extended capability offset, or 0 if not found + */ static rt_uint32_t dw_pcie_ep_find_ext_capability(struct dw_pcie *pci, int cap) { rt_uint32_t header; @@ -687,6 +959,15 @@ static rt_uint32_t dw_pcie_ep_find_ext_capability(struct dw_pcie *pci, int cap) return 0; } +/** + * @brief Complete EP initialization after dw_pcie_ep_init() + * + * Verifies the controller is in EP mode, disables any Resizable BAR + * capability left by firmware, and performs controller setup. + * + * @param[in] ep DW PCIe EP + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_init_complete(struct dw_pcie_ep *ep) { rt_off_t offset; @@ -723,6 +1004,22 @@ rt_err_t dw_pcie_ep_init_complete(struct dw_pcie_ep *ep) return RT_EOK; } +/** + * @brief Initialize the DW PCIe endpoint + * + * Full initialization sequence: + * 1. Validate DBI/DBI2 bases + * 2. Read iATU window counts from device tree + * 3. Allocate window bitmaps and outbound address array + * 4. Register with the EP framework + * 5. Discover MSI and MSI-X capabilities for each function + * 6. Initialize endpoint memory pool + * 7. Allocate MSI/MSI-X memory + * 8. Complete initialization (controller setup) + * + * @param[in] ep DW PCIe EP + * @return RT_EOK on success + */ rt_err_t dw_pcie_ep_init(struct dw_pcie_ep *ep) { rt_err_t err; diff --git a/components/drivers/pci/host/dw/pcie-dw_host.c b/components/drivers/pci/host/dw/pcie-dw_host.c index 6b483318f2e..195484b1665 100644 --- a/components/drivers/pci/host/dw/pcie-dw_host.c +++ b/components/drivers/pci/host/dw/pcie-dw_host.c @@ -8,12 +8,29 @@ * 2023-09-23 GuEe-GUI first version */ +/** + * @file pcie-dw_host.c + * @brief Synopsys DesignWare PCIe Root Complex (RC) mode support + * + * Implements the RC-mode host initialization: + * - MSI interrupt controller (acked via DBI register writes) + * - Root complex configuration (BAR setup, bus numbers, command register) + * - ATU programming for memory and I/O space + * - Own config space access via DBI (device 0 only) + * - Child bus config space access via ATU CFG0/CFG1 transactions + */ + #define DBG_TAG "pcie.dw-host" #define DBG_LVL DBG_INFO #include #include "pcie-dw.h" +/** + * @brief Acknowledge an MSI IRQ by clearing the status bit in the DBI register + * + * @param[in] pirq PIC IRQ descriptor + */ static void dw_pcie_irq_ack(struct rt_pic_irq *pirq) { int hwirq = pirq->hwirq; @@ -28,6 +45,11 @@ static void dw_pcie_irq_ack(struct rt_pic_irq *pirq) dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_STATUS + res, RT_BIT(bit)); } +/** + * @brief Mask an MSI IRQ (disable at PCIe core MSI controller level) + * + * @param[in] pirq PIC IRQ descriptor + */ static void dw_pcie_irq_mask(struct rt_pic_irq *pirq) { rt_ubase_t level; @@ -50,6 +72,11 @@ static void dw_pcie_irq_mask(struct rt_pic_irq *pirq) rt_spin_unlock_irqrestore(&port->lock, level); } +/** + * @brief Unmask an MSI IRQ + * + * @param[in] pirq PIC IRQ descriptor + */ static void dw_pcie_irq_unmask(struct rt_pic_irq *pirq) { rt_ubase_t level; @@ -72,6 +99,15 @@ static void dw_pcie_irq_unmask(struct rt_pic_irq *pirq) rt_spin_unlock_irqrestore(&port->lock, level); } +/** + * @brief Compose an MSI message for a DW PCIe IRQ + * + * The MSI message is a posted memory write to the msi_data_phy + * address with data equal to the hardware IRQ number. + * + * @param[in] pirq PIC IRQ descriptor + * @param[out] msg Composed MSI message + */ static void dw_pcie_compose_msi_msg(struct rt_pic_irq *pirq, struct rt_pci_msi_msg *msg) { rt_uint64_t msi_target; @@ -85,6 +121,13 @@ static void dw_pcie_compose_msi_msg(struct rt_pic_irq *pirq, struct rt_pci_msi_m msg->data = pirq->hwirq; } +/** + * @brief Allocate an MSI IRQ from the bitmap + * + * @param[in] pic MSI PIC instance + * @param[in] msi_desc MSI descriptor + * @return IRQ number on success, -RT_EEMPTY if no free IRQs + */ static int dw_pcie_irq_alloc_msi(struct rt_pic *pic, struct rt_pci_msi_desc *msi_desc) { rt_ubase_t level; @@ -114,6 +157,12 @@ static int dw_pcie_irq_alloc_msi(struct rt_pic *pic, struct rt_pci_msi_desc *msi return irq; } +/** + * @brief Free an MSI IRQ back to the bitmap + * + * @param[in] pic MSI PIC instance + * @param[in] irq IRQ number to free + */ static void dw_pcie_irq_free_msi(struct rt_pic *pic, int irq) { rt_ubase_t level; @@ -132,8 +181,8 @@ static void dw_pcie_irq_free_msi(struct rt_pic *pic, int irq) rt_spin_unlock_irqrestore(&port->lock, level); } -const static struct rt_pic_ops dw_pci_msi_ops = -{ +/** @brief DW PCIe MSI PIC operations */ +const static struct rt_pic_ops dw_pci_msi_ops = { .name = "DWPCI-MSI", .irq_ack = dw_pcie_irq_ack, .irq_mask = dw_pcie_irq_mask, @@ -144,7 +193,15 @@ const static struct rt_pic_ops dw_pci_msi_ops = .flags = RT_PIC_F_IRQ_ROUTING, }; -/* MSI int handler */ +/** + * @brief Handle all pending MSI interrupts + * + * Reads the MSI status registers, acknowledges each pending + * IRQ, and dispatches to the registered ISR. + * + * @param[in] port DW PCIe port + * @return RT_EOK if any IRQ was handled, -RT_EEMPTY if no pending IRQs + */ rt_err_t dw_handle_msi_irq(struct dw_pcie_port *port) { rt_err_t err; @@ -161,7 +218,7 @@ rt_err_t dw_handle_msi_irq(struct dw_pcie_port *port) for (i = 0; i < num_ctrls; ++i) { status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS + - (i * MSI_REG_CTRL_BLOCK_SIZE)); + (i * MSI_REG_CTRL_BLOCK_SIZE)); if (!status) { @@ -183,6 +240,12 @@ rt_err_t dw_handle_msi_irq(struct dw_pcie_port *port) return err; } +/** + * @brief MSI interrupt ISR callback + * + * @param[in] irqno Hardware IRQ number + * @param[in] param Port pointer + */ static void dw_pcie_msi_isr(int irqno, void *param) { struct dw_pcie_port *port = param; @@ -190,6 +253,11 @@ static void dw_pcie_msi_isr(int irqno, void *param) dw_handle_msi_irq(port); } +/** + * @brief Free MSI resources: detach IRQ, free DMA coherent memory + * + * @param[in] port DW PCIe port + */ void dw_pcie_free_msi(struct dw_pcie_port *port) { if (port->msi_irq >= 0) @@ -203,11 +271,19 @@ void dw_pcie_free_msi(struct dw_pcie_port *port) struct dw_pcie *pci = to_dw_pcie_from_port(port); rt_dma_free_coherent(pci->dev, sizeof(rt_uint64_t), port->msi_data, - port->msi_data_phy); + port->msi_data_phy); port->msi_data = RT_NULL; } } +/** + * @brief Initialize MSI for a DW PCIe port + * + * Programs the MSI address registers in the DBI space so that + * MSI writes from endpoints are captured by the controller. + * + * @param[in] port DW PCIe port + */ void dw_pcie_msi_init(struct dw_pcie_port *port) { #ifdef RT_PCI_MSI @@ -223,6 +299,21 @@ void dw_pcie_msi_init(struct dw_pcie_port *port) static const struct rt_pci_ops dw_child_pcie_ops; static const struct rt_pci_ops dw_pcie_ops; +/** + * @brief Initialize a DW PCIe host controller in RC mode + * + * Complete initialization sequence: + * 1. Parse "config" reg space and I/O space from DT + * 2. Allocate host bridge and parse bus-regions + * 3. Map DBI base + * 4. Set up MSI PIC and allocate MSI data memory + * 5. Install MSI ISR + * 6. Call platform-specific host_init callback + * 7. Probe host bridge (register root bus + scan) + * + * @param[in] port DW PCIe port + * @return RT_EOK on success + */ rt_err_t dw_pcie_host_init(struct dw_pcie_port *port) { rt_err_t err; @@ -359,7 +450,7 @@ rt_err_t dw_pcie_host_init(struct dw_pcie_port *port) } port->msi_data = rt_dma_alloc_coherent(pci->dev, sizeof(rt_uint64_t), - &port->msi_data_phy); + &port->msi_data_phy); if (!port->msi_data) { @@ -417,6 +508,11 @@ rt_err_t dw_pcie_host_init(struct dw_pcie_port *port) return err; } +/** + * @brief Deinitialize host mode (free MSI resources) + * + * @param[in] port DW PCIe port + */ void dw_pcie_host_deinit(struct dw_pcie_port *port) { if (!port->ops->msi_host_init) @@ -425,6 +521,11 @@ void dw_pcie_host_deinit(struct dw_pcie_port *port) } } +/** + * @brief Fully free host mode resources + * + * @param[in] port DW PCIe port + */ void dw_pcie_host_free(struct dw_pcie_port *port) { if (!port->ops->msi_host_init) @@ -441,6 +542,17 @@ void dw_pcie_host_free(struct dw_pcie_port *port) } } +/** + * @brief Map config space for devices behind a DW PCIe root port + * + * Uses ATU CFG0 (type 0) for devices on the root bus and + * CFG1 (type 1) for devices behind a bridge. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Register offset + * @return Virtual address for MMIO access, or RT_NULL if link is down + */ static void *dw_pcie_other_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg) { int type; @@ -452,8 +564,6 @@ static void *dw_pcie_other_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, i * Checking whether the link is up here is a last line of defense * against platforms that forward errors on the system bus as * SError upon PCI configuration transactions issued when the link is down. - * This check is racy by definition and does not stop the system from - * triggering an SError if the link goes down after this check is performed. */ if (!dw_pcie_link_up(pci)) { @@ -461,7 +571,7 @@ static void *dw_pcie_other_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, i } busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(RT_PCI_SLOT(devfn)) | - PCIE_ATU_FUNC(RT_PCI_FUNC(devfn)); + PCIE_ATU_FUNC(RT_PCI_FUNC(devfn)); if (rt_pci_is_root_bus(bus->parent)) { @@ -477,8 +587,21 @@ static void *dw_pcie_other_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, i return port->cfg0_base + reg; } +/** + * @brief Read config space for other (non-root) devices + * + * After the read, if IOCFG is shared with I/O space, + * restores the I/O ATU mapping. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Register offset + * @param[in] width Access width + * @param[out] value Read value + * @return RT_EOK on success + */ static rt_err_t dw_pcie_other_read_conf(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t *value) { rt_err_t err; struct dw_pcie_port *port = bus->sysdata; @@ -489,14 +612,24 @@ static rt_err_t dw_pcie_other_read_conf(struct rt_pci_bus *bus, if (!err && (pci->iatu_unroll_enabled & DWC_IATU_IOCFG_SHARED)) { dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, - port->io_addr, port->io_bus_addr, port->io_size); + port->io_addr, port->io_bus_addr, port->io_size); } return err; } +/** + * @brief Write config space for other (non-root) devices + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Register offset + * @param[in] width Access width + * @param[in] value Value to write + * @return RT_EOK on success + */ static rt_err_t dw_pcie_other_write_conf(struct rt_pci_bus *bus, - rt_uint32_t devfn, int reg, int width, rt_uint32_t value) + rt_uint32_t devfn, int reg, int width, rt_uint32_t value) { rt_err_t err; struct dw_pcie_port *port = bus->sysdata; @@ -507,19 +640,30 @@ static rt_err_t dw_pcie_other_write_conf(struct rt_pci_bus *bus, if (!err && (pci->iatu_unroll_enabled & DWC_IATU_IOCFG_SHARED)) { dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, - port->io_addr, port->io_bus_addr, port->io_size); + port->io_addr, port->io_bus_addr, port->io_size); } return err; } -static const struct rt_pci_ops dw_child_pcie_ops = -{ +/** @brief PCI ops for child buses (through ATU) */ +static const struct rt_pci_ops dw_child_pcie_ops = { .map = dw_pcie_other_conf_map, .read = dw_pcie_other_read_conf, .write = dw_pcie_other_write_conf, }; +/** + * @brief Map config space for the root bus (own config via DBI) + * + * The DW controller's own config (device 0) is accessed through + * the DBI space directly. Only slot 0 is valid on the root bus. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] reg Register offset + * @return Virtual address for MMIO access, or RT_NULL for invalid slots + */ void *dw_pcie_own_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg) { struct dw_pcie_port *port = bus->sysdata; @@ -533,13 +677,28 @@ void *dw_pcie_own_conf_map(struct rt_pci_bus *bus, rt_uint32_t devfn, int reg) return pci->dbi_base + reg; } -static const struct rt_pci_ops dw_pcie_ops = -{ +/** @brief PCI ops for root bus (own config via DBI) */ +static const struct rt_pci_ops dw_pcie_ops = { .map = dw_pcie_own_conf_map, .read = rt_pci_bus_read_config_uxx, .write = rt_pci_bus_write_config_uxx, }; +/** + * @brief Set up the DW PCIe root complex + * + * Configures: + * - DBI RO writable enable (for updating read-only registers) + * - Controller setup (lanes, speed, FTS) + * - MSI mask/enable registers + * - RC BARs + * - Interrupt Pin register + * - Bus numbers (primary=0, secondary=1, subordinate=ff) + * - Command register (I/O, MEM, Bus Master, SERR) + * - ATU regions for memory and I/O space translation + * + * @param[in] port DW PCIe port + */ void dw_pcie_setup_rc(struct dw_pcie_port *port) { rt_uint32_t val, num_ctrls; @@ -562,10 +721,8 @@ void dw_pcie_setup_rc(struct dw_pcie_port *port) { port->irq_mask[ctrl] = ~0; - dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + - (ctrl * MSI_REG_CTRL_BLOCK_SIZE), port->irq_mask[ctrl]); - dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE + - (ctrl * MSI_REG_CTRL_BLOCK_SIZE), ~0); + dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), port->irq_mask[ctrl]); + dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), ~0); } } @@ -617,8 +774,8 @@ void dw_pcie_setup_rc(struct dw_pcie_port *port) } dw_pcie_prog_outbound_atu(pci, atu_idx, - PCIE_ATU_TYPE_MEM, region->cpu_addr, - region->phy_addr, region->size); + PCIE_ATU_TYPE_MEM, region->cpu_addr, + region->phy_addr, region->size); } if (port->io_size) @@ -626,8 +783,8 @@ void dw_pcie_setup_rc(struct dw_pcie_port *port) if (pci->num_viewport > ++atu_idx) { dw_pcie_prog_outbound_atu(pci, atu_idx, - PCIE_ATU_TYPE_IO, port->io_addr, - port->io_bus_addr, port->io_size); + PCIE_ATU_TYPE_IO, port->io_addr, + port->io_bus_addr, port->io_size); } else { diff --git a/components/drivers/pci/host/dw/pcie-dw_platfrom.c b/components/drivers/pci/host/dw/pcie-dw_platfrom.c index 878ac41658d..4f15565d41b 100644 --- a/components/drivers/pci/host/dw/pcie-dw_platfrom.c +++ b/components/drivers/pci/host/dw/pcie-dw_platfrom.c @@ -8,6 +8,27 @@ * 2023-09-23 GuEe-GUI first version */ +/** + * @file pcie-dw_platfrom.c + * @brief Synopsys DesignWare PCIe platform driver + * + * This is the generic DesignWare PCIe platform driver that binds to + * "snps,dw-pcie" and "snps,dw-pcie-ep" compatible nodes in the device tree. + * It supports both Root Complex (RC) and Endpoint (EP) modes, selected + * by the compatible string. + * + * RC mode (@ref DW_PCIE_RC_TYPE): + * - Maps DBI registers + * - Configures MSI interrupt controller + * - Sets up ATU for memory and I/O space translation + * - Initiates link training + * + * EP mode (@ref DW_PCIE_EP_TYPE): + * - Maps DBI and DBI2 (endpoint config space) registers + * - Resets BARs to default state + * - Supports legacy INTx, MSI, and MSI-X IRQ raising + */ + #include #include @@ -17,18 +38,32 @@ #include "pcie-dw.h" +/** + * @brief Platform-specific SoC data + * + * Determines whether the controller operates as RC or EP. + */ struct dw_dw_platform_pcie_soc_data { - enum dw_pcie_device_mode mode; + enum dw_pcie_device_mode mode; /**< Device mode: RC or EP */ }; +/** + * @brief Platform PCIe instance + */ struct dw_platform_pcie { - struct dw_pcie *pci; - struct rt_syscon *regmap; - const struct dw_dw_platform_pcie_soc_data *soc_data; + struct dw_pcie *pci; /**< Core DW PCIe structure */ + struct rt_syscon *regmap; /**< Optional syscon regmap */ + const struct dw_dw_platform_pcie_soc_data *soc_data; /**< SoC-specific data */ }; +/** + * @brief RC host init: setup RC, wait for link, init MSI + * + * @param[in] port DW PCIe port + * @return RT_EOK + */ static rt_err_t dw_platform_pcie_host_init(struct dw_pcie_port *port) { struct dw_pcie *pci = to_dw_pcie_from_port(port); @@ -40,27 +75,44 @@ static rt_err_t dw_platform_pcie_host_init(struct dw_pcie_port *port) return RT_EOK; } +/** + * @brief Set maximum MSI IRQ count for the platform + * + * @param[in] pp DW PCIe port (irq_count updated) + */ static void dw_platform_set_irq_count(struct dw_pcie_port *pp) { pp->irq_count = MAX_MSI_IRQS; } -static const struct dw_pcie_host_ops dw_platform_pcie_host_ops = -{ +/** @brief Platform host ops */ +static const struct dw_pcie_host_ops dw_platform_pcie_host_ops = { .host_init = dw_platform_pcie_host_init, .set_irq_count = dw_platform_set_irq_count, }; +/** + * @brief Platform-specific link establishment (no-op, handled by dw_pcie_setup) + * + * @param[in] pci DW PCIe controller + * @return RT_EOK + */ static rt_err_t dw_platform_pcie_establish_link(struct dw_pcie *pci) { return RT_EOK; } -static const struct dw_pcie_ops dw_platform_pcie_ops = -{ +/** @brief Platform PCIe ops */ +static const struct dw_pcie_ops dw_platform_pcie_ops = { .start_link = dw_platform_pcie_establish_link, }; +/** + * @brief EP initialization: reset all BARs to default + * + * @param[in] ep DW PCIe EP + * @return RT_EOK + */ static rt_err_t dw_platform_pcie_ep_init(struct dw_pcie_ep *ep) { struct dw_pcie *pci = to_dw_pcie_from_endpoint(ep); @@ -73,8 +125,17 @@ static rt_err_t dw_platform_pcie_ep_init(struct dw_pcie_ep *ep) return RT_EOK; } +/** + * @brief EP IRQ raise: dispatch to legacy, MSI, or MSI-X handler + * + * @param[in] ep DW PCIe EP + * @param[in] func_no Function number + * @param[in] type IRQ type + * @param[in] irq Vector number (for MSI/MSI-X) + * @return RT_EOK on success + */ static rt_err_t dw_platform_pcie_ep_raise_irq(struct dw_pcie_ep *ep, - rt_uint8_t func_no, enum rt_pci_ep_irq type, unsigned irq) + rt_uint8_t func_no, enum rt_pci_ep_irq type, unsigned irq) { switch (type) { @@ -94,14 +155,24 @@ static rt_err_t dw_platform_pcie_ep_raise_irq(struct dw_pcie_ep *ep, return RT_EOK; } -static const struct dw_pcie_ep_ops dw_platform_pcie_ep_ops = -{ +/** @brief Platform EP ops */ +static const struct dw_pcie_ep_ops dw_platform_pcie_ep_ops = { .ep_init = dw_platform_pcie_ep_init, .raise_irq = dw_platform_pcie_ep_raise_irq, }; +/** + * @brief Add a PCIe port in RC mode + * + * Gets system IRQ and MSI IRQ from device tree, assigns + * host ops, and calls dw_pcie_host_init(). + * + * @param[in] plat_pcie Platform PCIe instance + * @param[in] dev Platform device + * @return RT_EOK on success + */ static rt_err_t dw_platform_add_pcie_port(struct dw_platform_pcie *plat_pcie, - struct rt_device *dev) + struct rt_device *dev) { rt_err_t err; struct dw_pcie *pci = plat_pcie->pci; @@ -134,8 +205,18 @@ static rt_err_t dw_platform_add_pcie_port(struct dw_platform_pcie *plat_pcie, return RT_EOK; } +/** + * @brief Add a PCIe port in EP mode + * + * Maps DBI2, gets the address space for outbound translation, + * and initializes the endpoint. + * + * @param[in] plat_pcie Platform PCIe instance + * @param[in] dev Platform device + * @return RT_EOK on success + */ static rt_err_t dw_platform_add_pcie_ep(struct dw_platform_pcie *plat_pcie, - struct rt_device *dev) + struct rt_device *dev) { rt_err_t err; struct dw_pcie *pci = plat_pcie->pci; @@ -167,6 +248,16 @@ static rt_err_t dw_platform_add_pcie_ep(struct dw_platform_pcie *plat_pcie, return RT_EOK; } +/** + * @brief Probe a DW PCIe platform device + * + * Binds to "snps,dw-pcie" (RC mode) or "snps,dw-pcie-ep" (EP mode). + * Maps DBI base, determines mode from soc_data, and initializes + * the appropriate port type. + * + * @param[in] pdev Platform device + * @return RT_EOK on success + */ static rt_err_t dw_platform_pcie_probe(struct rt_platform_device *pdev) { rt_err_t err; @@ -252,6 +343,12 @@ static rt_err_t dw_platform_pcie_probe(struct rt_platform_device *pdev) return err; } +/** + * @brief Remove a DW PCIe platform device + * + * @param[in] pdev Platform device + * @return RT_EOK + */ static rt_err_t dw_platform_pcie_remove(struct rt_platform_device *pdev) { struct dw_platform_pcie *plat_pcie = pdev->parent.user_data; @@ -267,25 +364,25 @@ static rt_err_t dw_platform_pcie_remove(struct rt_platform_device *pdev) return RT_EOK; } -static const struct dw_dw_platform_pcie_soc_data dw_platform_pcie_rc_soc_data = -{ +/** @brief RC mode SoC data */ +static const struct dw_dw_platform_pcie_soc_data dw_platform_pcie_rc_soc_data = { .mode = DW_PCIE_RC_TYPE, }; -static const struct dw_dw_platform_pcie_soc_data dw_platform_pcie_ep_soc_data = -{ +/** @brief EP mode SoC data */ +static const struct dw_dw_platform_pcie_soc_data dw_platform_pcie_ep_soc_data = { .mode = DW_PCIE_EP_TYPE, }; -static const struct rt_ofw_node_id dw_platform_pcie_ofw_ids[] = -{ +/** @brief Device tree compatible IDs */ +static const struct rt_ofw_node_id dw_platform_pcie_ofw_ids[] = { { .compatible = "snps,dw-pcie", .data = &dw_platform_pcie_rc_soc_data }, { .compatible = "snps,dw-pcie-ep", .data = &dw_platform_pcie_ep_soc_data }, { /* sentinel */ } }; -static struct rt_platform_driver dw_platform_pcie_driver = -{ +/** @brief DW PCIe platform driver */ +static struct rt_platform_driver dw_platform_pcie_driver = { .name = "dw-pcie", .ids = dw_platform_pcie_ofw_ids, diff --git a/components/drivers/pci/host/pci-host-common.c b/components/drivers/pci/host/pci-host-common.c index 776c02058c6..f2884d377ba 100644 --- a/components/drivers/pci/host/pci-host-common.c +++ b/components/drivers/pci/host/pci-host-common.c @@ -8,10 +8,32 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file pci-host-common.c + * @brief Common PCI host controller probe/remove logic + * + * Provides a shared probe sequence for ECAM-based PCI host controllers: + * 1. Allocate host bridge + * 2. IOMap the ECAM configuration space window + * 3. Parse device tree properties (bus-range, ranges, domain) + * 4. Create ECAM config window + * 5. Probe the host bridge (register root bus + scan) + */ + #include #include "../ecam.h" +/** + * @brief Common probe function for ECAM-based PCI host controllers + * + * Maps the config space MMIO region, initializes the host bridge + * from device tree data, creates an ECAM configuration window, + * and probes the PCI hierarchy. + * + * @param[in] pdev Platform device representing the PCI host controller + * @return RT_EOK on success, error code otherwise + */ rt_err_t pci_host_common_probe(struct rt_platform_device *pdev) { void *base; @@ -39,7 +61,7 @@ rt_err_t pci_host_common_probe(struct rt_platform_device *pdev) } host_bridge->sysdata = conf_win = pci_ecam_create(host_bridge, - (const struct pci_ecam_ops *)pdev->id->data); + (const struct pci_ecam_ops *)pdev->id->data); if (!conf_win) { @@ -69,6 +91,15 @@ rt_err_t pci_host_common_probe(struct rt_platform_device *pdev) return err; } +/** + * @brief Common remove function for ECAM-based PCI host controllers + * + * Removes the host bridge and all enumerated devices, unmaps the + * ECAM window, and frees the host bridge. + * + * @param[in] pdev Platform device to remove + * @return RT_EOK + */ rt_err_t pci_host_common_remove(struct rt_platform_device *pdev) { struct pci_ecam_config_window *conf_win; diff --git a/components/drivers/pci/host/pci-host-generic.c b/components/drivers/pci/host/pci-host-generic.c index 7496d5249b7..97163f907d2 100644 --- a/components/drivers/pci/host/pci-host-generic.c +++ b/components/drivers/pci/host/pci-host-generic.c @@ -8,21 +8,49 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file pci-host-generic.c + * @brief Generic PCI host controller driver + * + * Supports multiple ECAM-based PCI host controller implementations + * via device tree compatible strings: + * - "pci-host-cam-generic": CAM mode (16-bit bus shift, older PCI) + * - "pci-host-ecam-generic": Standard ECAM (20-bit bus shift, PCIe) + * - "marvell,armada8k-pcie-ecam": Marvell Armada 8K + * - "socionext,synquacer-pcie-ecam": Socionext SynQuacer + * - "snps,dw-pcie-ecam": Synopsys DesignWare PCIe in ECAM mode + * + * The DesignWare variants use a custom map function that restricts + * access to device 0 on the root bus (slot > 0 returns NULL) because + * the DW controller handles its own configuration differently. + */ + #include #include "../ecam.h" -static const struct pci_ecam_ops gen_pci_cfg_cam_bus_ops = -{ +/** @brief CAM-mode ECAM ops with 16-bit bus shift for legacy PCI */ +static const struct pci_ecam_ops gen_pci_cfg_cam_bus_ops = { .bus_shift = 16, - .pci_ops = - { + .pci_ops = { .map = pci_ecam_map, .read = rt_pci_bus_read_config_uxx, .write = rt_pci_bus_write_config_uxx, } }; +/** + * @brief DesignWare-specific ECAM map: restrict root bus to device 0 only + * + * On DesignWare controllers, the DBI (Data Bus Interface) is used for + * the host bridge's own configuration. ECAM should only be used for + * devices behind the root port, not for the root port itself. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] where Register offset + * @return Virtual address, or RT_NULL if access is prohibited + */ static void *pci_dw_ecam_map_bus(struct rt_pci_bus *bus, rt_uint32_t devfn, int where) { struct pci_ecam_config_window *conf_win = bus->sysdata; @@ -35,18 +63,17 @@ static void *pci_dw_ecam_map_bus(struct rt_pci_bus *bus, rt_uint32_t devfn, int return pci_ecam_map(bus, devfn, where); } -static const struct pci_ecam_ops pci_dw_ecam_bus_ops = -{ - .pci_ops = - { +/** @brief DesignWare ECAM ops (common across multiple DW-based SoCs) */ +static const struct pci_ecam_ops pci_dw_ecam_bus_ops = { + .pci_ops = { .map = pci_dw_ecam_map_bus, .read = rt_pci_bus_read_config_uxx, .write = rt_pci_bus_write_config_uxx, } }; -static const struct rt_ofw_node_id gen_pci_ofw_ids[] = -{ +/** @brief Device tree compatible IDs for supported PCI host controllers */ +static const struct rt_ofw_node_id gen_pci_ofw_ids[] = { { .compatible = "pci-host-cam-generic", .data = &gen_pci_cfg_cam_bus_ops }, { .compatible = "pci-host-ecam-generic", .data = &pci_generic_ecam_ops }, { .compatible = "marvell,armada8k-pcie-ecam", .data = &pci_dw_ecam_bus_ops }, @@ -55,8 +82,8 @@ static const struct rt_ofw_node_id gen_pci_ofw_ids[] = { /* sentinel */ } }; -static struct rt_platform_driver gen_pci_driver = -{ +/** @brief Generic PCI host platform driver */ +static struct rt_platform_driver gen_pci_driver = { .name = "pci-host-generic", .ids = gen_pci_ofw_ids, diff --git a/components/drivers/pci/irq.c b/components/drivers/pci/irq.c index c7bd1a849af..3692526b4c0 100644 --- a/components/drivers/pci/irq.c +++ b/components/drivers/pci/irq.c @@ -8,6 +8,17 @@ * 2022-11-07 GuEe-GUI first version */ +/** + * @file irq.c + * @brief PCI interrupt routing and assignment + * + * Handles INTx interrupt assignment for PCI devices, including: + * - Reading the Interrupt Pin register from config space + * - Route mapping via the host bridge's irq_map callback + * - Swizzling through PCI-to-PCI bridges (if the host bridge provides irq_slot) + * - Writing the assigned IRQ number back to the Interrupt Line register + */ + #include #define DBG_TAG "pci.irq" @@ -16,6 +27,20 @@ #include +/** + * @brief Assign an IRQ to a PCI device + * + * The assignment flow: + * 1. Read the Interrupt Pin register (INTA=1, INTB=2, INTC=3, INTD=4) + * 2. Optionally perform swizzling through bridges via irq_slot callback + * 3. Map the (slot, pin) pair to an IRQ number via irq_map callback + * 4. Write the assigned IRQ to the Interrupt Line register + * + * The host bridge must provide the irq_map callback. Without it, + * INTx interrupts are not available and the device must use MSI/MSI-X. + * + * @param[in] pdev PCI device to assign an IRQ to + */ void rt_pci_assign_irq(struct rt_pci_device *pdev) { int irq = 0; @@ -25,7 +50,7 @@ void rt_pci_assign_irq(struct rt_pci_device *pdev) if (!host_bridge->irq_map) { LOG_D("PCI-Device<%s> runtime IRQ mapping not provided by platform", - rt_dm_dev_get_name(&pdev->parent)); + rt_dm_dev_get_name(&pdev->parent)); return; } diff --git a/components/drivers/pci/msi/device.c b/components/drivers/pci/msi/device.c index 36d0d0d3ae0..948bfd70e1f 100644 --- a/components/drivers/pci/msi/device.c +++ b/components/drivers/pci/msi/device.c @@ -8,8 +8,27 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file device.c + * @brief MSI/MSI-X capability initialization and discovery + * + * Initializes (and disables) MSI and MSI-X capabilities for a newly + * probed PCI device. The capabilities are discovered via the PCI + * capability linked list and disabled as a safe default state. + * Drivers must explicitly enable MSI/MSI-X when needed. + */ + #include +/** + * @brief Initialize the MSI capability for a device (disable MSI) + * + * Finds the MSI capability (PCIY_MSI), checks if MSI is currently + * enabled (left over from firmware), and disables it. Also records + * whether the device supports 64-bit MSI addresses. + * + * @param[in] pdev PCI device (msi_cap and no_64bit_msi fields updated) + */ void rt_pci_msi_init(struct rt_pci_device *pdev) { if (pdev && (pdev->msi_cap = rt_pci_find_capability(pdev, PCIY_MSI))) @@ -30,6 +49,14 @@ void rt_pci_msi_init(struct rt_pci_device *pdev) } } +/** + * @brief Initialize the MSI-X capability for a device (disable MSI-X) + * + * Finds the MSI-X capability (PCIY_MSIX), checks if MSI-X is currently + * enabled (left over from firmware), and disables it. + * + * @param[in] pdev PCI device (msix_cap field updated) + */ void rt_pci_msix_init(struct rt_pci_device *pdev) { if (pdev && (pdev->msix_cap = rt_pci_find_capability(pdev, PCIY_MSIX))) diff --git a/components/drivers/pci/msi/irq.c b/components/drivers/pci/msi/irq.c index 8e3b7333e0e..a76c47e2d7e 100644 --- a/components/drivers/pci/msi/irq.c +++ b/components/drivers/pci/msi/irq.c @@ -8,15 +8,39 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file irq.c + * @brief MSI/MSI-X IRQ setup and cleanup + * + * Handles IRQ allocation from the MSI PIC (Programmable Interrupt + * Controller) and composition of MSI messages for delivery to devices. + */ + #include #define DBG_TAG "pci.msi.irq" #define DBG_LVL DBG_INFO #include +/** @brief Spinlock and bitmap for tracking allocated MSI IRQs */ static RT_DEFINE_SPINLOCK(msi_irq_map_lock); static RT_BITMAP_DECLARE(msi_irq_map, MAX_HANDLERS) = {}; +/** + * @brief Set up MSI/MSI-X IRQs for a device + * + * For MSI: allocates a contiguous block of IRQs from the MSI PIC, + * ensures they are consecutive (re-allocates if not), composes + * the MSI message, and writes it to the device. + * + * For MSI-X: allocates one IRQ per descriptor, composes each + * MSI message independently, and writes to the MSI-X table. + * + * @param[in] pdev PCI device + * @param[in] nvec Number of vectors to set up + * @param[in] type Interrupt type: PCIY_MSI or PCIY_MSIX + * @return RT_EOK on success, error code otherwise + */ rt_err_t rt_pci_msi_setup_irqs(struct rt_pci_device *pdev, int nvec, int type) { int irq, index = 0, irq_nr = 0; @@ -56,7 +80,7 @@ rt_err_t rt_pci_msi_setup_irqs(struct rt_pci_device *pdev, int nvec, int type) if (last_irq >= 0 && last_irq + 1 != irq) { - for (int idx = 0; idx < i; ++i, --last_irq) + for (int idx = 0; idx < i; ++idx, --last_irq) { rt_bitmap_set_bit(msi_irq_map, last_irq); } @@ -106,7 +130,7 @@ rt_err_t rt_pci_msi_setup_irqs(struct rt_pci_device *pdev, int nvec, int type) err = irq; LOG_E("Setup %s[%d] IRQ error = %s", "MSI-X", - desc->msix.index, rt_strerror(err)); + desc->msix.index, rt_strerror(err)); break; } @@ -145,6 +169,14 @@ rt_err_t rt_pci_msi_setup_irqs(struct rt_pci_device *pdev, int nvec, int type) return err; } +/** + * @brief Clean up (free) all MSI/MSI-X IRQs for a device + * + * Calls irq_free_msi on the MSI PIC for each allocated IRQ. + * + * @param[in] pdev PCI device + * @return RT_EOK on success + */ rt_err_t rt_pci_msi_cleanup_irqs(struct rt_pci_device *pdev) { int type; diff --git a/components/drivers/pci/msi/msi.c b/components/drivers/pci/msi/msi.c index 11ccdcb10c8..332ed4e5b4d 100644 --- a/components/drivers/pci/msi/msi.c +++ b/components/drivers/pci/msi/msi.c @@ -8,6 +8,21 @@ * 2022-11-07 GuEe-GUI first version */ +/** + * @file msi.c + * @brief PCI MSI (Message Signaled Interrupts) core implementation + * + * Implements MSI and MSI-X interrupt management: + * - MSI: Up to 32 vectors per device, config-space based + * - MSI-X: Up to 2048 vectors per device, table-based (MMIO) + * + * Key operations: + * - Vector allocation with affinity support + * - Interrupt mask/unmask (per-vector for MSI-X, bulk for MSI) + * - MSI message composition and delivery + * - NUMA-aware memory affinity for MSI data addresses + */ + #include #include @@ -15,37 +30,43 @@ #define DBG_LVL DBG_INFO #include -/* PCI has 2048 max IRQs in MSI-X */ +/** @brief Default affinity bitmap for up to 2048 MSI-X IRQs */ static RT_IRQ_AFFINITY_DECLARE(msi_affinity_default[2048]) rt_section(".bss.noclean.pci.msi"); +/** @brief Convenience wrapper for spinlock acquire */ rt_inline void spin_lock(struct rt_spinlock *lock) { rt_hw_spin_lock(&lock->lock); } +/** @brief Convenience wrapper for spinlock release */ rt_inline void spin_unlock(struct rt_spinlock *lock) { rt_hw_spin_unlock(&lock->lock); } +/** @brief Get the base address of an MSI-X table entry */ rt_inline void *msix_table_base(struct rt_pci_msix_conf *msix) { return msix->table_base + msix->index * PCIM_MSIX_ENTRY_SIZE; } +/** @brief Get the vector control register address for an MSI-X entry */ rt_inline void *msix_vector_ctrl_base(struct rt_pci_msix_conf *msix) { return msix_table_base(msix) + PCIM_MSIX_ENTRY_VECTOR_CTRL; } +/** @brief Write the vector control register for an MSI-X entry */ rt_inline void msix_write_vector_ctrl(struct rt_pci_msix_conf *msix, - rt_uint32_t ctrl) + rt_uint32_t ctrl) { void *vc_addr = msix_vector_ctrl_base(msix); HWREG32(vc_addr) = ctrl; } +/** @brief Mask an MSI-X vector (set mask bit in vector control) */ rt_inline void msix_mask(struct rt_pci_msix_conf *msix) { msix->msg_ctrl |= PCIM_MSIX_ENTRYVECTOR_CTRL_MASK; @@ -55,8 +76,15 @@ rt_inline void msix_mask(struct rt_pci_msix_conf *msix) HWREG32(msix->table_base); } +/** + * @brief Update MSI-X global control register (read-modify-write) + * + * @param[in] pdev PCI device + * @param[in] clear Bits to clear in the control register + * @param[in] set Bits to set in the control register + */ static void msix_update_ctrl(struct rt_pci_device *pdev, - rt_uint16_t clear, rt_uint16_t set) + rt_uint16_t clear, rt_uint16_t set) { rt_uint16_t msgctl; @@ -66,12 +94,22 @@ static void msix_update_ctrl(struct rt_pci_device *pdev, rt_pci_write_config_u16(pdev, pdev->msix_cap + PCIR_MSIX_CTRL, msgctl); } +/** @brief Unmask an MSI-X vector (clear mask bit in vector control) */ rt_inline void msix_unmask(struct rt_pci_msix_conf *msix) { msix->msg_ctrl &= ~PCIM_MSIX_ENTRYVECTOR_CTRL_MASK; msix_write_vector_ctrl(msix, msix->msg_ctrl); } +/** + * @brief Compute the full mask for a multi-MSI capability + * + * For devices supporting multiple MSI vectors, this computes + * a bitmask covering all allocated vectors (1 << (1 << multi_msg_max)). + * + * @param[in] msi MSI configuration + * @return Bitmask for all multi-MSI vectors + */ rt_inline rt_uint32_t msi_multi_mask(struct rt_pci_msi_conf *msi) { if (msi->cap.multi_msg_max >= 5) @@ -82,8 +120,18 @@ rt_inline rt_uint32_t msi_multi_mask(struct rt_pci_msi_conf *msi) return (1 << (1 << msi->cap.multi_msg_max)) - 1; } +/** + * @brief Modify the MSI mask register (with per-vector mask support) + * + * Only available if the MSI capability supports per-vector masking. + * + * @param[in] msi MSI configuration + * @param[in] clear Bits to clear in the mask + * @param[in] set Bits to set in the mask + * @param[in] pdev PCI device + */ static void msi_write_mask(struct rt_pci_msi_conf *msi, - rt_uint32_t clear, rt_uint32_t set, struct rt_pci_device *pdev) + rt_uint32_t clear, rt_uint32_t set, struct rt_pci_device *pdev) { if (msi->cap.is_masking) { @@ -97,18 +145,26 @@ static void msi_write_mask(struct rt_pci_msi_conf *msi, } } +/** @brief Set mask bits for MSI vectors */ rt_inline void msi_mask(struct rt_pci_msi_conf *msi, - rt_uint32_t mask, struct rt_pci_device *pdev) + rt_uint32_t mask, struct rt_pci_device *pdev) { msi_write_mask(msi, 0, mask, pdev); } +/** @brief Clear mask bits for MSI vectors */ rt_inline void msi_unmask(struct rt_pci_msi_conf *msi, - rt_uint32_t mask, struct rt_pci_device *pdev) + rt_uint32_t mask, struct rt_pci_device *pdev) { msi_write_mask(msi, mask, 0, pdev); } +/** + * @brief Enable or disable MSI globally for a device + * + * @param[in] pdev PCI device + * @param[in] enable RT_TRUE to enable MSI + */ static void msi_write_enable(struct rt_pci_device *pdev, rt_bool_t enable) { rt_uint16_t msgctl; @@ -125,8 +181,18 @@ static void msi_write_enable(struct rt_pci_device *pdev, rt_bool_t enable) rt_pci_write_config_u16(pdev, pdev->msi_cap + PCIR_MSI_CTRL, msgctl); } +/** + * @brief Initialize IRQ affinity for an MSI descriptor + * + * Sets up per-vector CPU affinity and handles NUMA memory affinity + * for the MSI data address. + * + * @param[in] desc MSI descriptor + * @param[in] msi_index Vector index within the MSI group + * @param[in] cpumasks CPU affinity bitmaps + */ static void msi_affinity_init(struct rt_pci_msi_desc *desc, int msi_index, - rt_bitmap_t *cpumasks) + rt_bitmap_t *cpumasks) { int irq; struct rt_pic_irq *pirq; @@ -176,6 +242,11 @@ static void msi_affinity_init(struct rt_pci_msi_desc *desc, int msi_index, } } +/** + * @brief Shutdown MSI for a device (disable MSI, re-enable INTx) + * + * @param[in] pdev PCI device + */ void rt_pci_msi_shutdown(struct rt_pci_device *pdev) { struct rt_pci_msi_desc *desc; @@ -198,6 +269,13 @@ void rt_pci_msi_shutdown(struct rt_pci_device *pdev) pdev->msi_enabled = RT_FALSE; } +/** + * @brief Shutdown MSI-X for a device (disable MSI-X, re-enable INTx) + * + * Masks all MSI-X vectors and disables MSI-X in the global control register. + * + * @param[in] pdev PCI device + */ void rt_pci_msix_shutdown(struct rt_pci_device *pdev) { struct rt_pci_msi_desc *desc; @@ -218,6 +296,14 @@ void rt_pci_msix_shutdown(struct rt_pci_device *pdev) pdev->msix_enabled = RT_FALSE; } +/** + * @brief Free all IRQs allocated for MSI/MSI-X on a device + * + * Unmaps MSI-X table if present, cleans up IRQs, and frees + * all MSI descriptors. + * + * @param[in] pdev PCI device + */ void rt_pci_msi_free_irqs(struct rt_pci_device *pdev) { struct rt_pci_msi_desc *desc, *last_desc = RT_NULL; @@ -254,6 +340,15 @@ void rt_pci_msi_free_irqs(struct rt_pci_device *pdev) } } +/** + * @brief Write an MSI/MSI-X message to the device + * + * For MSI-X: writes address/data to the MSI-X table entry in MMIO space. + * For MSI: writes address/data to the MSI capability registers in config space. + * + * @param[in] desc MSI descriptor + * @param[in] msg New MSI message (address_lo, address_hi, data) + */ void rt_pci_msi_write_msg(struct rt_pci_msi_desc *desc, struct rt_pci_msi_msg *msg) { struct rt_pci_device *pdev = desc->pdev; @@ -330,6 +425,11 @@ void rt_pci_msi_write_msg(struct rt_pci_msi_desc *desc, struct rt_pci_msi_msg *m } } +/** + * @brief Mask an MSI/MSI-X IRQ + * + * @param[in] pirq PIC IRQ with associated MSI descriptor + */ void rt_pci_msi_mask_irq(struct rt_pic_irq *pirq) { struct rt_pci_msi_desc *desc; @@ -347,6 +447,11 @@ void rt_pci_msi_mask_irq(struct rt_pic_irq *pirq) } } +/** + * @brief Unmask an MSI/MSI-X IRQ + * + * @param[in] pirq PIC IRQ with associated MSI descriptor + */ void rt_pci_msi_unmask_irq(struct rt_pic_irq *pirq) { struct rt_pci_msi_desc *desc; @@ -364,8 +469,21 @@ void rt_pci_msi_unmask_irq(struct rt_pic_irq *pirq) } } +/** + * @brief Allocate interrupt vectors for a PCI device + * + * Tries MSI-X first (if RT_PCI_IRQ_F_MSIX flag set), then MSI, + * and finally falls back to legacy INTx. + * + * @param[in] pdev PCI device + * @param[in] min Minimum number of vectors required + * @param[in] max Maximum number of vectors desired + * @param[in] flags IRQ type flags (RT_PCI_IRQ_F_MSIX, RT_PCI_IRQ_F_MSI, RT_PCI_IRQ_F_LEGACY) + * @param[in] affinities CPU affinity bitmaps for each vector (can be NULL) + * @return Number of allocated vectors on success, or negative error code + */ rt_ssize_t rt_pci_alloc_vector(struct rt_pci_device *pdev, int min, int max, - rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))) + rt_uint32_t flags, RT_IRQ_AFFINITY_DECLARE((*affinities))) { rt_ssize_t res = -RT_ENOSYS; @@ -435,6 +553,11 @@ rt_ssize_t rt_pci_alloc_vector(struct rt_pci_device *pdev, int min, int max, return res; } +/** + * @brief Free all vectors allocated for a PCI device + * + * @param[in] pdev PCI device + */ void rt_pci_free_vector(struct rt_pci_device *pdev) { if (!pdev) @@ -447,6 +570,15 @@ void rt_pci_free_vector(struct rt_pci_device *pdev) rt_pci_irq_mask(pdev); } +/** + * @brief Verify that all MSI entries respect the device's 64-bit capability + * + * If a device is marked no_64bit_msi but the MSI controller assigned a + * 64-bit address (address_hi != 0), the configuration is invalid. + * + * @param[in] pdev PCI device + * @return RT_EOK if valid, -RT_EIO otherwise + */ static rt_err_t msi_verify_entries(struct rt_pci_device *pdev) { if (pdev->no_64bit_msi) @@ -458,9 +590,9 @@ static rt_err_t msi_verify_entries(struct rt_pci_device *pdev) if (desc->msg.address_hi) { LOG_D("%s: Arch assigned 64-bit MSI address %08x%08x" - "but device only supports 32 bits", - rt_dm_dev_get_name(&pdev->parent), - desc->msg.address_hi, desc->msg.address_lo); + "but device only supports 32 bits", + rt_dm_dev_get_name(&pdev->parent), + desc->msg.address_hi, desc->msg.address_lo); return -RT_EIO; } @@ -470,8 +602,18 @@ static rt_err_t msi_verify_entries(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Insert an MSI descriptor into the device's descriptor list + * + * Allocates memory for the descriptor plus per-vector affinity pointers + * (for MSI, not needed for MSI-X). + * + * @param[in] pdev PCI device + * @param[in] init_desc Initialized descriptor template to copy + * @return RT_EOK on success, -RT_ENOMEM on allocation failure + */ static rt_err_t msi_insert_desc(struct rt_pci_device *pdev, - struct rt_pci_msi_desc *init_desc) + struct rt_pci_msi_desc *init_desc) { rt_size_t msi_affinity_ptr_size = 0; struct rt_pci_msi_desc *msi_desc; @@ -502,6 +644,14 @@ static rt_err_t msi_insert_desc(struct rt_pci_device *pdev, return RT_EOK; } +/** + * @brief Get the number of MSI vectors supported by a device + * + * Reads the Multiple Message Capable field from the MSI control register. + * + * @param[in] pdev PCI device + * @return Number of vectors (1, 2, 4, 8, 16, or 32), or negative error + */ rt_ssize_t rt_pci_msi_vector_count(struct rt_pci_device *pdev) { rt_uint16_t msgctl; @@ -521,6 +671,12 @@ rt_ssize_t rt_pci_msi_vector_count(struct rt_pci_device *pdev) return 1 << ((msgctl & PCIM_MSICTRL_MMC_MASK) >> 1); } +/** + * @brief Disable MSI on a device + * + * @param[in] pdev PCI device + * @return RT_EOK on success + */ rt_err_t rt_pci_msi_disable(struct rt_pci_device *pdev) { if (!pdev) @@ -543,6 +699,16 @@ rt_err_t rt_pci_msi_disable(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Set up an MSI descriptor from the device's MSI capability + * + * Reads the MSI control register to determine 64-bit support, + * per-vector masking support, and multi-message capability. + * + * @param[in] pdev PCI device + * @param[in] nvec Number of vectors to allocate + * @return RT_EOK on success + */ static rt_err_t msi_setup_msi_desc(struct rt_pci_device *pdev, int nvec) { rt_uint16_t msgctl; @@ -590,8 +756,19 @@ static rt_err_t msi_setup_msi_desc(struct rt_pci_device *pdev, int nvec) return msi_insert_desc(pdev, &desc); } +/** + * @brief Initialize MSI capability and allocate vectors + * + * Disables MSI first, sets up the descriptor, masks all vectors, + * allocates IRQs from the MSI PIC, and finally enables MSI. + * + * @param[in] pdev PCI device + * @param[in] nvec Number of vectors + * @param[in] affinities CPU affinity bitmaps + * @return Number of allocated vectors, or negative error + */ static rt_ssize_t msi_capability_init(struct rt_pci_device *pdev, - int nvec, RT_IRQ_AFFINITY_DECLARE((*affinities))) + int nvec, RT_IRQ_AFFINITY_DECLARE((*affinities))) { rt_err_t err; struct rt_pci_msi_desc *desc; @@ -624,7 +801,7 @@ static rt_ssize_t msi_capability_init(struct rt_pci_device *pdev, rt_pci_msi_free_irqs(pdev); LOG_E("%s: Setup %s interrupts(%d) error = %s", - rt_dm_dev_get_name(&pdev->parent), "MSI", nvec, rt_strerror(err)); + rt_dm_dev_get_name(&pdev->parent), "MSI", nvec, rt_strerror(err)); return err; } @@ -650,8 +827,17 @@ static rt_ssize_t msi_capability_init(struct rt_pci_device *pdev, return nvec; } +/** + * @brief Enable MSI with a range of vectors and affinity + * + * @param[in] pdev PCI device + * @param[in] min Minimum vectors + * @param[in] max Maximum vectors + * @param[in] affinities CPU affinity bitmaps + * @return Number of allocated vectors, or negative error + */ rt_ssize_t rt_pci_msi_enable_range_affinity(struct rt_pci_device *pdev, - int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))) + int min, int max, RT_IRQ_AFFINITY_DECLARE((*affinities))) { int nvec = max; rt_size_t entries_nr; @@ -693,6 +879,12 @@ rt_ssize_t rt_pci_msi_enable_range_affinity(struct rt_pci_device *pdev, return msi_capability_init(pdev, nvec, affinities); } +/** + * @brief Get the number of MSI-X vectors supported + * + * @param[in] pdev PCI device + * @return Number of MSI-X table entries, or negative error + */ rt_ssize_t rt_pci_msix_vector_count(struct rt_pci_device *pdev) { rt_uint16_t msgctl; @@ -712,6 +904,12 @@ rt_ssize_t rt_pci_msix_vector_count(struct rt_pci_device *pdev) return rt_pci_msix_table_size(msgctl); } +/** + * @brief Disable MSI-X on a device + * + * @param[in] pdev PCI device + * @return RT_EOK on success + */ rt_err_t rt_pci_msix_disable(struct rt_pci_device *pdev) { if (!pdev) @@ -734,6 +932,16 @@ rt_err_t rt_pci_msix_disable(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Remap the MSI-X table from a device's BAR to virtual memory + * + * Reads the MSI-X Table Offset / Table BIR register, locates the + * corresponding BAR, and maps the table region into kernel space. + * + * @param[in] pdev PCI device + * @param[in] entries_nr Number of table entries to map + * @return Virtual address of the MSI-X table, or RT_NULL on failure + */ static void *msix_table_remap(struct rt_pci_device *pdev, rt_size_t entries_nr) { rt_uint8_t bir; @@ -755,8 +963,20 @@ static void *msix_table_remap(struct rt_pci_device *pdev, rt_size_t entries_nr) return rt_ioremap((void *)table_base_phys, entries_nr * PCIM_MSIX_ENTRY_SIZE); } +/** + * @brief Set up MSI-X descriptors for an array of entries + * + * Creates one MSI descriptor per MSI-X entry, recording each entry's + * current vector control state. + * + * @param[in] pdev PCI device + * @param[in] table_base Virtual address of the MSI-X table + * @param[in] entries Array of MSI-X entry specifications + * @param[in] nvec Number of entries + * @return RT_EOK on success + */ static rt_err_t msix_setup_msi_descs(struct rt_pci_device *pdev, - void *table_base, struct rt_pci_msix_entry *entries, int nvec) + void *table_base, struct rt_pci_msix_entry *entries, int nvec) { rt_err_t err; struct rt_pci_msi_desc desc; @@ -788,9 +1008,24 @@ static rt_err_t msix_setup_msi_descs(struct rt_pci_device *pdev, return err; } +/** + * @brief Initialize MSI-X capability and allocate vectors + * + * 1. Enable MSI-X with all vectors masked + * 2. Map the MSI-X table from device BAR + * 3. Set up descriptors and allocate IRQs + * 4. Update entry IRQ numbers and affinity + * 5. Unmask vectors at the function level + * + * @param[in] pdev PCI device + * @param[in] entries MSI-X entry specifications + * @param[in] nvec Number of entries + * @param[in] affinities CPU affinity bitmaps + * @return Number of allocated vectors, or negative error + */ static rt_ssize_t msix_capability_init(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int nvec, - RT_IRQ_AFFINITY_DECLARE((*affinities))) + struct rt_pci_msix_entry *entries, int nvec, + RT_IRQ_AFFINITY_DECLARE((*affinities))) { rt_err_t err; rt_uint16_t msgctl; @@ -840,7 +1075,7 @@ static rt_ssize_t msix_capability_init(struct rt_pci_device *pdev, rt_pci_msi_free_irqs(pdev); LOG_E("%s: Setup %s interrupts(%d) error = %s", - rt_dm_dev_get_name(&pdev->parent), "MSI-X", nvec, rt_strerror(err)); + rt_dm_dev_get_name(&pdev->parent), "MSI-X", nvec, rt_strerror(err)); goto _out_disbale_msix; } @@ -878,9 +1113,19 @@ static rt_ssize_t msix_capability_init(struct rt_pci_device *pdev, return err; } +/** + * @brief Enable MSI-X with a range of vectors and affinity + * + * @param[in] pdev PCI device + * @param[in] entries MSI-X entry specifications (array of rt_pci_msix_entry) + * @param[in] min Minimum vectors + * @param[in] max Maximum vectors + * @param[in] affinities CPU affinity bitmaps + * @return Number of allocated vectors, or negative error + */ rt_ssize_t rt_pci_msix_enable_range_affinity(struct rt_pci_device *pdev, - struct rt_pci_msix_entry *entries, int min, int max, - RT_IRQ_AFFINITY_DECLARE((*affinities))) + struct rt_pci_msix_entry *entries, int min, int max, + RT_IRQ_AFFINITY_DECLARE((*affinities))) { int nvec = max; rt_size_t entries_nr; @@ -940,7 +1185,7 @@ rt_ssize_t rt_pci_msix_enable_range_affinity(struct rt_pci_device *pdev, if (target->index == entries[j].index) { LOG_E("%s: msix entry[%d].index = entry[%d].index", - rt_dm_dev_get_name(&pdev->parent), i, j); + rt_dm_dev_get_name(&pdev->parent), i, j); return -RT_EINVAL; } diff --git a/components/drivers/pci/ofw.c b/components/drivers/pci/ofw.c index 450402d6bda..847b07e34d5 100644 --- a/components/drivers/pci/ofw.c +++ b/components/drivers/pci/ofw.c @@ -8,6 +8,17 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file ofw.c + * @brief PCI Device Tree (Open Firmware) integration + * + * Integrates PCI bus enumeration with the device tree framework: + * - Parses "bus-range", "ranges", and "dma-ranges" properties for host bridges + * - Parses "interrupt-map" for INTx interrupt routing + * - Discovers MSI parent controllers via "msi-parent" and "msi-map" + * - Associates device tree nodes with enumerated PCI devices + */ + #include #include @@ -21,6 +32,19 @@ #include #include +/** + * @brief Parse interrupt routing for a PCI device from device tree + * + * Follows the PCI interrupt mapping rules from the Devicetree Specification: + * 1. Check the device's own device tree node for "interrupts" property + * 2. Check for a local "interrupt-map" in the device node + * 3. Walk up the PCI tree through bridges, swizzling INTx pins + * 4. At the root bus, use the host bridge's "interrupt-map" property + * + * @param[in] pdev PCI device + * @param[out] out_irq Parsed interrupt specifier + * @return RT_EOK on success, -RT_ENOSYS if no interrupt pin, error code otherwise + */ static rt_err_t pci_ofw_irq_parse(struct rt_pci_device *pdev, struct rt_ofw_cell_args *out_irq) { rt_err_t err = RT_EOK; @@ -115,21 +139,32 @@ static rt_err_t pci_ofw_irq_parse(struct rt_pci_device *pdev, struct rt_ofw_cell if (err == -RT_EEMPTY) { LOG_W("PCI-Device<%s> no interrupt-map found, INTx interrupts not available", - rt_dm_dev_get_name(&pdev->parent)); + rt_dm_dev_get_name(&pdev->parent)); LOG_W("PCI-Device<%s> possibly some PCI slots don't have level triggered interrupts capability", - rt_dm_dev_get_name(&pdev->parent)); + rt_dm_dev_get_name(&pdev->parent)); } else if (err && err != -RT_ENOSYS) { LOG_E("PCI-Device<%s> irq parse failed with err = %s", - rt_dm_dev_get_name(&pdev->parent), rt_strerror(err)); + rt_dm_dev_get_name(&pdev->parent), rt_strerror(err)); } return err; } +/** + * @brief Parse and map an IRQ for a PCI device from device tree + * + * Wrapper around pci_ofw_irq_parse() + rt_ofw_map_irq(). + * Called by the host bridge's irq_map callback. + * + * @param[in] pdev PCI device + * @param[in] slot Slot number at root bus (after swizzling) + * @param[in] pin INTx pin number (after swizzling) + * @return IRQ number on success, -1 on failure + */ int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev, - rt_uint8_t slot, rt_uint8_t pin) + rt_uint8_t slot, rt_uint8_t pin) { int irq = -1; rt_err_t status; @@ -158,9 +193,32 @@ int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev, return irq; } +/** + * @brief Parse PCI "ranges" or "dma-ranges" property from device tree + * + * The ranges property defines the mapping between CPU address space + * and PCI bus address space. Each entry contains: + * phys.hi: npt000ss bbbbbbbb dddddfff rrrrrrrr + * phys.mid/phys.lo: PCI address + * cpu.addr: CPU physical address + * size: region size + * + * where: + * n: relocatable flag, p: prefetchable, t: aliased + * ss: space code (00=config, 01=I/O, 10=32-bit mem, 11=64-bit mem) + * + * @param[in] dev_np Device tree node containing the ranges property + * @param[in] propname Property name ("ranges" or "dma-ranges") + * @param[in] phy_addr_cells Number of cells for PCI address (typically 3) + * @param[in] phy_size_cells Number of cells for size + * @param[in] cpu_addr_cells Number of cells for CPU address + * @param[out] out_regions Parsed bus regions array (caller must free) + * @param[out] out_regions_nr Number of parsed regions + * @return RT_EOK on success + */ static rt_err_t pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, const char *propname, - int phy_addr_cells, int phy_size_cells, int cpu_addr_cells, - struct rt_pci_bus_region **out_regions, rt_size_t *out_regions_nr) + int phy_addr_cells, int phy_size_cells, int cpu_addr_cells, + struct rt_pci_bus_region **out_regions, rt_size_t *out_regions_nr) { const fdt32_t *cell; rt_ssize_t total_cells; @@ -227,8 +285,7 @@ static rt_err_t pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, const char *pro if (space_code & 2) { - (*out_regions)[i].flags = phy_addr[0] & (1U << 30) ? - PCI_BUS_REGION_F_PREFETCH : PCI_BUS_REGION_F_MEM; + (*out_regions)[i].flags = phy_addr[0] & (1U << 30) ? PCI_BUS_REGION_F_PREFETCH : PCI_BUS_REGION_F_MEM; } else if (space_code & 1) { @@ -245,8 +302,19 @@ static rt_err_t pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, const char *pro return RT_EOK; } +/** + * @brief Parse bus address ranges from device tree for a host bridge + * + * Reads #address-cells (must be 3), #size-cells, and parses + * both "ranges" (CPU→PCI mapping) and "dma-ranges" (PCI→CPU mapping) + * properties. Calls rt_pci_region_setup() to finalize bus_start values. + * + * @param[in] dev_np Device tree node for the host bridge + * @param[in] host_bridge PCI host bridge to populate + * @return RT_EOK on success + */ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge) + struct rt_pci_host_bridge *host_bridge) { rt_err_t err; int phy_addr_cells = -1, phy_size_cells = -1, cpu_addr_cells; @@ -266,8 +334,8 @@ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, } if (pci_ofw_parse_ranges(dev_np, "ranges", - phy_addr_cells, phy_size_cells, cpu_addr_cells, - &host_bridge->bus_regions, &host_bridge->bus_regions_nr)) + phy_addr_cells, phy_size_cells, cpu_addr_cells, + &host_bridge->bus_regions, &host_bridge->bus_regions_nr)) { return -RT_EINVAL; } @@ -281,8 +349,8 @@ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, } err = pci_ofw_parse_ranges(dev_np, "dma-ranges", - phy_addr_cells, phy_size_cells, cpu_addr_cells, - &host_bridge->dma_regions, &host_bridge->dma_regions_nr); + phy_addr_cells, phy_size_cells, cpu_addr_cells, + &host_bridge->dma_regions, &host_bridge->dma_regions_nr); if (err && err != -RT_EEMPTY) { @@ -290,7 +358,7 @@ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, host_bridge->bus_regions_nr = 0; LOG_E("%s: Read dma-ranges error = %s", rt_ofw_node_full_name(dev_np), - rt_strerror(err)); + rt_strerror(err)); return err; } @@ -298,8 +366,19 @@ rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, return RT_EOK; } +/** + * @brief Initialize a host bridge from device tree + * + * Sets up the IRQ slot/pin swizzling and mapping callbacks, + * parses "bus-range" (default: 0x00-0xff), discovers the + * PCI domain from device tree, and parses address ranges. + * + * @param[in] dev_np Device tree node for the host bridge + * @param[in] host_bridge PCI host bridge to initialize + * @return RT_EOK on success + */ rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np, - struct rt_pci_host_bridge *host_bridge) + struct rt_pci_host_bridge *host_bridge) { rt_err_t err; const char *propname; @@ -317,7 +396,7 @@ rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np, host_bridge->bus_range[0] = 0x00; host_bridge->bus_range[1] = 0xff; LOG_I("%s: No \"%s\" found, using [%#02x, %#02x]", rt_ofw_node_full_name(dev_np), "bus-range", - host_bridge->bus_range[0], host_bridge->bus_range[1]); + host_bridge->bus_range[0], host_bridge->bus_range[1]); } propname = rt_ofw_get_prop_fuzzy_name(dev_np, ",pci-domain$"); @@ -328,6 +407,12 @@ rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np, return err; } +/** + * @brief Initialize OFW data for a PCI bus (currently a no-op) + * + * @param[in] bus PCI bus + * @return RT_EOK + */ rt_err_t rt_pci_ofw_bus_init(struct rt_pci_bus *bus) { rt_err_t err = RT_EOK; @@ -335,6 +420,12 @@ rt_err_t rt_pci_ofw_bus_init(struct rt_pci_bus *bus) return err; } +/** + * @brief Free OFW data for a PCI bus (currently a no-op) + * + * @param[in] bus PCI bus + * @return RT_EOK + */ rt_err_t rt_pci_ofw_bus_free(struct rt_pci_bus *bus) { rt_err_t err = RT_EOK; @@ -460,6 +551,19 @@ rt_err_t rt_pci_ofw_bus_free(struct rt_pci_bus *bus) * }; * }; */ + +/** + * @brief Initialize the MSI PIC for a device from device tree + * + * Finds the MSI parent controller by: + * 1. Checking the host bridge's "msi-parent" phandle + * 2. Falling back to "msi-map" lookup using the device's Requester ID + * + * Validates that the MSI PIC supports the required operations: + * irq_compose_msi_msg, irq_alloc_msi, irq_free_msi. + * + * @param[in] pdev PCI device + */ static void ofw_msi_pic_init(struct rt_pci_device *pdev) { #ifdef RT_PCI_MSI @@ -505,21 +609,21 @@ static void ofw_msi_pic_init(struct rt_pci_device *pdev) if (!pdev->msi_pic->ops->irq_compose_msi_msg) { LOG_E("%s: MSI pic MUST implemented %s", - rt_ofw_node_full_name(msi_ic_np), "irq_compose_msi_msg"); + rt_ofw_node_full_name(msi_ic_np), "irq_compose_msi_msg"); RT_ASSERT(0); } if (!pdev->msi_pic->ops->irq_alloc_msi) { LOG_E("%s: MSI pic MUST implemented %s", - rt_ofw_node_full_name(msi_ic_np), "irq_alloc_msi"); + rt_ofw_node_full_name(msi_ic_np), "irq_alloc_msi"); RT_ASSERT(0); } if (!pdev->msi_pic->ops->irq_free_msi) { LOG_E("%s: MSI pic MUST implemented %s", - rt_ofw_node_full_name(msi_ic_np), "irq_free_msi"); + rt_ofw_node_full_name(msi_ic_np), "irq_free_msi"); RT_ASSERT(0); } @@ -528,6 +632,15 @@ static void ofw_msi_pic_init(struct rt_pci_device *pdev) #endif } +/** + * @brief Extract the devfn from a device tree node's "reg" property + * + * PCI devices in device tree have reg = <(bus << 16) | (devfn << 8) 0 0 0 0> + * The bus portion is ignored here; only devfn (bits 15:8) is extracted. + * + * @param[in] np Device tree node + * @return devfn on success, negative error code on failure + */ static rt_int32_t ofw_pci_devfn(struct rt_ofw_node *np) { rt_int32_t res; @@ -538,6 +651,16 @@ static rt_int32_t ofw_pci_devfn(struct rt_ofw_node *np) return res > 0 ? ((reg[0] >> 8) & 0xff) : res; } +/** + * @brief Find a device tree child node matching a given devfn + * + * Iterates through the parent's children. Also checks inside + * "multifunc-device" containers for multi-function PCI devices. + * + * @param[in] np Parent device tree node + * @param[in] devfn Device/function number to find + * @return Matching device tree node, or RT_NULL if not found + */ static struct rt_ofw_node *ofw_find_device(struct rt_ofw_node *np, rt_uint32_t devfn) { struct rt_ofw_node *dev_np, *mfd_np; @@ -566,6 +689,16 @@ static struct rt_ofw_node *ofw_find_device(struct rt_ofw_node *np, rt_uint32_t d return RT_NULL; } +/** + * @brief Associate a device tree node with an enumerated PCI device + * + * Initializes the MSI PIC from device tree, then walks the DT to + * find the node matching this device's devfn. This is called + * during device setup (rt_pci_setup_device). + * + * @param[in] pdev PCI device (ofw_node set on success) + * @return RT_EOK + */ rt_err_t rt_pci_ofw_device_init(struct rt_pci_device *pdev) { struct rt_ofw_node *np = RT_NULL; @@ -599,6 +732,12 @@ rt_err_t rt_pci_ofw_device_init(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Release the device tree node reference for a PCI device + * + * @param[in] pdev PCI device + * @return RT_EOK + */ rt_err_t rt_pci_ofw_device_free(struct rt_pci_device *pdev) { if (!pdev) diff --git a/components/drivers/pci/pci.c b/components/drivers/pci/pci.c index 3ba3967e7d8..62d7ce04859 100644 --- a/components/drivers/pci/pci.c +++ b/components/drivers/pci/pci.c @@ -8,6 +8,15 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file pci.c + * @brief PCI bus core API implementation + * + * Provides fundamental PCI device operations: capability discovery, + * bus master control, INTx interrupt control, resource allocation, + * driver-device matching, and bus enumeration. + */ + #include #include @@ -19,16 +28,26 @@ #include #include +/** @brief Convenience wrapper for hardware spinlock acquire */ rt_inline void spin_lock(struct rt_spinlock *spinlock) { rt_hw_spin_lock(&spinlock->lock); } +/** @brief Convenience wrapper for hardware spinlock release */ rt_inline void spin_unlock(struct rt_spinlock *spinlock) { rt_hw_spin_unlock(&spinlock->lock); } +/** + * @brief Get the PCI domain number for a device + * + * Each independent PCI host bridge hierarchy belongs to a distinct domain. + * + * @param[in] pdev PCI device + * @return Domain number, or RT_UINT32_MAX on error + */ rt_uint32_t rt_pci_domain(struct rt_pci_device *pdev) { struct rt_pci_host_bridge *host_bridge; @@ -46,8 +65,22 @@ rt_uint32_t rt_pci_domain(struct rt_pci_device *pdev) return RT_UINT32_MAX; } +/** + * @brief Find the next PCI capability entry in the linked list + * + * PCI capability structures form a linked list starting from the + * Capabilities Pointer register. This function walks the list with + * a TTL (time-to-live) limit to prevent infinite loops on broken hardware. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number (bits 7-3: device, 2-0: function) + * @param[in] pos Starting offset of the current capability + * @param[in] cap Target capability ID to find + * @param[in,out] ttl Remaining search depth counter (decremented each step) + * @return Capability offset, or 0 if not found + */ static rt_uint8_t pci_find_next_cap_ttl(struct rt_pci_bus *bus, - rt_uint32_t devfn, rt_uint8_t pos, int cap, int *ttl) + rt_uint32_t devfn, rt_uint8_t pos, int cap, int *ttl) { rt_uint8_t ret = 0, id; rt_uint16_t ent; @@ -80,16 +113,37 @@ static rt_uint8_t pci_find_next_cap_ttl(struct rt_pci_bus *bus, return ret; } +/** + * @brief Find next capability entry with default TTL + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] pos Starting capability offset + * @param[in] cap Capability ID to find + * @return Capability offset, or 0 if not found + */ static rt_uint8_t pci_find_next_cap(struct rt_pci_bus *bus, - rt_uint32_t devfn, rt_uint8_t pos, int cap) + rt_uint32_t devfn, rt_uint8_t pos, int cap) { int ttl = RT_PCI_FIND_CAP_TTL; return pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl); } +/** + * @brief Get the starting offset of capabilities for a device + * + * Returns the Capabilities Pointer offset based on header type. + * Only type 0 (normal) and type 1 (PCI-to-PCI bridge) have their + * capabilities at offset 0x34; CardBus devices use 0x14. + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] hdr_type Header type value from configuration space + * @return Starting capability offset, or 0 if no capabilities present + */ static rt_uint8_t pci_bus_find_cap_start(struct rt_pci_bus *bus, - rt_uint32_t devfn, rt_uint8_t hdr_type) + rt_uint32_t devfn, rt_uint8_t hdr_type) { rt_uint8_t res = 0; rt_uint16_t status; @@ -114,6 +168,14 @@ static rt_uint8_t pci_bus_find_cap_start(struct rt_pci_bus *bus, return res; } +/** + * @brief Find a capability by ID on a specific bus/device/function + * + * @param[in] bus PCI bus + * @param[in] devfn Device/function number + * @param[in] cap Capability ID (e.g. PCIY_MSI, PCIY_EXPRESS) + * @return Capability offset, or RT_UINT8_MAX if not found + */ rt_uint8_t rt_pci_bus_find_capability(struct rt_pci_bus *bus, rt_uint32_t devfn, int cap) { rt_uint8_t hdr_type, ret = RT_UINT8_MAX; @@ -133,6 +195,15 @@ rt_uint8_t rt_pci_bus_find_capability(struct rt_pci_bus *bus, rt_uint32_t devfn, return ret; } +/** + * @brief Find a standard PCI capability on a device + * + * Searches the PCI capability linked list for a specific capability ID. + * + * @param[in] pdev PCI device + * @param[in] cap Capability ID (e.g. PCIY_MSI, PCIY_PMG, PCIY_EXPRESS) + * @return Capability register offset (0-255), or RT_UINT8_MAX if not found + */ rt_uint8_t rt_pci_find_capability(struct rt_pci_device *pdev, int cap) { rt_uint8_t res = RT_UINT8_MAX; @@ -150,6 +221,14 @@ rt_uint8_t rt_pci_find_capability(struct rt_pci_device *pdev, int cap) return res; } +/** + * @brief Find the next instance of a capability (continue from a previous find) + * + * @param[in] pdev PCI device + * @param[in] pos Current capability position (offset in config space) + * @param[in] cap Capability ID to find + * @return Next capability offset, or RT_UINT8_MAX if no more found + */ rt_uint8_t rt_pci_find_next_capability(struct rt_pci_device *pdev, rt_uint8_t pos, int cap) { rt_uint8_t res = RT_UINT8_MAX; @@ -162,11 +241,29 @@ rt_uint8_t rt_pci_find_next_capability(struct rt_pci_device *pdev, rt_uint8_t po return res; } +/** + * @brief Find a PCI Express extended capability on a device + * + * PCIe extended capabilities start at offset 0x100 and form a + * linked list in the extended configuration space (up to 4KB). + * + * @param[in] pdev PCI device + * @param[in] cap Extended capability ID (e.g. PCIZ_AER, PCIZ_ARI) + * @return Extended capability offset (>= 0x100), or 0 if not found + */ rt_uint16_t rt_pci_find_ext_capability(struct rt_pci_device *pdev, int cap) { return rt_pci_find_ext_next_capability(pdev, 0, cap); } +/** + * @brief Find the next PCIe extended capability starting from a given position + * + * @param[in] pdev PCI device + * @param[in] pos Starting offset in extended config space (0 to start from beginning) + * @param[in] cap Extended capability ID + * @return Extended capability offset, or 0 if not found + */ rt_uint16_t rt_pci_find_ext_next_capability(struct rt_pci_device *pdev, rt_uint16_t pos, int cap) { int ttl; @@ -223,6 +320,14 @@ rt_uint16_t rt_pci_find_ext_next_capability(struct rt_pci_device *pdev, rt_uint1 return 0; } +/** + * @brief Internal helper: enable or disable bus mastering on a PCI device + * + * Bus mastering allows the device to initiate DMA transactions on the PCI bus. + * + * @param[in] pdev PCI device + * @param[in] enable RT_TRUE to enable bus mastering, RT_FALSE to disable + */ static void pci_set_master(struct rt_pci_device *pdev, rt_bool_t enable) { rt_uint16_t old_cmd, cmd; @@ -246,6 +351,13 @@ static void pci_set_master(struct rt_pci_device *pdev, rt_bool_t enable) pdev->busmaster = !!enable; } +/** + * @brief Enable bus mastering (DMA capability) on a PCI device + * + * Most PCI device drivers need to call this to enable DMA transfers. + * + * @param[in] pdev PCI device + */ void rt_pci_set_master(struct rt_pci_device *pdev) { if (pdev) @@ -254,6 +366,11 @@ void rt_pci_set_master(struct rt_pci_device *pdev) } } +/** + * @brief Disable bus mastering on a PCI device + * + * @param[in] pdev PCI device + */ void rt_pci_clear_master(struct rt_pci_device *pdev) { if (pdev) @@ -262,6 +379,15 @@ void rt_pci_clear_master(struct rt_pci_device *pdev) } } +/** + * @brief Enable or disable legacy INTx interrupts on a PCI device + * + * Controls the Interrupt Disable bit in the PCI Command register. + * When INTx is disabled, the device must use MSI or MSI-X for interrupts. + * + * @param[in] pdev PCI device + * @param[in] enable RT_TRUE to enable INTx, RT_FALSE to disable + */ void rt_pci_intx(struct rt_pci_device *pdev, rt_bool_t enable) { rt_uint16_t pci_command, new; @@ -288,6 +414,16 @@ void rt_pci_intx(struct rt_pci_device *pdev, rt_bool_t enable) } } +/** + * @brief Atomically check INTx status and optionally mask/unmask + * + * Reads the command/status DWORD, checks the INTx status bit, + * and conditionally sets/clears the INTx disable bit. + * + * @param[in] pdev PCI device + * @param[in] mask RT_TRUE to mask INTx, RT_FALSE to unmask + * @return RT_TRUE if the operation was performed, RT_FALSE if skipped + */ static rt_bool_t pci_check_and_set_intx_mask(struct rt_pci_device *pdev, rt_bool_t mask) { rt_ubase_t level; @@ -332,6 +468,15 @@ static rt_bool_t pci_check_and_set_intx_mask(struct rt_pci_device *pdev, rt_bool return res; } +/** + * @brief Check if the device generated an INTx interrupt, and mask it + * + * Used by interrupt handlers to identify the interrupt source and + * prevent further interrupts while the handler runs. + * + * @param[in] pdev PCI device + * @return RT_TRUE if this device was the interrupt source + */ rt_bool_t rt_pci_check_and_mask_intx(struct rt_pci_device *pdev) { rt_bool_t res = RT_FALSE; @@ -344,6 +489,14 @@ rt_bool_t rt_pci_check_and_mask_intx(struct rt_pci_device *pdev) return res; } +/** + * @brief Check interrupt status and unmask INTx + * + * Used to re-enable interrupts after the handler completes. + * + * @param[in] pdev PCI device + * @return RT_TRUE if interrupt is pending (next IRQ ready) + */ rt_bool_t rt_pci_check_and_unmask_intx(struct rt_pci_device *pdev) { rt_bool_t res = RT_FALSE; @@ -356,6 +509,11 @@ rt_bool_t rt_pci_check_and_unmask_intx(struct rt_pci_device *pdev) return res; } +/** + * @brief Mask a PCI device's interrupt (disable INTx + mask at interrupt controller if unused) + * + * @param[in] pdev PCI device + */ void rt_pci_irq_mask(struct rt_pci_device *pdev) { if (pdev) @@ -379,6 +537,11 @@ void rt_pci_irq_mask(struct rt_pci_device *pdev) } } +/** + * @brief Unmask a PCI device's interrupt (enable at controller + re-enable INTx) + * + * @param[in] pdev PCI device + */ void rt_pci_irq_unmask(struct rt_pci_device *pdev) { if (pdev) @@ -388,6 +551,12 @@ void rt_pci_irq_unmask(struct rt_pci_device *pdev) } } +/** + * @brief Find the root PCI bus by walking up the parent chain + * + * @param[in] bus Starting bus (any level in the hierarchy) + * @return Root bus pointer, or RT_NULL on error + */ struct rt_pci_bus *rt_pci_find_root_bus(struct rt_pci_bus *bus) { if (!bus) @@ -403,6 +572,15 @@ struct rt_pci_bus *rt_pci_find_root_bus(struct rt_pci_bus *bus) return bus; } +/** + * @brief Find the host bridge for a given PCI bus + * + * Walks up to the root bus, then retrieves the host bridge that + * manages that bus hierarchy. + * + * @param[in] bus PCI bus (any level) + * @return Host bridge pointer, or RT_NULL on error + */ struct rt_pci_host_bridge *rt_pci_find_host_bridge(struct rt_pci_bus *bus) { if (!bus) @@ -418,6 +596,17 @@ struct rt_pci_host_bridge *rt_pci_find_host_bridge(struct rt_pci_bus *bus) return RT_NULL; } +/** + * @brief Perform INTx interrupt line swizzling for PCI-to-PCI bridges + * + * PCI Express and conventional PCI use a swizzling algorithm to route + * INTA-D pins through bridges. The output pin is rotated based on + * the device's slot number. + * + * @param[in] pdev PCI device + * @param[in] pin Input INTx pin number (1=INTA, 2=INTB, 3=INTC, 4=INTD) + * @return Swizzled (rotated) pin number + */ rt_uint8_t rt_pci_irq_intx(struct rt_pci_device *pdev, rt_uint8_t pin) { int slot = 0; @@ -430,6 +619,16 @@ rt_uint8_t rt_pci_irq_intx(struct rt_pci_device *pdev, rt_uint8_t pin) return (((pin - 1) + slot) % 4) + 1; } +/** + * @brief Perform INTx swizzling up through the PCI bridge hierarchy + * + * Follows the device's INTx pin through all PCI-to-PCI bridges + * to determine the final pin and slot at the root bus level. + * + * @param[in] pdev PCI device + * @param[in,out] pinp INTx pin number (updated through bridge traversal) + * @return Slot number at the root bus + */ rt_uint8_t rt_pci_irq_slot(struct rt_pci_device *pdev, rt_uint8_t *pinp) { rt_uint8_t pin = *pinp; @@ -445,6 +644,15 @@ rt_uint8_t rt_pci_irq_slot(struct rt_pci_device *pdev, rt_uint8_t *pinp) return RT_PCI_SLOT(pdev->devfn); } +/** + * @brief Initialize bus resource regions for a host bridge + * + * Ensures no PCI resource is allocated from address 0 (illegal per + * PCI 2.1+ spec). Sets bus_start to max(0x1000, phy_addr). + * + * @param[in] host_bridge PCI host bridge to initialize + * @return RT_EOK on success, -RT_EEMPTY if no regions defined + */ rt_err_t rt_pci_region_setup(struct rt_pci_host_bridge *host_bridge) { rt_err_t err = host_bridge->bus_regions_nr == 0 ? -RT_EEMPTY : RT_EOK; @@ -460,9 +668,7 @@ rt_err_t rt_pci_region_setup(struct rt_pci_host_bridge *host_bridge) region->bus_start = rt_max_t(rt_size_t, 0x1000, region->phy_addr); LOG_I("Bus %s region(%d):", - region->flags == PCI_BUS_REGION_F_MEM ? "Memory" : - (region->flags == PCI_BUS_REGION_F_PREFETCH ? "Prefetchable Mem" : - (region->flags == PCI_BUS_REGION_F_IO ? "I/O" : "Unknown")), i); + region->flags == PCI_BUS_REGION_F_MEM ? "Memory" : (region->flags == PCI_BUS_REGION_F_PREFETCH ? "Prefetchable Mem" : (region->flags == PCI_BUS_REGION_F_IO ? "I/O" : "Unknown")), i); LOG_I(" cpu: [%p, %p]", region->cpu_addr, (region->cpu_addr + region->size - 1)); LOG_I(" physical: [%p, %p]", region->phy_addr, (region->phy_addr + region->size - 1)); } @@ -470,8 +676,23 @@ rt_err_t rt_pci_region_setup(struct rt_pci_host_bridge *host_bridge) return err; } +/** + * @brief Allocate a region of bus address space from the host bridge + * + * Allocates from the host bridge's bus resource regions. The region + * is selected by matching the resource flags (I/O, Memory, Prefetchable). + * If a 64-bit allocation is requested but no 64-bit space is available, + * it falls back to 32-bit. + * + * @param[in] host_bridge PCI host bridge with defined bus regions + * @param[out] out_addr Allocated bus address on success + * @param[in] size Requested size in bytes + * @param[in] flags Resource type flags (PCI_BUS_REGION_F_*) + * @param[in] mem64 RT_TRUE to require 64-bit address, RT_FALSE for 32-bit + * @return Pointer to the matching bus region, or RT_NULL on failure + */ struct rt_pci_bus_region *rt_pci_region_alloc(struct rt_pci_host_bridge *host_bridge, - void **out_addr, rt_size_t size, rt_ubase_t flags, rt_bool_t mem64) + void **out_addr, rt_size_t size, rt_ubase_t flags, rt_bool_t mem64) { struct rt_pci_bus_region *bus_region, *region = RT_NULL; @@ -525,8 +746,20 @@ struct rt_pci_bus_region *rt_pci_region_alloc(struct rt_pci_host_bridge *host_br return region; } +/** + * @brief Allocate bus resources (BARs and ROM) for a PCI device + * + * Scans all BARs (Base Address Registers) of the device, determines + * their size and type (I/O, Memory 32-bit, Memory 64-bit), allocates + * bus address space, and programs the BARs with the assigned addresses. + * Also handles the Expansion ROM BAR if present. + * + * @param[in] host_bridge PCI host bridge providing address space + * @param[in] pdev PCI device to allocate resources for + * @return RT_EOK on success, -RT_ERROR if any BAR could not be allocated + */ rt_err_t rt_pci_device_alloc_resource(struct rt_pci_host_bridge *host_bridge, - struct rt_pci_device *pdev) + struct rt_pci_device *pdev) { rt_err_t err = RT_EOK; rt_size_t size; @@ -649,15 +882,15 @@ rt_err_t rt_pci_device_alloc_resource(struct rt_pci_host_bridge *host_bridge, if (mem64) { bar_base += sizeof(rt_uint32_t); - #ifdef RT_PCI_SYS_64BIT +#ifdef RT_PCI_SYS_64BIT rt_pci_write_config_u32(pdev, bar_base, (rt_uint32_t)(addr >> 32)); - #else +#else /* * If we are a 64-bit decoder then increment to the upper 32 bits * of the bar and force it to locate in the lower 4GB of memory. */ rt_pci_write_config_u32(pdev, bar_base, 0UL); - #endif +#endif } pdev->resource[i].size = size; @@ -716,7 +949,18 @@ rt_err_t rt_pci_device_alloc_resource(struct rt_pci_host_bridge *host_bridge, return err; } -struct rt_pci_bus_resource *rt_pci_find_bar(struct rt_pci_device* pdev,rt_ubase_t flags,int index) +/** + * @brief Find a specific BAR resource by flags and index + * + * Searches a device's BAR resources for a BAR matching the given + * flags and satisfying the index count (1-based). + * + * @param[in] pdev PCI device + * @param[in] flags Resource type flags (PCI_BUS_REGION_F_*) + * @param[in] index 1-based index of the matching BAR to return + * @return Pointer to the matching BAR resource, or RT_NULL if not found + */ +struct rt_pci_bus_resource *rt_pci_find_bar(struct rt_pci_device *pdev, rt_ubase_t flags, int index) { for (int i = 0; i < RT_PCI_BAR_NR_MAX; i++) { @@ -730,8 +974,26 @@ struct rt_pci_bus_resource *rt_pci_find_bar(struct rt_pci_device* pdev,rt_ubase_ return RT_NULL; } +/** + * @brief Enumerate all PCI devices in a bus hierarchy + * + * Performs a depth-first walk of the PCI bus tree, calling the + * callback for each device. The callback can stop enumeration by + * returning RT_TRUE. Reference counting protects devices from + * being freed during enumeration. + * + * The tree walk algorithm: + * 1. Descend to the deepest leaf bus + * 2. Visit each device on the leaf bus + * 3. Ascend to the parent, visit parent devices + * 4. Move to next sibling bus and repeat + * + * @param[in] bus Root bus to start enumeration from + * @param[in] callback Callback function for each device + * @param[in] data User data passed to callback + */ void rt_pci_enum_device(struct rt_pci_bus *bus, - rt_bool_t (callback(struct rt_pci_device *, void *)), void *data) + rt_bool_t(callback(struct rt_pci_device *, void *)), void *data) { rt_bool_t is_end = RT_FALSE; struct rt_spinlock *lock; @@ -858,8 +1120,18 @@ void rt_pci_enum_device(struct rt_pci_bus *bus, } } +/** + * @brief Match a PCI device against a single device ID entry + * + * Checks vendor, device, subsystem vendor/device, and class mask. + * PCI_ANY_ID acts as a wildcard that matches any value. + * + * @param[in] pdev PCI device + * @param[in] id Device ID entry to match against + * @return Pointer to id if matched, RT_NULL otherwise + */ const struct rt_pci_device_id *rt_pci_match_id(struct rt_pci_device *pdev, - const struct rt_pci_device_id *id) + const struct rt_pci_device_id *id) { if ((id->vendor == PCI_ANY_ID || id->vendor == pdev->vendor) && (id->device == PCI_ANY_ID || id->device == pdev->device) && @@ -873,8 +1145,18 @@ const struct rt_pci_device_id *rt_pci_match_id(struct rt_pci_device *pdev, return RT_NULL; } +/** + * @brief Match a PCI device against an array of device IDs + * + * Iterates through the ID table until a match is found or the + * sentinel entry (all-zero) is reached. + * + * @param[in] pdev PCI device + * @param[in] ids Array of device IDs, terminated by sentinel + * @return Pointer to matching ID, or RT_NULL if no match + */ const struct rt_pci_device_id *rt_pci_match_ids(struct rt_pci_device *pdev, - const struct rt_pci_device_id *ids) + const struct rt_pci_device_id *ids) { while (ids->vendor || ids->subsystem_vendor || ids->class_mask) { @@ -891,6 +1173,15 @@ const struct rt_pci_device_id *rt_pci_match_ids(struct rt_pci_device *pdev, static struct rt_bus pci_bus; +/** + * @brief Register a PCI driver + * + * Associates the driver with the PCI bus and registers it with + * the driver framework. + * + * @param[in] pdrv PCI driver to register + * @return RT_EOK on success + */ rt_err_t rt_pci_driver_register(struct rt_pci_driver *pdrv) { RT_ASSERT(pdrv != RT_NULL); @@ -905,6 +1196,12 @@ rt_err_t rt_pci_driver_register(struct rt_pci_driver *pdrv) return rt_driver_register(&pdrv->parent); } +/** + * @brief Register a PCI device on the PCI bus + * + * @param[in] pdev PCI device to register + * @return RT_EOK on success + */ rt_err_t rt_pci_device_register(struct rt_pci_device *pdev) { rt_err_t err; @@ -918,6 +1215,16 @@ rt_err_t rt_pci_device_register(struct rt_pci_device *pdev) return RT_EOK; } +/** + * @brief Bus match callback: match a PCI driver to a PCI device + * + * First tries name-based matching, then falls back to ID-based + * matching using the driver's device ID table. + * + * @param[in] drv Driver to match + * @param[in] dev Device to match + * @return RT_TRUE if driver can handle this device + */ static rt_bool_t pci_match(rt_driver_t drv, rt_device_t dev) { rt_bool_t match = RT_FALSE; @@ -939,6 +1246,14 @@ static rt_bool_t pci_match(rt_driver_t drv, rt_device_t dev) return match; } +/** + * @brief Bus probe callback: initialize a matched device + * + * Assigns IRQ, enables wake, and calls the driver's probe function. + * + * @param[in] dev Device to probe + * @return RT_EOK on success, driver probe error code otherwise + */ static rt_err_t pci_probe(rt_device_t dev) { rt_err_t err = RT_EOK; @@ -958,6 +1273,15 @@ static rt_err_t pci_probe(rt_device_t dev) return err; } +/** + * @brief Bus remove callback: remove a device + * + * Calls the driver's remove method, disables wake, and + * removes the device from the bus. + * + * @param[in] dev Device to remove + * @return RT_EOK on success + */ static rt_err_t pci_remove(rt_device_t dev) { rt_err_t err = RT_EOK; @@ -983,6 +1307,15 @@ static rt_err_t pci_remove(rt_device_t dev) return err; } +/** + * @brief Bus shutdown callback: shutdown a device + * + * Calls the driver's shutdown method, disables wake, and + * removes the device. + * + * @param[in] dev Device to shutdown + * @return RT_EOK + */ static rt_err_t pci_shutdown(rt_device_t dev) { struct rt_pci_bus *bus; @@ -1004,8 +1337,8 @@ static rt_err_t pci_shutdown(rt_device_t dev) return RT_EOK; } -static struct rt_bus pci_bus = -{ +/** @brief PCI bus type descriptor */ +static struct rt_bus pci_bus = { .name = "pci", .match = pci_match, .probe = pci_probe, @@ -1013,6 +1346,14 @@ static struct rt_bus pci_bus = .shutdown = pci_shutdown, }; +/** + * @brief Initialize the PCI bus subsystem + * + * Registers the PCI bus type with the driver framework. + * Called automatically at boot via INIT_CORE_EXPORT. + * + * @return 0 on success + */ static int pci_bus_init(void) { rt_bus_register(&pci_bus); diff --git a/components/drivers/pci/pme.c b/components/drivers/pci/pme.c index 8deb25dc5e9..70717b574cf 100644 --- a/components/drivers/pci/pme.c +++ b/components/drivers/pci/pme.c @@ -8,6 +8,29 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file pme.c + * @brief PCI Power Management Event (PME) support + * + * Implements PCI Power Management capability handling: + * - PME capability detection and initialization + * - Device wake-up enable/disable (PME# signal control) + * - Power state transition tracking + * + * The PM Capability Register layout is documented in the PCI Power + * Management Specification: + * bits 31:27 - PME Support (which power states support PME#) + * bits 26 - D2 Support + * bits 25 - D1 Support + * bits 24:22 - Aux Current + * bit 21 - Device Specific Initialization + * bit 20 - Immediate Readiness on Return to D0 + * bit 19 - PME Clock + * bits 18:16 - Version + * bits 15:8 - Next Capability Pointer + * bits 7:0 - Capability ID (always 0x01 for PM) + */ + #include #include @@ -15,26 +38,15 @@ #define DBG_LVL DBG_INFO #include -/* - * Power Management Capability Register: +/** + * @brief Initialize PME (Power Management Event) for a PCI device + * + * Finds the PCI Power Management capability (PCIY_PMG) and reads the + * PM Capabilities register to determine which power states support PME#. + * If PME is supported, it disables PME# initially (safe default state). * - * 31 27 26 25 24 22 21 20 19 18 16 15 8 7 0 - * +---------+---+---+--------+---+---+---+------+-----------+----------------+ - * | | | | | | | | | | Capabilitiy ID | - * +---------+---+---+--------+---+---+---+------+-----------+----------------+ - * ^ ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | | | - * | | | | | | | | +---- Next Capabilitiy Pointer - * | | | | | | | +------------- Version - * | | | | | | +------------------- PME Clock - * | | | | | +----------------------- Immediate Readiness on Return to D0 - * | | | | +--------------------------- Device Specifiic Initializtion - * | | | +--------------------------------- Aux Current - * | | +---------------------------------------- D1 Support - * | +-------------------------------------------- D2 Support - * +--------------------------------------------------- PME Support + * @param[in] pdev PCI device */ - void rt_pci_pme_init(struct rt_pci_device *pdev) { rt_uint16_t pmc; @@ -49,7 +61,7 @@ void rt_pci_pme_init(struct rt_pci_device *pdev) if ((pmc & PCIM_PCAP_SPEC) > 3) { LOG_E("%s: Unsupported PME CAP regs spec %u", - rt_dm_dev_get_name(&pdev->parent), pmc & PCIM_PCAP_SPEC); + rt_dm_dev_get_name(&pdev->parent), pmc & PCIM_PCAP_SPEC); return; } @@ -64,8 +76,19 @@ void rt_pci_pme_init(struct rt_pci_device *pdev) } } +/** + * @brief Enable or disable device wake-up from a given power state + * + * If the device supports PME in the requested state (or D3cold as + * fallback), enables the PME# signal. Otherwise disables it. + * + * @param[in] pdev PCI device + * @param[in] state Power state to check PME capability for (RT_PCI_D0 through RT_PCI_D3COLD) + * @param[in] enable RT_TRUE to enable wake, RT_FALSE to disable + * @return RT_EOK on success, -RT_EINVAL on invalid parameters + */ rt_err_t rt_pci_enable_wake(struct rt_pci_device *pdev, - enum rt_pci_power state, rt_bool_t enable) + enum rt_pci_power state, rt_bool_t enable) { if (!pdev || state >= RT_PCI_PME_MAX) { @@ -88,6 +111,16 @@ rt_err_t rt_pci_enable_wake(struct rt_pci_device *pdev, return RT_EOK; } +/** + * @brief Internal: Activate or deactivate the PME# signal + * + * Modifies the PME_Status and PME_Enable bits in the Power Management + * Control/Status register (PMCSR). Writing 1 to PME_Status clears it + * (per PCI spec). + * + * @param[in] pdev PCI device + * @param[in] enable RT_TRUE to enable PME#, RT_FALSE to disable + */ static void pci_pme_active(struct rt_pci_device *pdev, rt_bool_t enable) { rt_uint16_t pmcsr; @@ -110,6 +143,15 @@ static void pci_pme_active(struct rt_pci_device *pdev, rt_bool_t enable) pdev->pm_enabled = enable; } +/** + * @brief Public API: Activate or deactivate PME# and manage power domain + * + * In addition to the PMCSR manipulation, also attaches/detaches + * the device's power domain for runtime PM. + * + * @param[in] pdev PCI device + * @param[in] enable RT_TRUE to enable PME#, RT_FALSE to disable + */ void rt_pci_pme_active(struct rt_pci_device *pdev, rt_bool_t enable) { if (!pdev) diff --git a/components/drivers/pci/probe.c b/components/drivers/pci/probe.c index 77cbb4185f7..ad85abe61a9 100644 --- a/components/drivers/pci/probe.c +++ b/components/drivers/pci/probe.c @@ -8,6 +8,23 @@ * 2022-10-24 GuEe-GUI first version */ +/** + * @file probe.c + * @brief PCI bus scanning and device probing + * + * Implements the PCI bus enumeration process: + * - Host bridge allocation and registration + * - Bus scanning (depth-first tree walk) + * - Device probing (vendor/device ID readout, resource allocation) + * - Bridge scanning (recursive child bus discovery) + * - Device and bus teardown + * + * The enumeration flow: + * 1. Allocate host bridge → register root bus → scan child buses + * 2. For each slot (devfn): read vendor, probe device, allocate BARs + * 3. For each bridge: create child bus, configure bridge window, recurse + */ + #include #define DBG_TAG "pci.probe" @@ -19,16 +36,24 @@ #include "procfs.h" +/** @brief Convenience wrapper for spinlock acquire */ rt_inline void spin_lock(struct rt_spinlock *spinlock) { rt_hw_spin_lock(&spinlock->lock); } +/** @brief Convenience wrapper for spinlock release */ rt_inline void spin_unlock(struct rt_spinlock *spinlock) { rt_hw_spin_unlock(&spinlock->lock); } +/** + * @brief Allocate a PCI host bridge structure + * + * @param[in] priv_size Size in bytes of private data to reserve after the bridge + * @return New host bridge (zeroed), or RT_NULL on allocation failure + */ struct rt_pci_host_bridge *rt_pci_host_bridge_alloc(rt_size_t priv_size) { struct rt_pci_host_bridge *bridge = rt_calloc(1, sizeof(*bridge) + priv_size); @@ -36,6 +61,12 @@ struct rt_pci_host_bridge *rt_pci_host_bridge_alloc(rt_size_t priv_size) return bridge; } +/** + * @brief Free a PCI host bridge and its associated regions + * + * @param[in] bridge Host bridge to free + * @return RT_EOK on success, -RT_EINVAL if bridge is NULL + */ rt_err_t rt_pci_host_bridge_free(struct rt_pci_host_bridge *bridge) { if (!bridge) @@ -58,6 +89,15 @@ rt_err_t rt_pci_host_bridge_free(struct rt_pci_host_bridge *bridge) return RT_EOK; } +/** + * @brief Initialize a host bridge from device tree data + * + * If the host bridge has an ofw_node (device tree node), parses + * bus-range, domain, and ranges properties from the DT. + * + * @param[in] host_bridge PCI host bridge to initialize + * @return RT_EOK on success, error code otherwise + */ rt_err_t rt_pci_host_bridge_init(struct rt_pci_host_bridge *host_bridge) { rt_err_t err = RT_EOK; @@ -70,6 +110,16 @@ rt_err_t rt_pci_host_bridge_init(struct rt_pci_host_bridge *host_bridge) return err; } +/** + * @brief Allocate and initialize a new PCI device on a bus + * + * Initializes the device's linked list node, associates it with the bus, + * sets default subsystem IDs to PCI_ANY_ID (wildcard), marks all resources + * as unused, and initializes MSI descriptor list and lock. + * + * @param[in] bus Parent PCI bus (may be NULL for host bridge devices) + * @return New PCI device (zeroed), or RT_NULL on failure + */ struct rt_pci_device *rt_pci_alloc_device(struct rt_pci_bus *bus) { struct rt_pci_device *pdev = rt_calloc(1, sizeof(*pdev)); @@ -107,6 +157,17 @@ struct rt_pci_device *rt_pci_alloc_device(struct rt_pci_bus *bus) return pdev; } +/** + * @brief Scan and probe a single PCI device at a given devfn + * + * Reads vendor and device IDs from config space. If valid, + * allocates a pci_device structure, reads basic configuration, + * sets up BAR resources, and registers the device on the PCI bus. + * + * @param[in] bus PCI bus to scan on + * @param[in] devfn Device/function number to probe + * @return Pointer to the new PCI device, or RT_NULL if no device present + */ struct rt_pci_device *rt_pci_scan_single_device(struct rt_pci_bus *bus, rt_uint32_t devfn) { rt_err_t err; @@ -137,8 +198,8 @@ struct rt_pci_device *rt_pci_scan_single_device(struct rt_pci_bus *bus, rt_uint3 pdev->device = device; rt_dm_dev_set_name(&pdev->parent, "%04x:%02x:%02x.%u", - rt_pci_domain(pdev), pdev->bus->number, - RT_PCI_SLOT(pdev->devfn), RT_PCI_FUNC(pdev->devfn)); + rt_pci_domain(pdev), pdev->bus->number, + RT_PCI_SLOT(pdev->devfn), RT_PCI_FUNC(pdev->devfn)); if (rt_pci_setup_device(pdev)) { @@ -155,6 +216,16 @@ struct rt_pci_device *rt_pci_scan_single_device(struct rt_pci_bus *bus, rt_uint3 return pdev; } +/** + * @brief Detect whether INTx masking is broken on this device + * + * Writes a toggled INTx disable bit and reads back to verify. + * If the read value doesn't match, the device doesn't support + * INTx masking (common on some older PCI devices). + * + * @param[in] pdev PCI device + * @return RT_TRUE if INTx masking is broken + */ static rt_bool_t pci_intx_mask_broken(struct rt_pci_device *pdev) { rt_bool_t res = RT_FALSE; @@ -175,6 +246,11 @@ static rt_bool_t pci_intx_mask_broken(struct rt_pci_device *pdev) return res; } +/** + * @brief Read Interrupt Pin and Interrupt Line from config space + * + * @param[in] pdev PCI device (updated in place) + */ static void pci_read_irq(struct rt_pci_device *pdev) { rt_uint8_t irq = 0; @@ -189,6 +265,11 @@ static void pci_read_irq(struct rt_pci_device *pdev) pdev->irq = irq; } +/** + * @brief Detect and record PCIe port type from the PCI Express capability + * + * @param[in] pdev PCI device (pcie_cap field updated in place) + */ static void pcie_set_port_type(struct rt_pci_device *pdev) { int pos; @@ -201,6 +282,15 @@ static void pcie_set_port_type(struct rt_pci_device *pdev) pdev->pcie_cap = pos; } +/** + * @brief Configure ARI (Alternative Routing-ID Interpretation) on a PCIe device + * + * ARI extends the function number field from 3 to 8 bits, allowing + * up to 256 functions per device (instead of 8). This must be + * enabled on both the downstream port (bridge) and the device. + * + * @param[in] pdev PCI device (function 0 of a multi-function device) + */ static void pci_configure_ari(struct rt_pci_device *pdev) { rt_uint32_t cap, ctl2_ari; @@ -240,6 +330,15 @@ static void pci_configure_ari(struct rt_pci_device *pdev) rt_pci_write_config_u32(bridge, bridge->pcie_cap + PCIER_DEVICE_CTL2, ctl2_ari); } +/** + * @brief Determine the extended configuration space size + * + * Tries reading beyond the standard 256-byte config space (at offset 256). + * If accessible, the device has PCIe extended config space (4KB). + * + * @param[in] pdev PCI device + * @return PCIE_REGMAX+1 (4096) for extended space, or PCI_REGMAX+1 (256) for standard + */ static rt_uint16_t pci_cfg_space_size_ext(struct rt_pci_device *pdev) { rt_uint32_t status; @@ -252,6 +351,15 @@ static rt_uint16_t pci_cfg_space_size_ext(struct rt_pci_device *pdev) return PCIE_REGMAX + 1; } +/** + * @brief Determine the configuration space size for a device + * + * Host bridges, PCIe devices, and PCI-X 266/533-capable devices + * have extended (4KB) config space. Others have standard 256 bytes. + * + * @param[in] pdev PCI device + * @return Total config space size in bytes + */ static rt_uint16_t pci_cfg_space_size(struct rt_pci_device *pdev) { int pos; @@ -283,6 +391,14 @@ static rt_uint16_t pci_cfg_space_size(struct rt_pci_device *pdev) return PCI_REGMAX + 1; } +/** + * @brief Initialize all capabilities for a newly probed device + * + * Sets up PME, MSI/MSI-X (disables them initially), PCIe port type, + * config space size, and ARI configuration. + * + * @param[in] pdev PCI device + */ static void pci_init_capabilities(struct rt_pci_device *pdev) { rt_pci_pme_init(pdev); @@ -301,6 +417,16 @@ static void pci_init_capabilities(struct rt_pci_device *pdev) pdev->msix_enabled = RT_FALSE; } +/** + * @brief Complete PCI device setup after initial probe + * + * Reads revision ID, class code, header type. Clears error status. + * Detects multi-function devices. Checks INTx masking capability. + * Allocates BAR resources. Initializes capabilities. + * + * @param[in] pdev PCI device (populated in place) + * @return RT_EOK on success, -RT_EIO for unsupported header types + */ rt_err_t rt_pci_setup_device(struct rt_pci_device *pdev) { rt_uint8_t pos; @@ -340,7 +466,7 @@ rt_err_t rt_pci_setup_device(struct rt_pci_device *pdev) } rt_dm_dev_set_name(&pdev->parent, "%04x:%02x:%02x.%u", rt_pci_domain(pdev), - pdev->bus->number, RT_PCI_SLOT(pdev->devfn), RT_PCI_FUNC(pdev->devfn)); + pdev->bus->number, RT_PCI_SLOT(pdev->devfn), RT_PCI_FUNC(pdev->devfn)); class = pdev->class >> 8; @@ -400,15 +526,28 @@ rt_err_t rt_pci_setup_device(struct rt_pci_device *pdev) static struct rt_pci_bus *pci_alloc_bus(struct rt_pci_bus *parent); +/** + * @brief Initialize a child PCI bus (for bridge devices) + * + * Sets up the bus number, attaches the bridge device as bus->self, + * inherits sysdata and ops from the parent, and calls the bus's + * add() operation if present. + * + * @param[in] bus Child bus to initialize + * @param[in] bus_no Bus number to assign + * @param[in] host_bridge Host bridge for this hierarchy + * @param[in] pdev Bridge device (bus->self) + * @return RT_EOK on success + */ static rt_err_t pci_child_bus_init(struct rt_pci_bus *bus, rt_uint32_t bus_no, - struct rt_pci_host_bridge *host_bridge, struct rt_pci_device *pdev) + struct rt_pci_host_bridge *host_bridge, struct rt_pci_device *pdev) { rt_err_t err; struct rt_pci_bus *parent_bus = bus->parent; bus->sysdata = parent_bus->sysdata; bus->self = pdev; - bus->ops = host_bridge->child_ops ? : parent_bus->ops; + bus->ops = host_bridge->child_ops ?: parent_bus->ops; bus->number = bus_no; rt_sprintf(bus->name, "%04x:%02x", host_bridge->domain, bus_no); @@ -422,7 +561,7 @@ static rt_err_t pci_child_bus_init(struct rt_pci_bus *bus, rt_uint32_t bus_no, rt_pci_ofw_bus_free(bus); LOG_E("PCI-Bus<%s> add bus failed with err = %s", - bus->name, rt_strerror(err)); + bus->name, rt_strerror(err)); return err; } @@ -431,8 +570,16 @@ static rt_err_t pci_child_bus_init(struct rt_pci_bus *bus, rt_uint32_t bus_no, return RT_EOK; } +/** + * @brief Check if the Enhanced Allocation capability specifies fixed bus numbers + * + * @param[in] pdev Bridge device + * @param[out] sec Secondary bus number from EA + * @param[out] sub Subordinate bus number from EA + * @return RT_TRUE if EA provides valid fixed bus numbers + */ static rt_bool_t pci_ea_fixed_busnrs(struct rt_pci_device *pdev, - rt_uint8_t *sec, rt_uint8_t *sub) + rt_uint8_t *sec, rt_uint8_t *sub) { int pos, offset; rt_uint32_t dw; @@ -448,7 +595,7 @@ static rt_bool_t pci_ea_fixed_busnrs(struct rt_pci_device *pdev, rt_pci_read_config_u32(pdev, offset, &dw); ea_sec = PCIM_EA_SEC_NR(dw); ea_sub = PCIM_EA_SUB_NR(dw); - if (ea_sec == 0 || ea_sub < ea_sec) + if (ea_sec == 0 || ea_sub < ea_sec) { return RT_FALSE; } @@ -459,6 +606,14 @@ static rt_bool_t pci_ea_fixed_busnrs(struct rt_pci_device *pdev, return RT_TRUE; } +/** + * @brief Fix up PCIe link speed after enumeration + * + * For PCIe root ports, downstream ports, and bridges: + * retrain the link to negotiate the optimal speed. + * + * @param[in] pdev PCIe bridge device + */ static void pcie_fixup_link(struct rt_pci_device *pdev) { int pos = pdev->pcie_cap; @@ -481,9 +636,9 @@ static void pcie_fixup_link(struct rt_pci_device *pdev) rt_pci_read_config_u16(pdev, pos + PCIER_LINK_CTL2, &exp_lnkctl2); rt_pci_write_config_u16(pdev, pos + PCIER_LINK_CTL2, - (exp_lnkctl2 & ~PCIEM_LNKCTL2_TLS) | PCIEM_LNKCTL2_TLS_2_5GT); + (exp_lnkctl2 & ~PCIEM_LNKCTL2_TLS) | PCIEM_LNKCTL2_TLS_2_5GT); rt_pci_write_config_u16(pdev, pos + PCIER_LINK_CTL, - exp_lnkctl | PCIEM_LINK_CTL_RETRAIN_LINK); + exp_lnkctl | PCIEM_LINK_CTL_RETRAIN_LINK); for (int i = 0; i < 20; ++i) { @@ -500,15 +655,29 @@ static void pcie_fixup_link(struct rt_pci_device *pdev) /* Fail, restore */ rt_pci_write_config_u16(pdev, pos + PCIER_LINK_CTL2, exp_lnkctl2); rt_pci_write_config_u16(pdev, pos + PCIER_LINK_CTL, - exp_lnkctl | PCIEM_LINK_CTL_RETRAIN_LINK); + exp_lnkctl | PCIEM_LINK_CTL_RETRAIN_LINK); _status_sync: /* Wait a while for success or failure */ rt_thread_mdelay(100); } +/** + * @brief Scan behind a PCI bridge, extending the bus topology + * + * Creates a child bus, configures the bridge's primary/secondary/subordinate + * bus numbers, scans child buses recursively, and writes back the final + * subordinate bus number. + * + * @param[in] bus Parent bus + * @param[in] pdev Bridge PCI device + * @param[in] bus_no_start Starting bus number for allocation + * @param[in] buses Maximum number of buses to allocate (0 = unlimited) + * @param[in] reconfigured Whether this is a reconfiguration + * @return Final bus number allocated + */ static rt_uint32_t pci_scan_bridge_extend(struct rt_pci_bus *bus, struct rt_pci_device *pdev, - rt_uint32_t bus_no_start, rt_uint32_t buses, rt_bool_t reconfigured) + rt_uint32_t bus_no_start, rt_uint32_t buses, rt_bool_t reconfigured) { rt_bool_t fixed_buses; rt_uint8_t fixed_sub, fixed_sec; @@ -538,7 +707,7 @@ static rt_uint32_t pci_scan_bridge_extend(struct rt_pci_bus *bus, struct rt_pci_ } LOG_I("Bridge configuration: primary(%02x) secondary(%02x) subordinate(%02x)", - primary, secondary, subordinate); + primary, secondary, subordinate); } if (pdev->pcie_cap) @@ -597,8 +766,17 @@ static rt_uint32_t pci_scan_bridge_extend(struct rt_pci_bus *bus, struct rt_pci_ return bus_no; } +/** + * @brief Scan behind a PCI bridge (convenience wrapper) + * + * @param[in] bus Parent bus + * @param[in] pdev Bridge device + * @param[in] bus_no_start Starting bus number + * @param[in] reconfigured Whether reconfiguring + * @return Final bus number + */ rt_uint32_t rt_pci_scan_bridge(struct rt_pci_bus *bus, struct rt_pci_device *pdev, - rt_uint32_t bus_no_start, rt_bool_t reconfigured) + rt_uint32_t bus_no_start, rt_bool_t reconfigured) { if (!bus || !pdev) { @@ -608,6 +786,16 @@ rt_uint32_t rt_pci_scan_bridge(struct rt_pci_bus *bus, struct rt_pci_device *pde return pci_scan_bridge_extend(bus, pdev, bus_no_start, 0, reconfigured); } +/** + * @brief Check if a PCI bus has only one child (for enumeration optimization) + * + * PCIe root ports, downstream ports, and PCIe bridges typically have + * only one device on their secondary bus. This is used to avoid + * scanning beyond function 0 on such buses. + * + * @param[in] bus PCI bus (must be non-root) + * @return RT_TRUE if the bus is likely a point-to-point link + */ rt_inline rt_bool_t only_one_child(struct rt_pci_bus *bus) { struct rt_pci_device *pdev; @@ -634,6 +822,17 @@ rt_inline rt_bool_t only_one_child(struct rt_pci_bus *bus) return RT_FALSE; } +/** + * @brief Get the next function number to scan + * + * Handles ARI (Alternative Routing-ID Interpretation) where function + * numbers go beyond the standard 0-7 range (up to 255). + * + * @param[in] bus PCI bus + * @param[in] pdev Current device (may be NULL for first call) + * @param[in] fn Current function number + * @return Next function number, or negative on error/end + */ static int next_fn(struct rt_pci_bus *bus, struct rt_pci_device *pdev, int fn) { if (!rt_pci_is_root_bus(bus) && bus->self->ari_enabled) @@ -677,6 +876,17 @@ static int next_fn(struct rt_pci_bus *bus, struct rt_pci_device *pdev, int fn) return fn + 1; } +/** + * @brief Scan all functions in a PCI slot + * + * Iterates through function numbers starting from devfn, probing + * each function. Stops when function 0 returns nothing (no device + * in this slot) or when a non-multi-function device is found. + * + * @param[in] bus PCI bus + * @param[in] devfn Starting device/function number + * @return Number of devices found in this slot + */ rt_size_t rt_pci_scan_slot(struct rt_pci_bus *bus, rt_uint32_t devfn) { rt_size_t nr = 0; @@ -714,6 +924,17 @@ rt_size_t rt_pci_scan_slot(struct rt_pci_bus *bus, rt_uint32_t devfn) return nr; } +/** + * @brief Recursively scan child buses of a PCI bus + * + * Scans all slots (devfn 0..255 step 8), then for each bridge found, + * creates a child bus and scans behind it. Bus numbers are allocated + * incrementally. + * + * @param[in] bus Parent PCI bus + * @param[in] buses Maximum number of buses to allocate (0 = unlimited) + * @return Final bus number used + */ rt_uint32_t rt_pci_scan_child_buses(struct rt_pci_bus *bus, rt_size_t buses) { rt_uint32_t bus_no; @@ -729,8 +950,8 @@ rt_uint32_t rt_pci_scan_child_buses(struct rt_pci_bus *bus, rt_size_t buses) bus_no = bus->number; for (rt_uint32_t devfn = 0; - devfn < RT_PCI_DEVFN(RT_PCI_DEVICE_MAX - 1, RT_PCI_FUNCTION_MAX - 1); - devfn += RT_PCI_FUNCTION_MAX) + devfn < RT_PCI_DEVFN(RT_PCI_DEVICE_MAX - 1, RT_PCI_FUNCTION_MAX - 1); + devfn += RT_PCI_FUNCTION_MAX) { rt_pci_scan_slot(bus, devfn); } @@ -756,11 +977,23 @@ rt_uint32_t rt_pci_scan_child_buses(struct rt_pci_bus *bus, rt_size_t buses) return bus_no; } +/** + * @brief Scan all child buses of a PCI bus (unlimited buses) + * + * @param[in] bus PCI bus + * @return Final bus number + */ rt_uint32_t rt_pci_scan_child_bus(struct rt_pci_bus *bus) { return rt_pci_scan_child_buses(bus, 0); } +/** + * @brief Allocate and initialize a new PCI bus structure + * + * @param[in] parent Parent bus (or NULL for root bus) + * @return New bus, or RT_NULL on failure + */ static struct rt_pci_bus *pci_alloc_bus(struct rt_pci_bus *parent) { struct rt_pci_bus *bus = rt_calloc(1, sizeof(*bus)); @@ -781,6 +1014,15 @@ static struct rt_pci_bus *pci_alloc_bus(struct rt_pci_bus *parent) return bus; } +/** + * @brief Register a host bridge's root bus + * + * Creates the root bus, links it to the host bridge, and calls + * the bus's add() operation. + * + * @param[in] host_bridge Host bridge to register + * @return RT_EOK on success + */ rt_err_t rt_pci_host_bridge_register(struct rt_pci_host_bridge *host_bridge) { struct rt_pci_bus *bus = pci_alloc_bus(RT_NULL); @@ -812,6 +1054,15 @@ rt_err_t rt_pci_host_bridge_register(struct rt_pci_host_bridge *host_bridge) return RT_EOK; } +/** + * @brief Register a host bridge and scan its root bus + * + * This is the main entry point for initializing a PCI hierarchy: + * register the root bus, then scan and enumerate all devices. + * + * @param[in] host_bridge Host bridge + * @return RT_EOK on success + */ rt_err_t rt_pci_scan_root_bus_bridge(struct rt_pci_host_bridge *host_bridge) { rt_err_t err; @@ -826,6 +1077,12 @@ rt_err_t rt_pci_scan_root_bus_bridge(struct rt_pci_host_bridge *host_bridge) return err; } +/** + * @brief Probe a host bridge (register + scan) + * + * @param[in] host_bridge Host bridge + * @return RT_EOK on success + */ rt_err_t rt_pci_host_bridge_probe(struct rt_pci_host_bridge *host_bridge) { rt_err_t err; @@ -835,6 +1092,13 @@ rt_err_t rt_pci_host_bridge_probe(struct rt_pci_host_bridge *host_bridge) return err; } +/** + * @brief Enumeration callback: remove a device from its bus + * + * @param[in] pdev PCI device to remove + * @param[in] data Unused + * @return Always RT_FALSE (continue removal for all devices) + */ static rt_bool_t pci_remove_bus_device(struct rt_pci_device *pdev, void *data) { /* Bus will free if this is the last device */ @@ -844,6 +1108,14 @@ static rt_bool_t pci_remove_bus_device(struct rt_pci_device *pdev, void *data) return RT_FALSE; } +/** + * @brief Remove a host bridge and all its devices + * + * Enumerates all devices under the root bus and removes them. + * + * @param[in] host_bridge Host bridge to remove + * @return RT_EOK on success, -RT_EINVAL if bridge is NULL + */ rt_err_t rt_pci_host_bridge_remove(struct rt_pci_host_bridge *host_bridge) { rt_err_t err = RT_EOK; @@ -861,6 +1133,15 @@ rt_err_t rt_pci_host_bridge_remove(struct rt_pci_host_bridge *host_bridge) return err; } +/** + * @brief Remove a PCI bus if it has no children or devices + * + * A bus can only be removed when both its children_nodes and + * devices_nodes lists are empty. + * + * @param[in] bus PCI bus to remove + * @return RT_EOK on success, -RT_EBUSY if bus still has children/devices + */ rt_err_t rt_pci_bus_remove(struct rt_pci_bus *bus) { rt_err_t err = RT_EOK; @@ -898,6 +1179,15 @@ rt_err_t rt_pci_bus_remove(struct rt_pci_bus *bus) return err; } +/** + * @brief Remove a PCI device from its bus + * + * Waits for any remaining references to be released (yielding), + * then removes the device from the bus's device list and frees it. + * + * @param[in] pdev PCI device to remove + * @return RT_EOK on success, -RT_EINVAL if device is NULL + */ rt_err_t rt_pci_device_remove(struct rt_pci_device *pdev) { rt_err_t err = RT_EOK; diff --git a/components/drivers/pci/procfs.c b/components/drivers/pci/procfs.c index b2ce0b057b0..a0a9068dd23 100644 --- a/components/drivers/pci/procfs.c +++ b/components/drivers/pci/procfs.c @@ -8,6 +8,20 @@ * 2024-07-07 GuEe-GUI first version */ +/** + * @file procfs.c + * @brief PCI procfs interface for userspace configuration space access + * + * Provides: + * - /proc/pci/devices: A list of all PCI devices with their BARs and drivers + * - /proc/pci/: Per-device config space read/write (like Linux's sysfs config) + * + * The per-device file allows userspace tools (e.g. lspci equivalent) to + * read and write PCI configuration space directly, with proper endian + * conversion (little-endian to CPU byte order). PM runtime wake is + * managed so that config space accesses work even on suspended devices. + */ + #include #include @@ -23,6 +37,7 @@ static struct rt_bus *pci_bus; static struct proc_dentry *pci_proc_dentry; +/** @brief Copy data to userspace (with LWP support) */ rt_inline void copy_to_user(void *to, void *from, size_t size) { #ifdef RT_USING_LWP @@ -33,6 +48,7 @@ rt_inline void copy_to_user(void *to, void *from, size_t size) } } +/** @brief Copy data from userspace (with LWP support) */ rt_inline void copy_from_user(void *to, const void *from, size_t size) { #ifdef RT_USING_LWP @@ -43,6 +59,15 @@ rt_inline void copy_from_user(void *to, const void *from, size_t size) } } +/** + * @brief Ensure the device is powered on for config space access + * + * If PM is disabled on the device, temporarily enables it for the + * duration of the access. The original state is saved in out_flags. + * + * @param[in] pdev PCI device + * @param[out] out_flags Original PM enabled state + */ static void pci_pm_runtime_get(struct rt_pci_device *pdev, rt_ubase_t *out_flags) { *out_flags = pdev->pm_enabled; @@ -53,6 +78,14 @@ static void pci_pm_runtime_get(struct rt_pci_device *pdev, rt_ubase_t *out_flags } } +/** + * @brief Restore PM state after config space access + * + * If PM was off before the access, disables it again. + * + * @param[in] pdev PCI device + * @param[in] flags Original PM enabled state + */ static void pci_pm_runtime_put(struct rt_pci_device *pdev, rt_ubase_t *flags) { if (!*flags) @@ -61,6 +94,18 @@ static void pci_pm_runtime_put(struct rt_pci_device *pdev, rt_ubase_t *flags) } } +/** + * @brief Read PCI config space from userspace (/proc/pci/) + * + * Handles arbitrary offsets and sizes with proper alignment. + * Data is converted to little-endian for userspace consumption. + * + * @param[in] file File descriptor + * @param[out] buf User buffer + * @param[in] count Number of bytes to read + * @param[in] ppos File position (offset in config space) + * @return Number of bytes read + */ static ssize_t pci_read(struct dfs_file *file, void *buf, size_t count, off_t *ppos) { off_t pos = *ppos; @@ -142,6 +187,18 @@ static ssize_t pci_read(struct dfs_file *file, void *buf, size_t count, off_t *p return res; } +/** + * @brief Write PCI config space from userspace (/proc/pci/) + * + * Handles arbitrary offsets and sizes with proper alignment. + * Data is converted from little-endian (userspace) to CPU byte order. + * + * @param[in] file File descriptor + * @param[in] buf User buffer + * @param[in] count Number of bytes to write + * @param[in] ppos File position (offset in config space) + * @return Number of bytes written + */ static ssize_t pci_write(struct dfs_file *file, const void *buf, size_t count, off_t *ppos) { off_t pos = *ppos; @@ -217,6 +274,14 @@ static ssize_t pci_write(struct dfs_file *file, const void *buf, size_t count, o return res; } +/** + * @brief Seek within PCI config space (used by pread/pwrite-like operations) + * + * @param[in] file File descriptor + * @param[in] offset Seek offset + * @param[in] wherece SEEK_SET, SEEK_CUR, or SEEK_END (config space size) + * @return New position, or negative error + */ static off_t pci_lseek(struct dfs_file *file, off_t offset, int wherece) { struct proc_dentry *dentry = file->vnode->data; @@ -247,13 +312,18 @@ static off_t pci_lseek(struct dfs_file *file, off_t offset, int wherece) return -EIO; } -static const struct dfs_file_ops pci_fops = -{ +/** @brief File operations for per-device config space access */ +static const struct dfs_file_ops pci_fops = { .read = pci_read, .write = pci_write, .lseek = pci_lseek, }; +/** + * @brief Create /proc/pci/ entry for a PCI device + * + * @param[in] pdev PCI device + */ void pci_procfs_attach(struct rt_pci_device *pdev) { const char *name; @@ -275,6 +345,11 @@ void pci_procfs_attach(struct rt_pci_device *pdev) proc_release(dentry); } +/** + * @brief Remove /proc/pci/ entry for a PCI device + * + * @param[in] pdev PCI device + */ void pci_procfs_detach(struct rt_pci_device *pdev) { if (!pci_proc_dentry) @@ -285,6 +360,16 @@ void pci_procfs_detach(struct rt_pci_device *pdev) proc_remove_dentry(rt_dm_dev_get_name(&pdev->parent), pci_proc_dentry); } +/** + * @brief Show all PCI devices in /proc/pci/devices + * + * Output format for each device: + * BBDF\tVVVVDDDD\tIRQ\tBAR0_base...\tROM_base\tBAR0_size...\tROM_size\tdriver + * + * @param[in] seq Seq-file output buffer + * @param[in] data Unused + * @return 0 on success + */ static int pci_single_show(struct dfs_seq_file *seq, void *data) { struct rt_device *dev; @@ -298,11 +383,11 @@ static int pci_single_show(struct dfs_seq_file *seq, void *data) pdev = rt_container_of(dev, struct rt_pci_device, parent); dfs_seq_printf(seq, "%02x%02x\t%04x%04x\t%x", - pdev->bus->number, - pdev->devfn, - pdev->vendor, - pdev->device, - pdev->irq); + pdev->bus->number, + pdev->devfn, + pdev->vendor, + pdev->device, + pdev->irq); /* BAR, ROM base */ for (int bar = 0; bar < RT_PCI_BAR_NR_MAX; ++bar) @@ -337,6 +422,13 @@ static int pci_single_show(struct dfs_seq_file *seq, void *data) return 0; } +/** + * @brief Initialize PCI procfs: create /proc/pci and /proc/pci/devices + * + * Called at the INIT_PREV_EXPORT level (before most other init calls). + * + * @return 0 on success, negative on error + */ static int pci_procfs_init(void) { struct proc_dentry *dentry; diff --git a/components/drivers/pci/procfs.h b/components/drivers/pci/procfs.h index 8059538a04e..73a912e7024 100644 --- a/components/drivers/pci/procfs.h +++ b/components/drivers/pci/procfs.h @@ -8,6 +8,16 @@ * 2024-07-07 GuEe-GUI first version */ +/** + * @file procfs.h + * @brief PCI procfs interface declarations + * + * Provides /proc/pci/ per-device configuration space files + * and /proc/pci/devices enumeration for userspace access. + * When RT_USING_DFS_PROCFS is disabled, the attach/detach + * functions are no-ops. + */ + #ifndef __PCI_PROCFS_H__ #define __PCI_PROCFS_H__ @@ -16,13 +26,34 @@ #ifdef RT_USING_DFS_PROCFS #include +/** + * @brief Create /proc/pci/ entry for a PCI device + * + * @param[in] pdev PCI device to attach + */ void pci_procfs_attach(struct rt_pci_device *pdev); + +/** + * @brief Remove /proc/pci/ entry for a PCI device + * + * @param[in] pdev PCI device to detach + */ void pci_procfs_detach(struct rt_pci_device *pdev); #else +/** + * @brief No-op: create /proc/pci/ entry (procfs disabled) + * + * @param[in] pdev PCI device + */ rt_inline void pci_procfs_attach(struct rt_pci_device *pdev) { } +/** + * @brief No-op: remove /proc/pci/ entry (procfs disabled) + * + * @param[in] pdev PCI device + */ rt_inline void pci_procfs_detach(struct rt_pci_device *pdev) { }