#include "opt_acpi.h"
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/cpuset.h>
#include <sys/intr.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/rman.h>
#include <sys/smp.h>
#include <sys/vmem.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/atomic.h>
#include <machine/vmparam.h>
#ifdef FDT
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/ofw/ofw_subr.h>
#endif
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include "gicv5var.h"
#include "gic_v3_var.h"
#include "pic_if.h"
#include "msi_if.h"
#define ITS_IDR0 0x0000
#define ITS_IDR0_PA_RANGE_SHIFT 2
#define ITS_IDR0_PA_RANGE_MASK (0xfu << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_4G (0x0u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_64G (0x1u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_1T (0x2u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_4T (0x3u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_16T (0x4u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_256T (0x5u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_4P (0x6u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR0_PA_RANGE_64P (0x7u << ITS_IDR0_PA_RANGE_SHIFT)
#define ITS_IDR1 0x0004
#define ITS_IDR1_L2SZ_SHIFT 8
#define ITS_IDR1_L2SZ_MASK (0x7u << ITS_IDR1_L2SZ_SHIFT)
#define ITS_IDR1_L2SZ_64K_MASK (0x4u << ITS_IDR1_L2SZ_SHIFT)
#define ITS_IDR1_L2SZ_16K_MASK (0x2u << ITS_IDR1_L2SZ_SHIFT)
#define ITS_IDR1_L2SZ_4K_MASK (0x1u << ITS_IDR1_L2SZ_SHIFT)
#define ITS_IDR1_ITT_LEVELS (0x1u << 7)
#define ITS_IDR1_DT_LEVELS (0x1u << 6)
#define ITS_IDR1_DEVICEID_BITS_SHIFT 0
#define ITS_IDR1_DEVICEID_BITS (0x3fu << ITS_IDR1_DEVICEID_BITS_SHIFT)
#define ITS_IDR1_DEVICEID_BITS_VAL(x) \
(((x) & ITS_IDR1_DEVICEID_BITS) >> ITS_IDR1_DEVICEID_BITS_SHIFT)
#define ITS_IDR2 0x0008
#define ITS_IDR2_XDMN_EVENTS_SHIFT 5
#define ITS_IDR2_XDMN_EVENTS_MASK (0x3u << ITS_IDR2_XDMN_EVENTS_SHIFT)
#define ITS_IDR2_EVENTID_SHIFT 0
#define ITS_IDR2_EVENTID_MASK (0x1fu << ITS_IDR2_EVENTID_SHIFT)
#define ITS_IIDR 0x0040
#define ITS_AIDR 0x0044
#define ITS_CR0 0x0080
#define ITS_CR0_IDLE (0x1u << 1)
#define ITS_CR0_ITSEN (0x1u << 0)
#define ITS_CR1 0x0084
#define ITS_CR1_ITT_RA (0x1u << 7)
#define ITS_CR1_DT_RA (0x1u << 6)
#define ITS_CR1_IC_SHIFT 4
#define ITS_CR1_IC_MASK (0x3u << ITS_CR1_IC_SHIFT)
#define ITS_CR1_IC_NC (0x0u << ITS_CR1_IC_SHIFT)
#define ITS_CR1_IC_WB (0x1u << ITS_CR1_IC_SHIFT)
#define ITS_CR1_IC_WT (0x2u << ITS_CR1_IC_SHIFT)
#define ITS_CR1_OC_SHIFT 2
#define ITS_CR1_OC_MASK (0x3u << ITS_CR1_OC_SHIFT)
#define ITS_CR1_OC_NC (0x0u << ITS_CR1_OC_SHIFT)
#define ITS_CR1_OC_WB (0x1u << ITS_CR1_OC_SHIFT)
#define ITS_CR1_OC_WT (0x2u << ITS_CR1_OC_SHIFT)
#define ITS_CR1_SH_SHIFT 0
#define ITS_CR1_SH_MASK (0x3u << ITS_CR1_SH_SHIFT)
#define ITS_CR1_SH_NS (0x0u << ITS_CR1_SH_SHIFT)
#define ITS_CR1_SH_OS (0x2u << ITS_CR1_SH_SHIFT)
#define ITS_CR1_SH_IS (0x3u << ITS_CR1_SH_SHIFT)
#define ITS_DT_BASER 0x00c0
#define ITS_DT_BASER_ADDR_MASK 0x00fffffffffffff8ul
#define ITS_DT_BASER_ADDR_LIMIT 0x0100000000000000ul
#define ITS_DT_CFGR 0x00d0
#define IRS_DT_CFGR_STRUCTURE_LINEAR (0x0 << 16)
#define IRS_DT_CFGR_STRUCTURE_2LVL (0x1 << 16)
#define ITS_DT_CFGR_L2SZ_SHIFT 6
#define ITS_DT_CFGR_L2SZ_64K_VAL 0x2
#define ITS_DT_CFGR_L2SZ_64K (0x2 << ITS_DT_CFGR_L2SZ_SHIFT)
#define ITS_DT_CFGR_L2SZ_16K_VAL 0x1
#define ITS_DT_CFGR_L2SZ_16K (0x1 << ITS_DT_CFGR_L2SZ_SHIFT)
#define ITS_DT_CFGR_L2SZ_4K_VAL 0x0
#define ITS_DT_CFGR_L2SZ_4K (0x0 << ITS_DT_CFGR_L2SZ_SHIFT)
#define IRS_DT_CFGR_DEVICE_ID_BITS_SHIFT 0
#define ITS_DIDR 0x0100
#define ITS_EIDR 0x0108
#define ITS_INV_EVENTR 0x010c
#define ITS_INV_DEVICER 0x0110
#define ITS_INV_DEVICER_I (0x1u << 31)
#define ITS_INV_DEVICER_EVENTID_BITS_SHIFT 1
#define ITS_INV_DEVICER_EVENTID_BITS_MASK \
(0x1ful << ITS_INV_DEVICER_EVENTID_BITS_SHIFT)
#define ITS_INV_DEVICER_L1 (0x1u << 0)
#define ITS_READ_EVENTR 0x0114
#define ITS_READ_EVENT_DATAR 0x0118
#define ITS_STATUSR 0x0120
#define ITS_STATUSR_IDLE (0x1u << 0)
#define ITS_SYNCR 0x0140
#define ITS_SYNC_STATUSR 0x0148
#define ITS_GEN_EVENT_DIDR 0x0180
#define ITS_GEN_EVENT_EIDR 0x0188
#define ITS_GEN_EVENTR 0x018c
#define ITS_GEN_EVENT_STATUSR 0x0190
#define ITS_MEC_IDR 0x01c0
#define ITS_MEC_MECID_R 0x01c4
#define ITS_MPAM_IDR 0x0200
#define ITS_MPAM_PARTID_R 0x0204
#define ITS_SWERR_STATUSR 0x0240
#define ITS_SWERR_SYNDROMER0 0x0248
#define ITS_SWERR_SYNDROMER1 0x0250
#define ITS_TRANSLATER 0x0000
#define ITS_RL_TRANSLATER 0x0008
#define L1_DTE_SIZE 8
#define L1_DTE_SPAN_SHIFT 60
#define L1_DTE_SPAN_MASK (0xful << L1_DTE_SPAN_SHIFT)
#define L1_DTE_L2_ADDR_MASK 0xfffffffffffff8
#define L1_DTE_VALID (0x1ul << 0)
#define L2_DTE_SIZE 8
#define L2_DTE_EVENTID_BITS_SHIFT 59
#define L2_DTE_EVENTID_BITS_MASK (0x1ful << L2_DTE_EVENTID_BITS_SHIFT)
#define L2_DTE_ITT_STRUCTURE_SHIFT 58
#define L2_DTE_ITT_STRUCTURE_MASK (0x1ul << L2_DTE_ITT_STRUCTURE_SHIFT)
#define L2_DTE_ITT_STRUCTURE_LINEAR (0x0ul << L2_DTE_ITT_STRUCTURE_SHIFT)
#define L2_DTE_ITT_STRUCTURE_2_LEVEL (0x1ul << L2_DTE_ITT_STRUCTURE_SHIFT)
#define L2_DTE_DSWE_SHIFT 57
#define L2_DTE_DSWE (0x1 << L2_DTE_DSWE_SHIFT)
#define L2_DTE_ITT_ADDR_MASK 0x00fffffffffffff8
#define L2_DTE_ITT_L2SZ_SHIFT 1
#define L2_DTE_ITT_L2SZ_MASK (0x3ul << L2_DTE_ITT_L2SZ_SHIFT)
#define L2_DTE_ITT_L2SZ_4K (0x0ul << L2_DTE_ITT_L2SZ_SHIFT)
#define L2_DTE_ITT_L2SZ_16K (0x1ul << L2_DTE_ITT_L2SZ_SHIFT)
#define L2_DTE_ITT_L2SZ_64K (0x2ul << L2_DTE_ITT_L2SZ_SHIFT)
#define L2_DTE_VALID (0x1ul << 0)
#define L2_DTE_LOG2_ENTRIES(l2sz) (9 + (2 * l2sz))
#define L2_DTE_ENTRIES(l2sz) (1ul << L2_DTE_LOG2_ENTRIES(l2sz))
#define ITT_MAX_ADDR 0x00ffffffffffffff
#define L1_ITTE_SIZE 8
#define L1_ITTE_SPAN_SHIFT 60
#define L1_ITTE_SPAN_MASK (0xful << L1_ITTE_SPAN_SHIFT)
#define L1_ITTE_L2_ADDR_MASK 0xfffffffffffff8
#define L1_ITTE_VALID (0x1ul << 0)
#define L2_ITTE_SIZE 8
#define L2_ITTE_VM_ID_SHIFT 32
#define L2_ITTE_VM_ID_MASK (0xfffful << L2_ITT_VM_ID_SHIFT)
#define L2_ITTE_VALID (0x1ul << 31)
#define L2_ITTE_VIRTUAL (0x1ul << 30)
#define L2_ITTE_DAC_SHIFT 28
#define L2_ITTE_DAC_MASK (0x3ul << L2_ITT_DAC_SHIFT)
#define L2_ITTE_LPI_ID_SHIFT 0
#define L2_ITTE_LPI_ID_MASK (0xfffffful << L2_ITT_LPI_ID_SHIFT)
struct lpi_chunk {
u_int lpi_base;
u_int lpi_free;
u_int lpi_num;
u_int lpi_busy;
};
struct its_dev {
TAILQ_ENTRY(its_dev) entry;
device_t pci_dev;
uint32_t devid;
struct lpi_chunk lpis;
uint64_t *itt;
};
struct its_device_list {
struct mtx its_dev_lock;
TAILQ_HEAD(its_dev_list, its_dev) its_dev_list;
uint64_t *its_dev_dte_base;
size_t its_dev_dte_l2size;
u_int its_dev_dte_l2bits;
bool its_dev_dte_2l;
vmem_t *its_dev_irq_alloc;
struct gicv5_its_irqsrc **its_irqs;
};
struct gicv5_its_irqsrc {
struct gicv5_base_irqsrc gi_isrc;
u_int gi_event_id;
struct its_dev *gi_its_dev;
TAILQ_ENTRY(gicv5_its_irqsrc) gi_link;
};
struct gicv5_its_translate_frame {
struct intr_pic *its_pic;
intptr_t its_xref;
bus_addr_t its_frame_paddr;
};
struct gicv5_its_softc {
struct its_device_list its_dl;
struct resource *its_cfg;
struct gicv5_its_translate_frame its_frame;
struct gicv5_its_irqsrc **sc_irqs;
cpuset_t its_cpus;
u_int its_irq_cpu;
TAILQ_HEAD(free_irqs, gicv5_its_irqsrc) sc_free_irqs;
uint8_t its_parange;
bool its_coherent;
};
static uint64_t *gicv5_its_dte_extend(struct gicv5_its_softc *,
struct its_device_list *, uint32_t);
static device_attach_t gicv5_its_attach;
static pic_disable_intr_t gicv5_its_disable_intr;
static pic_enable_intr_t gicv5_its_enable_intr;
static pic_map_intr_t gicv5_its_map_intr;
static pic_setup_intr_t gicv5_its_setup_intr;
static pic_post_filter_t gicv5_its_post_filter;
static pic_post_ithread_t gicv5_its_post_ithread;
static pic_pre_ithread_t gicv5_its_pre_ithread;
static pic_bind_intr_t gicv5_its_bind_intr;
static msi_alloc_msi_t gicv5_its_alloc_msi;
static msi_release_msi_t gicv5_its_release_msi;
static msi_alloc_msix_t gicv5_its_alloc_msix;
static msi_release_msix_t gicv5_its_release_msix;
static msi_map_msi_t gicv5_its_map_msi;
#ifdef IOMMU
static msi_iommu_init_t gicv5_iommu_init;
static msi_iommu_deinit_t gicv5_iommu_deinit;
#endif
static device_method_t gicv5_its_methods[] = {
DEVMETHOD(pic_disable_intr, gicv5_its_disable_intr),
DEVMETHOD(pic_enable_intr, gicv5_its_enable_intr),
DEVMETHOD(pic_map_intr, gicv5_its_map_intr),
DEVMETHOD(pic_setup_intr, gicv5_its_setup_intr),
DEVMETHOD(pic_post_filter, gicv5_its_post_filter),
DEVMETHOD(pic_post_ithread, gicv5_its_post_ithread),
DEVMETHOD(pic_pre_ithread, gicv5_its_pre_ithread),
#ifdef SMP
DEVMETHOD(pic_bind_intr, gicv5_its_bind_intr),
#endif
DEVMETHOD(msi_alloc_msi, gicv5_its_alloc_msi),
DEVMETHOD(msi_release_msi, gicv5_its_release_msi),
DEVMETHOD(msi_alloc_msix, gicv5_its_alloc_msix),
DEVMETHOD(msi_release_msix, gicv5_its_release_msix),
DEVMETHOD(msi_map_msi, gicv5_its_map_msi),
#ifdef IOMMU
DEVMETHOD(msi_iommu_init, gicv5_iommu_init),
DEVMETHOD(msi_iommu_deinit, gicv5_iommu_deinit),
#endif
DEVMETHOD_END
};
static DEFINE_CLASS_0(gic, gicv5_its_driver, gicv5_its_methods,
sizeof(struct gicv5_its_softc));
static void
its_write_cr0(struct gicv5_its_softc *sc, bool en)
{
uint32_t val;
int timeout;
val = en ? ITS_CR0_ITSEN : 0;
bus_write_4(sc->its_cfg, ITS_CR0, val);
timeout = 10000;
do {
val = bus_read_4(sc->its_cfg, ITS_CR0);
if ((val & ITS_CR0_IDLE) == ITS_CR0_IDLE)
return;
DELAY(1);
} while (--timeout > 0);
panic("Timeout waiting for ITS CR0 becoming idle");
}
static void
its_wait_for_statusr(struct gicv5_its_softc *sc)
{
uint32_t val;
int timeout;
timeout = 10000;
do {
val = bus_read_4(sc->its_cfg, ITS_STATUSR);
if ((val & ITS_STATUSR_IDLE) == ITS_STATUSR_IDLE)
return;
DELAY(1);
} while (--timeout > 0);
panic("Timeout waiting for ITS STATUSR becoming idle");
}
static void
its_dcache_wbinv(struct gicv5_its_softc *sc, void *addr, size_t size)
{
if (sc->its_coherent)
dsb(ishst);
else
cpu_dcache_wbinv_range(addr, size);
}
static void
its_device_list_init(struct its_device_list *dev_list)
{
mtx_init(&dev_list->its_dev_lock, "ITS device lock", NULL, MTX_SPIN);
TAILQ_INIT(&dev_list->its_dev_list);
}
static struct its_dev *
its_device_find_locked(struct its_device_list *dev_list, device_t child)
{
struct its_dev *its_dev;
mtx_assert(&dev_list->its_dev_lock, MA_OWNED);
TAILQ_FOREACH(its_dev, &dev_list->its_dev_list, entry) {
if (its_dev->pci_dev == child)
return (its_dev);
}
return (NULL);
}
static struct its_dev *
its_device_find(struct its_device_list *dev_list, device_t child)
{
struct its_dev *its_dev;
mtx_lock_spin(&dev_list->its_dev_lock);
its_dev = its_device_find_locked(dev_list, child);
mtx_unlock_spin(&dev_list->its_dev_lock);
return (its_dev);
}
static uint32_t
its_get_devid(device_t pci_dev)
{
uintptr_t id;
if (pci_get_id(pci_dev, PCI_ID_MSI, &id) != 0)
panic("%s: %s: Unable to get the MSI DeviceID", __func__,
device_get_nameunit(pci_dev));
return (id);
}
static bool
its_device_itt_alloc_linear(struct gicv5_its_softc *sc,
struct its_dev *its_dev, u_int eventid_bits, uint64_t *dtep)
{
size_t size;
size = ((size_t)1 << eventid_bits) * L2_ITTE_SIZE;
MPASS(size <= PAGE_SIZE_4K);
its_dev->itt = contigmalloc(size, M_DEVBUF, M_NOWAIT | M_ZERO, 0,
(1ul << sc->its_parange) - 1, max(size, PAGE_SIZE), 0);
if (its_dev->itt == NULL)
return (false);
its_dcache_wbinv(sc, its_dev->itt, size);
*dtep = (uint64_t)eventid_bits << L2_DTE_EVENTID_BITS_SHIFT |
L2_DTE_ITT_STRUCTURE_LINEAR | vtophys(its_dev->itt) |
L2_DTE_ITT_L2SZ_4K | L2_DTE_VALID;
return (true);
}
static bool
its_device_dte_update(struct gicv5_its_softc *sc,
struct its_device_list *dev_list, struct its_dev *its_dev, uint64_t dte)
{
uint64_t *dtep;
dtep = gicv5_its_dte_extend(sc, dev_list, its_dev->devid);
if (dtep == NULL)
return (false);
MPASS(atomic_load_64(dtep) == 0);
atomic_store_64(dtep, dte);
its_dcache_wbinv(sc, dtep, sizeof(*dtep));
return (true);
}
static struct its_dev *
its_device_get(device_t dev, struct its_device_list *dev_list, device_t child,
u_int nvecs)
{
struct gicv5_its_softc *sc;
struct its_dev *its_dev, *tmp_dev;
vmem_addr_t irq_base;
uint64_t dte;
u_int eventid_bits;
its_dev = its_device_find(dev_list, child);
if (its_dev != NULL)
return (its_dev);
its_dev = malloc(sizeof(*its_dev), M_DEVBUF, M_NOWAIT | M_ZERO);
if (its_dev == NULL)
return (NULL);
its_dev->pci_dev = child;
its_dev->devid = its_get_devid(child);
its_dev->lpis.lpi_busy = 0;
its_dev->lpis.lpi_num = nvecs;
its_dev->lpis.lpi_free = nvecs;
sc = device_get_softc(dev);
if (gicv5_its_dte_extend(sc, dev_list, its_dev->devid) == NULL) {
free(its_dev, M_DEVBUF);
return (NULL);
}
eventid_bits = order_base_2(nvecs);
if (!its_device_itt_alloc_linear(sc, its_dev, eventid_bits, &dte)) {
free(its_dev, M_DEVBUF);
return (NULL);
}
if (vmem_alloc(dev_list->its_dev_irq_alloc, nvecs,
M_FIRSTFIT | M_NOWAIT, &irq_base) != 0) {
free(its_dev->itt, M_DEVBUF);
free(its_dev, M_DEVBUF);
return (NULL);
}
mtx_lock_spin(&dev_list->its_dev_lock);
tmp_dev = its_device_find_locked(dev_list, child);
if (tmp_dev != NULL) {
mtx_unlock_spin(&dev_list->its_dev_lock);
vmem_free(dev_list->its_dev_irq_alloc, its_dev->lpis.lpi_base,
nvecs);
free(its_dev->itt, M_DEVBUF);
free(its_dev, M_DEVBUF);
return (tmp_dev);
}
if (!its_device_dte_update(sc, dev_list, its_dev, dte)) {
mtx_unlock_spin(&dev_list->its_dev_lock);
vmem_free(dev_list->its_dev_irq_alloc, its_dev->lpis.lpi_base,
nvecs);
free(its_dev->itt, M_DEVBUF);
free(its_dev, M_DEVBUF);
return (NULL);
}
its_dev->lpis.lpi_base = irq_base;
TAILQ_INSERT_TAIL(&dev_list->its_dev_list, its_dev, entry);
bus_write_8(sc->its_cfg, ITS_DIDR, its_dev->devid);
bus_write_4(sc->its_cfg, ITS_INV_DEVICER, ITS_INV_DEVICER_I |
(eventid_bits << ITS_INV_DEVICER_EVENTID_BITS_SHIFT));
its_wait_for_statusr(sc);
mtx_unlock_spin(&dev_list->its_dev_lock);
return (its_dev);
}
static int
gicv5_its_intr(void *arg, uintptr_t irq)
{
struct gicv5_its_softc *sc = arg;
struct gicv5_its_irqsrc *gi;
struct trapframe *tf;
gi = sc->sc_irqs[irq];
if (gi == NULL)
panic("%s: Invalid interrupt %ld", __func__, irq);
tf = curthread->td_intr_frame;
intr_isrc_dispatch(&gi->gi_isrc.gbi_isrc, tf);
return (FILTER_HANDLED);
}
static int
gicv5_its_select_cpu(device_t dev, struct intr_irqsrc *isrc)
{
struct gicv5_its_softc *sc;
sc = device_get_softc(dev);
if (CPU_EMPTY(&isrc->isrc_cpu)) {
sc->its_irq_cpu = intr_irq_next_cpu(sc->its_irq_cpu,
&sc->its_cpus);
CPU_SETOF(sc->its_irq_cpu, &isrc->isrc_cpu);
}
return (0);
}
static void
gicv5_its_dte_alloc(struct gicv5_its_softc *sc, size_t size)
{
sc->its_dl.its_dev_dte_base = contigmalloc(size, M_DEVBUF,
M_WAITOK | M_ZERO, 0, (1ul << sc->its_parange) - 1,
size, 0);
its_dcache_wbinv(sc, sc->its_dl.its_dev_dte_base, size);
}
static void
gicv5_its_dte_alloc_linear(struct gicv5_its_softc *sc, uint32_t *cfgrp,
u_int devid_bits)
{
size_t size;
uint32_t cfgr;
u_int n;
n = 2 + devid_bits;
size = 1ul << (n + 1);
gicv5_its_dte_alloc(sc, size);
sc->its_dl.its_dev_dte_2l = false;
cfgr = IRS_DT_CFGR_STRUCTURE_LINEAR;
cfgr |= devid_bits << IRS_DT_CFGR_DEVICE_ID_BITS_SHIFT;
*cfgrp = cfgr;
}
static void
gicv5_its_dte_alloc_2level(struct gicv5_its_softc *sc, uint32_t *cfgrp,
u_int devid_bits, u_int l2sz)
{
size_t size;
uint32_t cfgr;
u_int n;
n = MAX(2, devid_bits - L2_DTE_LOG2_ENTRIES(l2sz) + 2);
size = 1ul << (n + 1);
gicv5_its_dte_alloc(sc, size);
sc->its_dl.its_dev_dte_l2bits = L2_DTE_LOG2_ENTRIES(l2sz);
sc->its_dl.its_dev_dte_l2size = L2_DTE_ENTRIES(l2sz) * L2_DTE_SIZE;
sc->its_dl.its_dev_dte_2l = true;
cfgr = IRS_DT_CFGR_STRUCTURE_2LVL;
cfgr |= l2sz << ITS_DT_CFGR_L2SZ_SHIFT;
cfgr |= devid_bits << IRS_DT_CFGR_DEVICE_ID_BITS_SHIFT;
*cfgrp = cfgr;
}
static uint64_t *
gicv5_its_dte_extend(struct gicv5_its_softc *sc,
struct its_device_list *dev_list, uint32_t devid)
{
uint64_t *l2_dtep;
size_t size;
uint64_t dte, new_dte;
u_int index;
if (!dev_list->its_dev_dte_2l)
return (&dev_list->its_dev_dte_base[devid]);
index = devid >> dev_list->its_dev_dte_l2bits;
size = dev_list->its_dev_dte_l2size;
dte = atomic_load_64(&dev_list->its_dev_dte_base[index]);
if ((dte & L1_DTE_VALID) != 0) {
l2_dtep = (uint64_t *)PHYS_TO_DMAP(dte & L1_DTE_L2_ADDR_MASK);
goto out;
}
l2_dtep = contigmalloc(size, M_DEVBUF, M_NOWAIT | M_ZERO, 0,
(1ul << sc->its_parange) - 1, size, 0);
if (l2_dtep == NULL)
return (NULL);
its_dcache_wbinv(sc, l2_dtep, size);
new_dte = (uint64_t)dev_list->its_dev_dte_l2bits << L1_DTE_SPAN_SHIFT;
new_dte |= vtophys(l2_dtep);
new_dte |= L1_DTE_VALID;
while (!atomic_fcmpset_64(&dev_list->its_dev_dte_base[index], &dte,
new_dte)) {
if ((dte & L1_DTE_VALID) != 0) {
free(l2_dtep, M_DEVBUF);
l2_dtep =
(uint64_t *)PHYS_TO_DMAP(dte & L1_DTE_L2_ADDR_MASK);
goto out;
}
}
out:
return (&l2_dtep[devid % (1 << dev_list->its_dev_dte_l2bits)]);
}
static int
gicv5_its_attach(device_t dev)
{
struct gicv5_its_softc *sc;
uint32_t cfgr, idr;
u_int devid_bits, l2sz, lpi_start, nlpis;
int error, rid;
bool two_levels;
sc = device_get_softc(dev);
lpi_start = gicv5_get_lpi_start(dev);
nlpis = gicv3_get_nirqs(dev);
rid = 0;
sc->its_cfg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->its_cfg == NULL) {
device_printf(dev, "Unable to map config frame\n");
return (ENXIO);
}
switch (bus_read_4(sc->its_cfg, ITS_IDR0) & ITS_IDR0_PA_RANGE_MASK) {
default:
case ITS_IDR0_PA_RANGE_4G:
sc->its_parange = 32;
break;
case ITS_IDR0_PA_RANGE_64G:
sc->its_parange = 36;
break;
case ITS_IDR0_PA_RANGE_1T:
sc->its_parange = 40;
break;
case ITS_IDR0_PA_RANGE_4T:
sc->its_parange = 42;
break;
case ITS_IDR0_PA_RANGE_16T:
sc->its_parange = 44;
break;
case ITS_IDR0_PA_RANGE_256T:
sc->its_parange = 48;
break;
case ITS_IDR0_PA_RANGE_4P:
sc->its_parange = 52;
break;
case ITS_IDR0_PA_RANGE_64P:
sc->its_parange = 56;
break;
}
idr = bus_read_4(sc->its_cfg, ITS_IDR1);
if ((idr & ITS_IDR1_ITT_LEVELS) != 0)
device_printf(dev, "2 level itt\n");
if ((idr & ITS_IDR1_DT_LEVELS) != 0)
device_printf(dev, "2 level device table\n");
if ((idr & (ITS_IDR1_ITT_LEVELS | ITS_IDR1_DT_LEVELS)) != 0) {
if ((idr & ITS_IDR1_L2SZ_64K_MASK) != 0)
device_printf(dev, "64K l2 size\n");
if ((idr & ITS_IDR1_L2SZ_16K_MASK) != 0)
device_printf(dev, "16K l2 size\n");
if ((idr & ITS_IDR1_L2SZ_4K_MASK) != 0)
device_printf(dev, "4K l2 size\n");
if ((idr & ITS_IDR1_L2SZ_MASK) == 0)
device_printf(dev, "2 level tables, but no l2 size\n");
}
its_device_list_init(&sc->its_dl);
TAILQ_INIT(&sc->sc_free_irqs);
error = bus_get_cpus(dev, LOCAL_CPUS, sizeof(sc->its_cpus),
&sc->its_cpus);
if (error != 0) {
device_printf(dev, "Failed to read CPU list\n");
goto exit;
}
if (sc->its_coherent) {
bus_write_4(sc->its_cfg, ITS_CR1, ITS_CR1_ITT_RA |
ITS_CR1_DT_RA | ITS_CR1_IC_WB | ITS_CR1_OC_WB |
ITS_CR1_SH_IS);
} else {
bus_write_4(sc->its_cfg, ITS_CR1, ITS_CR1_IC_NC |
ITS_CR1_OC_NC | ITS_CR1_SH_NS);
}
two_levels = (idr & ITS_IDR1_DT_LEVELS) != 0;
if (two_levels) {
if ((idr & ITS_IDR1_L2SZ_64K_MASK) != 0)
l2sz = ITS_DT_CFGR_L2SZ_64K_VAL;
else if ((idr & ITS_IDR1_L2SZ_16K_MASK) != 0)
l2sz = ITS_DT_CFGR_L2SZ_16K_VAL;
else
l2sz = ITS_DT_CFGR_L2SZ_4K_VAL;
}
devid_bits = ITS_IDR1_DEVICEID_BITS_VAL(idr);
if (two_levels && devid_bits > (9 + (2 * l2sz)))
gicv5_its_dte_alloc_2level(sc, &cfgr, devid_bits, l2sz);
else
gicv5_its_dte_alloc_linear(sc, &cfgr, devid_bits);
bus_write_4(sc->its_cfg, ITS_DT_CFGR, cfgr);
bus_write_8(sc->its_cfg, ITS_DT_BASER,
vtophys(sc->its_dl.its_dev_dte_base));
sc->its_dl.its_dev_irq_alloc = vmem_create(device_get_nameunit(dev),
lpi_start, nlpis, 1, 0, M_FIRSTFIT | M_WAITOK);
sc->sc_irqs = mallocarray(nlpis, sizeof(*sc->sc_irqs), M_DEVBUF,
M_WAITOK | M_ZERO);
its_write_cr0(sc, true);
sc->its_frame.its_pic = intr_pic_register(dev, sc->its_frame.its_xref);
error = intr_pic_add_handler(device_get_parent(dev),
sc->its_frame.its_pic, gicv5_its_intr, sc, lpi_start, nlpis);
if (error != 0) {
device_printf(dev, "Failed to add PIC handler\n");
goto exit;
}
error = intr_msi_register(dev, sc->its_frame.its_xref);
if (error != 0) {
device_printf(dev, "Failed to register for MSIs\n");
goto exit;
}
return (0);
exit:
if (sc->its_frame.its_pic != NULL)
intr_pic_deregister(dev, sc->its_frame.its_xref);
free(sc->sc_irqs, M_DEVBUF);
if (sc->its_dl.its_dev_irq_alloc != NULL)
vmem_destroy(sc->its_dl.its_dev_irq_alloc);
mtx_destroy(&sc->its_dl.its_dev_lock);
bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->its_cfg);
return (error);
}
static void
gicv5_its_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
return (PIC_DISABLE_INTR(device_get_parent(dev), isrc));
}
static void
gicv5_its_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
return (PIC_ENABLE_INTR(device_get_parent(dev), isrc));
}
static void
gicv5_its_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
PIC_PRE_ITHREAD(device_get_parent(dev), isrc);
}
static void
gicv5_its_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
PIC_POST_ITHREAD(device_get_parent(dev), isrc);
}
static void
gicv5_its_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
PIC_POST_FILTER(device_get_parent(dev), isrc);
}
static int
gicv5_its_bind_intr(device_t dev, struct intr_irqsrc *isrc)
{
gicv5_its_select_cpu(dev, isrc);
return (PIC_BIND_INTR(device_get_parent(dev), isrc));
}
static int
gicv5_its_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
panic("%s: Unable to map a MSI interrupt", __func__);
}
static int
gicv5_its_setup_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
gicv5_its_bind_intr(dev, isrc);
return (0);
}
static struct gicv5_its_irqsrc *
gicv5_its_alloc_irqsrc(device_t dev, struct gicv5_its_softc *sc,
u_int event_id, u_int lpi)
{
struct gicv5_its_irqsrc *girq = NULL;
KASSERT(sc->sc_irqs[lpi] == NULL,
("%s: LPI %u already allocated", __func__, lpi));
mtx_lock_spin(&sc->its_dl.its_dev_lock);
if (!TAILQ_EMPTY(&sc->sc_free_irqs)) {
girq = TAILQ_FIRST(&sc->sc_free_irqs);
TAILQ_REMOVE(&sc->sc_free_irqs, girq, gi_link);
}
mtx_unlock_spin(&sc->its_dl.its_dev_lock);
if (girq == NULL) {
girq = malloc(sizeof(*girq), M_DEVBUF,
M_NOWAIT | M_ZERO);
if (girq == NULL)
return (NULL);
girq->gi_isrc.gbi_space = GICv5_LPI;
girq->gi_isrc.gbi_ipi = false;
if (intr_isrc_register(&girq->gi_isrc.gbi_isrc, dev, 0,
"%s,%u", device_get_nameunit(dev), lpi) != 0) {
free(girq, M_DEVBUF);
return (NULL);
}
}
gicv5_irs_extend_ist(device_get_parent(dev), dev, lpi);
girq->gi_isrc.gbi_irq = lpi;
girq->gi_event_id = event_id;
sc->sc_irqs[lpi] = girq;
return (girq);
}
static void
gicv5_its_release_irqsrc(struct gicv5_its_softc *sc,
struct gicv5_its_irqsrc *girq)
{
int error;
error = intr_isrc_deregister(&girq->gi_isrc.gbi_isrc);
if (error != 0)
panic("Failed to deregister ITS irqsrc");
girq->gi_isrc.gbi_irq = -1;
girq->gi_event_id = -1;
girq->gi_its_dev = NULL;
mtx_lock_spin(&sc->its_dl.its_dev_lock);
TAILQ_INSERT_TAIL(&sc->sc_free_irqs, girq, gi_link);
mtx_unlock_spin(&sc->its_dl.its_dev_lock);
}
static void
gicv5_its_update_itt(struct gicv5_its_softc *sc, struct its_dev *its_dev,
struct gicv5_its_irqsrc *girq, u_int event_id)
{
atomic_store_64(&its_dev->itt[event_id],
L2_ITTE_VALID | girq->gi_isrc.gbi_irq);
its_dcache_wbinv(sc, &its_dev->itt[event_id],
sizeof(its_dev->itt[event_id]));
bus_write_8(sc->its_cfg, ITS_DIDR, its_dev->devid);
bus_write_4(sc->its_cfg, ITS_EIDR, event_id);
bus_write_4(sc->its_cfg, ITS_INV_EVENTR, 0x1ul << 31);
its_wait_for_statusr(sc);
}
static int
gicv5_its_alloc_msi(device_t dev, device_t child, int count, int maxcount,
device_t *pic, struct intr_irqsrc **srcs)
{
struct gicv5_its_softc *sc;
struct gicv5_its_irqsrc *girq;
struct its_dev *its_dev;
u_int event_id, lpi;
int i;
sc = device_get_softc(dev);
its_dev = its_device_get(dev, &sc->its_dl, child, count);
if (its_dev == NULL)
return (ENXIO);
KASSERT(its_dev->lpis.lpi_free > 0, ("%s: No free LPIs", __func__));
event_id = its_dev->lpis.lpi_num - its_dev->lpis.lpi_free;
lpi = its_dev->lpis.lpi_base + event_id;
for (i = 0; i < count; i++, lpi++) {
its_dev->lpis.lpi_free--;
srcs[i] = (struct intr_irqsrc *)gicv5_its_alloc_irqsrc(dev, sc,
event_id, lpi);
if (srcs[i] == NULL)
break;
}
if (i != count) {
for (int j = 0; j < i; j++)
gicv5_its_release_irqsrc(sc,
(struct gicv5_its_irqsrc *)srcs[j]);
return (ENXIO);
}
for (i = 0; i < count; i++) {
girq = (struct gicv5_its_irqsrc *)srcs[i];
girq->gi_its_dev = its_dev;
gicv5_its_select_cpu(dev, (struct intr_irqsrc *)girq);
gicv5_its_update_itt(sc, its_dev, girq, event_id + i);
}
its_dev->lpis.lpi_busy += count;
*pic = dev;
return (0);
}
static int
gicv5_its_release_msi(device_t dev, device_t child, int count,
struct intr_irqsrc **isrc)
{
struct gicv5_its_softc *sc;
struct gicv5_its_irqsrc *girq;
struct its_dev *its_dev;
sc = device_get_softc(dev);
its_dev = its_device_find(&sc->its_dl, child);
KASSERT(its_dev != NULL,
("%s: Releasing a MSI interrupt with no ITS device", __func__));
KASSERT(its_dev->lpis.lpi_busy >= count,
("%s: Releasing more interrupts than were allocated: "
"releasing %d, allocated %d", __func__, count,
its_dev->lpis.lpi_busy));
for (int i = 0; i < count; i++) {
girq = (struct gicv5_its_irqsrc *)isrc[i];
gicv5_its_release_irqsrc(sc, girq);
}
its_dev->lpis.lpi_busy -= count;
return (0);
}
static int
gicv5_its_alloc_msix(device_t dev, device_t child, device_t *pic,
struct intr_irqsrc **isrcp)
{
struct gicv5_its_softc *sc;
struct gicv5_its_irqsrc *girq;
struct its_dev *its_dev;
u_int nvecs, event_id, lpi;
sc = device_get_softc(dev);
nvecs = pci_msix_count(child);
its_dev = its_device_get(dev, &sc->its_dl, child, nvecs);
if (its_dev == NULL)
return (ENXIO);
KASSERT(its_dev->lpis.lpi_free > 0, ("%s: No free LPIs", __func__));
event_id = its_dev->lpis.lpi_num - its_dev->lpis.lpi_free;
lpi = its_dev->lpis.lpi_base + event_id;
girq = gicv5_its_alloc_irqsrc(dev, sc, event_id, lpi);
if (girq == NULL)
return (ENXIO);
girq->gi_its_dev = its_dev;
its_dev->lpis.lpi_free--;
its_dev->lpis.lpi_busy++;
gicv5_its_select_cpu(dev, (struct intr_irqsrc *)girq);
gicv5_its_update_itt(sc, its_dev, girq, event_id);
*pic = dev;
*isrcp = (struct intr_irqsrc *)girq;
return (0);
}
static int
gicv5_its_release_msix(device_t dev, device_t child, struct intr_irqsrc *isrc)
{
struct gicv5_its_softc *sc;
struct gicv5_its_irqsrc *girq;
struct its_dev *its_dev;
sc = device_get_softc(dev);
its_dev = its_device_find(&sc->its_dl, child);
KASSERT(its_dev != NULL,
("%s: Releasing a MSI interrupt with no ITS device", __func__));
KASSERT(its_dev->lpis.lpi_busy > 0,
("%s: Releasing more interrupts than were allocated: "
"releasing 1, allocated %d", __func__, its_dev->lpis.lpi_busy));
girq = (struct gicv5_its_irqsrc *)isrc;
gicv5_its_release_irqsrc(sc, girq);
its_dev->lpis.lpi_busy--;
return (0);
}
static int
gicv5_its_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
uint64_t *addr, uint32_t *data)
{
struct gicv5_its_softc *sc;
struct gicv5_its_irqsrc *gi;
struct gicv5_its_translate_frame *frame;
sc = device_get_softc(dev);
gi = (struct gicv5_its_irqsrc *)isrc;
frame = &sc->its_frame;
*addr = frame->its_frame_paddr + ITS_TRANSLATER;
*data = gi->gi_event_id;
return (0);
}
#ifdef IOMMU
static int
gicv5_iommu_init(device_t dev, device_t child, struct iommu_domain **domain)
{
panic("%s", __func__);
}
static void
gicv5_iommu_deinit(device_t dev, device_t child)
{
panic("%s", __func__);
}
#endif
#ifdef FDT
static device_probe_t gicv5_its_fdt_probe;
static device_attach_t gicv5_its_fdt_attach;
static device_method_t gicv5_its_fdt_methods[] = {
DEVMETHOD(device_probe, gicv5_its_fdt_probe),
DEVMETHOD(device_attach, gicv5_its_fdt_attach),
DEVMETHOD_END
};
#define its_baseclasses itsv5_fdt_baseclasses
DEFINE_CLASS_1(its, gicv5_its_fdt_driver, gicv5_its_fdt_methods,
sizeof(struct gicv5_its_softc), gicv5_its_driver);
#undef its_baseclasses
EARLY_DRIVER_MODULE(itsv5_fdt, gic, gicv5_its_fdt_driver, 0, 0,
BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
static int
gicv5_its_fdt_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "arm,gic-v5-its"))
return (ENXIO);
if (!gic_get_support_lpis(dev))
return (ENXIO);
device_set_desc(dev, "ARM GICv5 Interrupt Translation Service");
return (BUS_PROBE_DEFAULT);
}
static int
gicv5_its_fdt_attach(device_t dev)
{
struct gicv5_its_softc *sc;
phandle_t node, child;
bool found;
sc = device_get_softc(dev);
node = ofw_bus_get_node(dev);
found = false;
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
if (OF_hasprop(child, "msi-controller")) {
bus_size_t size;
int idx;
if (ofw_bus_find_string_index(child, "reg-names",
"ns-translate", &idx) != 0)
continue;
if (found) {
device_printf(dev,
"Too many ITS frames found\n");
return (EINVAL);
}
if (ofw_reg_to_paddr(child, idx,
&sc->its_frame.its_frame_paddr, &size, NULL) != 0) {
device_printf(dev,
"Unable to read frame physical address\n");
return (EINVAL);
}
sc->its_frame.its_pic = NULL;
sc->its_frame.its_xref = OF_xref_from_node(child);
found = true;
}
}
if (!found) {
device_printf(dev, "No valid ITS frame found\n");
return (EINVAL);
}
sc->its_coherent = !OF_hasprop(node, "dma-noncoherent");
return (gicv5_its_attach(dev));
}
#endif