drivers: pcie: Add support for IRQ allocation management

There are x86 platforms where the IRQ configuration register for PCIe
is not pre-populated and the OS needs to assign a number dynamically
by writing to the register.

In order to allocate interrupts we have to know which ones have been
hard-coded in device tree. We accomplish this by collecting these
values through the IRQ_CONNECT() macro and placing them in a dedicated
linker section (in ROM).

The full set of allocated interrupts are managed through a bitmap, and
the pre-allocated values (from the linker section) are inserted into
this upon initial runtime access.

This patch introduces a new pcie_alloc_irq() API that drivers can use
to allocate interrupt line numbers. The two in-tree drivers that were
using this API (I2C and UART) are converted to use the new API.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2020-11-10 16:26:05 +02:00 committed by Johan Hedberg
commit 9e4dfd8f4e
4 changed files with 107 additions and 0 deletions

View file

@ -68,6 +68,83 @@ uintptr_t pcie_get_mbar(pcie_bdf_t bdf, unsigned int index)
return PCIE_CONF_BAR_ADDR(addr); return PCIE_CONF_BAR_ADDR(addr);
} }
/* The first bit is used to indicate whether the list of reserved interrupts
* have been initialized based on content stored in the irq_alloc linker
* section in ROM.
*/
#define IRQ_LIST_INITIALIZED 0
static ATOMIC_DEFINE(irq_reserved, CONFIG_MAX_IRQ_LINES);
static unsigned int irq_alloc(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(irq_reserved); i++) {
unsigned int fz, irq;
while ((fz = find_lsb_set(~atomic_get(&irq_reserved[i])))) {
irq = (fz - 1) + (i * sizeof(atomic_val_t) * 8);
if (irq >= CONFIG_MAX_IRQ_LINES) {
break;
}
if (!atomic_test_and_set_bit(irq_reserved, irq)) {
return irq;
}
}
}
return PCIE_CONF_INTR_IRQ_NONE;
}
static bool irq_is_reserved(unsigned int irq)
{
return atomic_test_bit(irq_reserved, irq);
}
static void irq_init(void)
{
extern uint8_t __irq_alloc_start[];
extern uint8_t __irq_alloc_end[];
const uint8_t *irq;
for (irq = __irq_alloc_start; irq < __irq_alloc_end; irq++) {
__ASSERT_NO_MSG(*irq < CONFIG_MAX_IRQ_LINES);
atomic_set_bit(irq_reserved, *irq);
}
}
unsigned int pcie_alloc_irq(pcie_bdf_t bdf)
{
unsigned int irq;
uint32_t data;
if (!atomic_test_and_set_bit(irq_reserved, IRQ_LIST_INITIALIZED)) {
irq_init();
}
data = pcie_conf_read(bdf, PCIE_CONF_INTR);
irq = PCIE_CONF_INTR_IRQ(data);
if (irq == PCIE_CONF_INTR_IRQ_NONE || irq >= CONFIG_MAX_IRQ_LINES ||
irq_is_reserved(irq)) {
irq = irq_alloc();
if (irq == PCIE_CONF_INTR_IRQ_NONE) {
return irq;
}
data &= ~0xffU;
data |= irq;
pcie_conf_write(bdf, PCIE_CONF_INTR, data);
} else {
atomic_set_bit(irq_reserved, irq);
}
return irq;
}
unsigned int pcie_wired_irq(pcie_bdf_t bdf) unsigned int pcie_wired_irq(pcie_bdf_t bdf)
{ {
uint32_t data = pcie_conf_read(bdf, PCIE_CONF_INTR); uint32_t data = pcie_conf_read(bdf, PCIE_CONF_INTR);

View file

@ -93,11 +93,20 @@ struct x86_ssf {
#endif /* _ASMLANGUAGE */ #endif /* _ASMLANGUAGE */
#ifdef CONFIG_PCIE
#define X86_RESERVE_IRQ(irq_p, name) \
static Z_DECL_ALIGN(uint8_t) name \
__in_section(_irq_alloc, static, name) __used = irq_p
#else
#define X86_RESERVE_IRQ(irq_p, name)
#endif
/* /*
* All Intel64 interrupts are dynamically connected. * All Intel64 interrupts are dynamically connected.
*/ */
#define ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \ #define ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \
X86_RESERVE_IRQ(irq_p, _CONCAT(_irq_alloc_fixed, __COUNTER__)); \
arch_irq_connect_dynamic(irq_p, priority_p, \ arch_irq_connect_dynamic(irq_p, priority_p, \
(void (*)(const void *))isr_p, \ (void (*)(const void *))isr_p, \
isr_param_p, flags_p) isr_param_p, flags_p)

View file

@ -93,6 +93,18 @@ extern uintptr_t pcie_get_mbar(pcie_bdf_t bdf, unsigned int index);
*/ */
extern void pcie_set_cmd(pcie_bdf_t bdf, uint32_t bits, bool on); extern void pcie_set_cmd(pcie_bdf_t bdf, uint32_t bits, bool on);
/**
* @brief Allocate an IRQ for an endpoint.
*
* This function first checks the IRQ register and if it contains a valid
* value this is returned. If the register does not contain a valid value
* allocation of a new one is attempted.
*
* @param bdf the PCI(e) endpoint
* @return the IRQ number, or PCIE_CONF_INTR_IRQ_NONE if allocation failed.
*/
extern unsigned int pcie_alloc_irq(pcie_bdf_t bdf);
/** /**
* @brief Return the IRQ assigned by the firmware/board to an endpoint. * @brief Return the IRQ assigned by the firmware/board to an endpoint.
* *

View file

@ -138,6 +138,15 @@
Z_ITERABLE_SECTION_ROM(dns_sd_rec, 4) Z_ITERABLE_SECTION_ROM(dns_sd_rec, 4)
#endif #endif
#if defined(CONFIG_PCIE)
SECTION_DATA_PROLOGUE(irq_alloc,,)
{
__irq_alloc_start = .;
KEEP(*(SORT_BY_NAME("._irq_alloc*")));
__irq_alloc_end = .;
} GROUP_LINK_IN(ROMABLE_REGION)
#endif /* CONFIG_PCIE */
SECTION_DATA_PROLOGUE(log_const_sections,,) SECTION_DATA_PROLOGUE(log_const_sections,,)
{ {
__log_const_start = .; __log_const_start = .;