#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pci_msi_machdep.c,v 1.10 2024/06/30 09:38:07 jmcneill Exp $");
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <arm/pic/picvar.h>
#include <arm/pci/pci_msi_machdep.h>
static SIMPLEQ_HEAD(, arm_pci_msi) arm_pci_msi_list =
SIMPLEQ_HEAD_INITIALIZER(arm_pci_msi_list);
static struct arm_pci_msi *
arm_pci_msi_find_frame(pci_intr_handle_t ih)
{
struct arm_pci_msi *msip;
const int id = __SHIFTOUT(ih, ARM_PCI_INTR_FRAME);
SIMPLEQ_FOREACH(msip, &arm_pci_msi_list, msi_link)
if (id == msip->msi_id)
return msip;
return NULL;
}
static struct arm_pci_msi *
arm_pci_msi_lookup(const struct pci_attach_args *pa)
{
struct arm_pci_msi *msip;
uint32_t rid, frameid;
int b, d, f;
pci_decompose_tag(pa->pa_pc, pa->pa_tag, &b, &d, &f);
rid = (b << 8) | (d << 3) | f;
frameid = pci_get_frameid(pa->pa_pc, rid);
SIMPLEQ_FOREACH(msip, &arm_pci_msi_list, msi_link)
if (frameid == msip->msi_id)
return msip;
return NULL;
}
static int
arm_pci_msi_alloc_common(pci_intr_handle_t **ihps, int *count, const struct pci_attach_args *pa, bool exact)
{
pci_intr_handle_t *vectors;
struct arm_pci_msi *msi;
if ((pa->pa_flags & PCI_FLAGS_MSI_OKAY) == 0)
return ENODEV;
msi = arm_pci_msi_lookup(pa);
if (msi == NULL || msi->msi_alloc == NULL)
return EINVAL;
vectors = msi->msi_alloc(msi, count, pa, exact);
if (vectors == NULL)
return ENOMEM;
*ihps = vectors;
return 0;
}
static int
arm_pci_msix_alloc_common(pci_intr_handle_t **ihps, u_int *table_indexes, int *count, const struct pci_attach_args *pa, bool exact)
{
pci_intr_handle_t *vectors;
struct arm_pci_msi *msi;
if ((pa->pa_flags & PCI_FLAGS_MSIX_OKAY) == 0)
return ENODEV;
msi = arm_pci_msi_lookup(pa);
if (msi == NULL || msi->msix_alloc == NULL)
return EINVAL;
vectors = msi->msix_alloc(msi, table_indexes, count, pa, exact);
if (vectors == NULL)
return ENOMEM;
*ihps = vectors;
return 0;
}
int
arm_pci_msi_add(struct arm_pci_msi *msi)
{
SIMPLEQ_INSERT_TAIL(&arm_pci_msi_list, msi, msi_link);
return 0;
}
void *
arm_pci_msi_intr_establish(pci_chipset_tag_t pc, pci_intr_handle_t pih,
int ipl, int (*func)(void *), void *arg, const char *xname)
{
struct arm_pci_msi *msi;
msi = arm_pci_msi_find_frame(pih);
if (msi == NULL)
return NULL;
return msi->msi_intr_establish(msi, pih, ipl, func, arg, xname);
}
int
pci_msi_alloc(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int *count)
{
return arm_pci_msi_alloc_common(ihps, count, pa, false);
}
int
pci_msi_alloc_exact(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int count)
{
return arm_pci_msi_alloc_common(ihps, &count, pa, true);
}
int
pci_msix_alloc(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int *count)
{
return arm_pci_msix_alloc_common(ihps, NULL, count, pa, false);
}
int
pci_msix_alloc_exact(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int count)
{
return arm_pci_msix_alloc_common(ihps, NULL, &count, pa, true);
}
int
pci_msix_alloc_map(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, u_int *table_indexes, int count)
{
return arm_pci_msix_alloc_common(ihps, table_indexes, &count, pa, true);
}
int
pci_intx_alloc(const struct pci_attach_args *pa, pci_intr_handle_t **ihp)
{
pci_intr_handle_t *pih;
if (ihp == NULL)
return EINVAL;
pih = kmem_alloc(sizeof(*pih), KM_SLEEP);
if (pci_intr_map(pa, pih) != 0) {
kmem_free(pih, sizeof(*pih));
return EINVAL;
}
*ihp = pih;
return 0;
}
int
pci_intr_alloc(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int *counts, pci_intr_type_t max_type)
{
int intx_count, msi_count, msix_count, error;
error = EINVAL;
if (counts != NULL) {
intx_count = msi_count = msix_count = 0;
switch (max_type) {
case PCI_INTR_TYPE_MSIX:
msix_count = counts[PCI_INTR_TYPE_MSIX];
case PCI_INTR_TYPE_MSI:
msi_count = counts[PCI_INTR_TYPE_MSI];
case PCI_INTR_TYPE_INTX:
intx_count = counts[PCI_INTR_TYPE_INTX];
if (intx_count > 1)
return EINVAL;
break;
default:
return EINVAL;
}
memset(counts, 0, sizeof(*counts) * PCI_INTR_TYPE_SIZE);
} else {
intx_count = msi_count = msix_count = 1;
}
if (msix_count == -1)
msix_count = pci_msix_count(pa->pa_pc, pa->pa_tag);
if (msix_count > 0 && (error = pci_msix_alloc_exact(pa, ihps, msix_count)) == 0) {
if (counts != NULL)
counts[PCI_INTR_TYPE_MSIX] = msix_count;
return 0;
}
if (msi_count == -1)
msi_count = pci_msi_count(pa->pa_pc, pa->pa_tag);
if (msi_count > 0 && (error = pci_msi_alloc_exact(pa, ihps, msi_count)) == 0) {
if (counts != NULL)
counts[PCI_INTR_TYPE_MSI] = msi_count;
return 0;
}
if (intx_count > 0 && (error = pci_intx_alloc(pa, ihps)) == 0) {
if (counts != NULL)
counts[PCI_INTR_TYPE_INTX] = intx_count;
return 0;
}
return error;
}
void
pci_intr_release(pci_chipset_tag_t pc, pci_intr_handle_t *pih, int count)
{
struct arm_pci_msi *msi = NULL;
if (pih == NULL || count == 0)
return;
if ((pih[0] & (ARM_PCI_INTR_MSIX|ARM_PCI_INTR_MSI)) != 0) {
msi = arm_pci_msi_find_frame(pih[0]);
KASSERT(msi != NULL);
msi->msi_intr_release(msi, pih, count);
}
kmem_free(pih, sizeof(*pih) * count);
}
pci_intr_type_t
pci_intr_type(pci_chipset_tag_t pc, pci_intr_handle_t ih)
{
if (ih & ARM_PCI_INTR_MSIX)
return PCI_INTR_TYPE_MSIX;
if (ih & ARM_PCI_INTR_MSI)
return PCI_INTR_TYPE_MSI;
return PCI_INTR_TYPE_INTX;
}