Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
740 changes: 546 additions & 194 deletions components/drivers/include/drivers/pci.h

Large diffs are not rendered by default.

404 changes: 321 additions & 83 deletions components/drivers/include/drivers/pci_endpoint.h

Large diffs are not rendered by default.

112 changes: 81 additions & 31 deletions components/drivers/include/drivers/pci_msi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__

Expand Down Expand Up @@ -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 */
};

/*
Expand Down Expand Up @@ -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__ */
160 changes: 130 additions & 30 deletions components/drivers/pci/access.c
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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 <rthw.h>
#include <rtthread.h>

#include <drivers/pci.h>

/** @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) \
Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand Down
Loading