297 lines
5.8 KiB
C
297 lines
5.8 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
* Copyright (c) 2020 acontis technologies GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <kernel.h>
|
|
#include <stdbool.h>
|
|
#include <drivers/pcie/pcie.h>
|
|
|
|
#if CONFIG_PCIE_MSI
|
|
#include <drivers/pcie/msi.h>
|
|
#endif
|
|
|
|
/* functions documented in drivers/pcie/pcie.h */
|
|
|
|
bool pcie_probe(pcie_bdf_t bdf, pcie_id_t id)
|
|
{
|
|
uint32_t data;
|
|
|
|
data = pcie_conf_read(bdf, PCIE_CONF_ID);
|
|
|
|
if (data == PCIE_ID_NONE) {
|
|
return false;
|
|
}
|
|
|
|
if (id == PCIE_ID_NONE) {
|
|
return true;
|
|
}
|
|
|
|
return (id == data);
|
|
}
|
|
|
|
void pcie_set_cmd(pcie_bdf_t bdf, uint32_t bits, bool on)
|
|
{
|
|
uint32_t cmdstat;
|
|
|
|
cmdstat = pcie_conf_read(bdf, PCIE_CONF_CMDSTAT);
|
|
|
|
if (on) {
|
|
cmdstat |= bits;
|
|
} else {
|
|
cmdstat &= ~bits;
|
|
}
|
|
|
|
pcie_conf_write(bdf, PCIE_CONF_CMDSTAT, cmdstat);
|
|
}
|
|
|
|
uint32_t pcie_get_cap(pcie_bdf_t bdf, uint32_t cap_id)
|
|
{
|
|
uint32_t reg = 0U;
|
|
uint32_t data;
|
|
|
|
data = pcie_conf_read(bdf, PCIE_CONF_CMDSTAT);
|
|
if (data & PCIE_CONF_CMDSTAT_CAPS) {
|
|
data = pcie_conf_read(bdf, PCIE_CONF_CAPPTR);
|
|
reg = PCIE_CONF_CAPPTR_FIRST(data);
|
|
}
|
|
|
|
while (reg) {
|
|
data = pcie_conf_read(bdf, reg);
|
|
|
|
if (PCIE_CONF_CAP_ID(data) == cap_id) {
|
|
break;
|
|
}
|
|
|
|
reg = PCIE_CONF_CAP_NEXT(data);
|
|
}
|
|
|
|
return reg;
|
|
}
|
|
|
|
uint32_t pcie_get_ext_cap(pcie_bdf_t bdf, uint32_t cap_id)
|
|
{
|
|
unsigned int reg = PCIE_CONF_EXT_CAPPTR; /* Start at end of the PCI configuration space */
|
|
uint32_t data;
|
|
|
|
while (reg) {
|
|
data = pcie_conf_read(bdf, reg);
|
|
if (!data || data == 0xffffffff) {
|
|
return 0;
|
|
}
|
|
|
|
if (PCIE_CONF_EXT_CAP_ID(data) == cap_id) {
|
|
break;
|
|
}
|
|
|
|
reg = PCIE_CONF_EXT_CAP_NEXT(data) >> 2;
|
|
|
|
if (reg < PCIE_CONF_EXT_CAPPTR) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return reg;
|
|
}
|
|
|
|
bool pcie_get_mbar(pcie_bdf_t bdf,
|
|
unsigned int bar_index,
|
|
struct pcie_mbar *mbar)
|
|
{
|
|
uint32_t reg = bar_index + PCIE_CONF_BAR0;
|
|
uintptr_t phys_addr;
|
|
size_t size;
|
|
|
|
if (reg > PCIE_CONF_BAR5) {
|
|
return false;
|
|
}
|
|
|
|
phys_addr = pcie_conf_read(bdf, reg);
|
|
if (PCIE_CONF_BAR_IO(phys_addr)) {
|
|
/* Discard I/O bars */
|
|
return false;
|
|
}
|
|
|
|
if (PCIE_CONF_BAR_INVAL_FLAGS(phys_addr)) {
|
|
/* Discard on invalid flags */
|
|
return false;
|
|
}
|
|
|
|
pcie_conf_write(bdf, reg, 0xFFFFFFFF);
|
|
size = pcie_conf_read(bdf, reg);
|
|
pcie_conf_write(bdf, reg, (uint32_t)phys_addr);
|
|
|
|
if (IS_ENABLED(CONFIG_64BIT) && PCIE_CONF_BAR_64(phys_addr)) {
|
|
reg++;
|
|
phys_addr |= ((uint64_t)pcie_conf_read(bdf, reg)) << 32;
|
|
|
|
if (PCIE_CONF_BAR_ADDR(phys_addr) == PCIE_CONF_BAR_INVAL64 ||
|
|
PCIE_CONF_BAR_ADDR(phys_addr) == PCIE_CONF_BAR_NONE) {
|
|
/* Discard on invalid address */
|
|
return false;
|
|
}
|
|
|
|
pcie_conf_write(bdf, reg, 0xFFFFFFFF);
|
|
size |= ((uint64_t)pcie_conf_read(bdf, reg)) << 32;
|
|
pcie_conf_write(bdf, reg, (uint32_t)((uint64_t)phys_addr >> 32));
|
|
} else if (PCIE_CONF_BAR_ADDR(phys_addr) == PCIE_CONF_BAR_INVAL ||
|
|
PCIE_CONF_BAR_ADDR(phys_addr) == PCIE_CONF_BAR_NONE) {
|
|
/* Discard on invalid address */
|
|
return false;
|
|
}
|
|
|
|
size = PCIE_CONF_BAR_ADDR(size);
|
|
if (size == 0) {
|
|
/* Discard on invalid size */
|
|
return false;
|
|
}
|
|
|
|
mbar->phys_addr = PCIE_CONF_BAR_ADDR(phys_addr);
|
|
mbar->size = size & ~(size-1);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pcie_probe_mbar(pcie_bdf_t bdf,
|
|
unsigned int index,
|
|
struct pcie_mbar *mbar)
|
|
{
|
|
uint32_t reg;
|
|
|
|
for (reg = PCIE_CONF_BAR0;
|
|
index > 0 && reg <= PCIE_CONF_BAR5; reg++, index--) {
|
|
uintptr_t addr = pcie_conf_read(bdf, reg);
|
|
|
|
if (PCIE_CONF_BAR_MEM(addr) && PCIE_CONF_BAR_64(addr)) {
|
|
reg++;
|
|
}
|
|
}
|
|
|
|
if (index != 0) {
|
|
return false;
|
|
}
|
|
|
|
return pcie_get_mbar(bdf, reg - PCIE_CONF_BAR0, mbar);
|
|
}
|
|
|
|
/* 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_get_irq(pcie_bdf_t bdf)
|
|
{
|
|
uint32_t data = pcie_conf_read(bdf, PCIE_CONF_INTR);
|
|
|
|
return PCIE_CONF_INTR_IRQ(data);
|
|
}
|
|
|
|
void pcie_irq_enable(pcie_bdf_t bdf, unsigned int irq)
|
|
{
|
|
#if CONFIG_PCIE_MSI
|
|
if (pcie_msi_enable(bdf, NULL, 1, irq)) {
|
|
return;
|
|
}
|
|
#endif
|
|
irq_enable(irq);
|
|
}
|
|
|
|
pcie_bdf_t pcie_bdf_lookup(pcie_id_t id)
|
|
{
|
|
int bus, dev, func;
|
|
|
|
for (bus = 0; bus <= PCIE_MAX_BUS; bus++) {
|
|
for (dev = 0; dev <= PCIE_MAX_DEV; dev++) {
|
|
for (func = 0; func <= PCIE_MAX_FUNC; func++) {
|
|
pcie_bdf_t bdf = PCIE_BDF(bus, dev, func);
|
|
uint32_t data;
|
|
|
|
data = pcie_conf_read(bdf, PCIE_CONF_ID);
|
|
if (data == PCIE_ID_NONE) {
|
|
continue;
|
|
}
|
|
|
|
if (data == id) {
|
|
return bdf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return PCIE_BDF_NONE;
|
|
}
|