#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/pool.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/dsdt.h>
#include <dev/pci/pcivar.h>
#include <arm64/dev/acpiiort.h>
#include <arm64/dev/smmuvar.h>
#include <arm64/dev/smmureg.h>
struct smmu_v2_acpi_softc {
void *sc_gih;
};
struct smmu_v3_acpi_softc {
void *sc_eih;
void *sc_gih;
void *sc_pih;
};
struct smmu_acpi_softc {
struct smmu_softc sc_smmu;
union {
struct smmu_v2_acpi_softc v2;
struct smmu_v3_acpi_softc v3;
};
};
int smmu_acpi_match(struct device *, void *, void *);
void smmu_acpi_attach(struct device *, struct device *, void *);
int smmu_v2_acpi_attach(struct smmu_acpi_softc *, struct acpi_iort_node *);
int smmu_v3_acpi_attach(struct smmu_acpi_softc *, struct acpi_iort_node *);
int smmu_acpi_foundqcom(struct aml_node *, void *);
const struct cfattach smmu_acpi_ca = {
sizeof(struct smmu_acpi_softc), smmu_acpi_match, smmu_acpi_attach
};
int
smmu_acpi_match(struct device *parent, void *match, void *aux)
{
struct acpiiort_attach_args *aia = aux;
struct acpi_iort_node *node = aia->aia_node;
if (node->type != ACPI_IORT_SMMU)
return 0;
return 1;
}
void
smmu_acpi_attach(struct device *parent, struct device *self, void *aux)
{
struct smmu_acpi_softc *asc = (struct smmu_acpi_softc *)self;
struct smmu_softc *sc = &asc->sc_smmu;
struct acpiiort_attach_args *aia = aux;
struct acpi_iort_node *node = aia->aia_node;
struct acpiiort_smmu *as;
int ret = ENXIO;
sc->sc_dmat = aia->aia_dmat;
sc->sc_iot = aia->aia_memt;
if (node->type == ACPI_IORT_SMMU)
ret = smmu_v2_acpi_attach(asc, node);
if (node->type == ACPI_IORT_SMMU_V3)
ret = smmu_v3_acpi_attach(asc, node);
if (ret)
return;
as = malloc(sizeof(*as), M_DEVBUF, M_WAITOK | M_ZERO);
as->as_node = node;
as->as_cookie = sc;
as->as_map = smmu_device_map;
as->as_reserve = smmu_reserve_region;
acpiiort_smmu_register(as);
}
int
smmu_v2_acpi_attach(struct smmu_acpi_softc *asc, struct acpi_iort_node *node)
{
struct smmu_softc *sc = &asc->sc_smmu;
struct acpi_iort_smmu_node *smmu;
struct acpi_iort_smmu_global_interrupt *girq;
struct acpi_iort_smmu_context_interrupt *cirq;
int i;
smmu = (struct acpi_iort_smmu_node *)&node[1];
printf(" addr 0x%llx/0x%llx", smmu->base_address, smmu->span);
if (bus_space_map(sc->sc_iot, smmu->base_address, smmu->span,
0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return EIO;
}
switch (smmu->model) {
case ACPI_IORT_SMMU_V1:
case ACPI_IORT_SMMU_CORELINK_MMU400:
case ACPI_IORT_SMMU_CORELINK_MMU401:
printf(": SMMUv1 is unsupported\n");
return ENXIO;
case ACPI_IORT_SMMU_CORELINK_MMU500:
sc->sc_is_mmu500 = 1;
case ACPI_IORT_SMMU_V2:
case ACPI_IORT_SMMU_CAVIUM_THUNDERX:
break;
default:
printf(": unknown model %u\n", smmu->model);
return ENXIO;
}
if (smmu->flags & ACPI_IORT_SMMU_COHERENT)
sc->sc_coherent = 1;
aml_find_node(acpi_softc->sc_root, "_HID", smmu_acpi_foundqcom, sc);
if (sc->sc_is_qcom) {
printf(": disabled\n");
return ENXIO;
}
if (smmu_v2_attach(sc) != 0)
return ENXIO;
girq = (struct acpi_iort_smmu_global_interrupt *)
((char *)node + smmu->global_interrupt_offset);
asc->v2.sc_gih = acpi_intr_establish(girq->nsgirpt_gsiv,
girq->nsgirpt_flags & ACPI_IORT_SMMU_INTR_EDGE ?
LR_EXTIRQ_MODE : 0, IPL_TTY, smmu_v2_global_irq,
sc, sc->sc_dev.dv_xname);
if (asc->v2.sc_gih == NULL)
return ENXIO;
cirq = (struct acpi_iort_smmu_context_interrupt *)
((char *)node + smmu->context_interrupt_offset);
for (i = 0; i < smmu->number_of_context_interrupts; i++) {
struct smmu_cb_irq *cbi = malloc(sizeof(*cbi),
M_DEVBUF, M_WAITOK);
cbi->cbi_sc = sc;
cbi->cbi_idx = i;
acpi_intr_establish(cirq[i].gsiv,
cirq[i].flags & ACPI_IORT_SMMU_INTR_EDGE ?
LR_EXTIRQ_MODE : 0, IPL_TTY, smmu_v2_context_irq,
cbi, sc->sc_dev.dv_xname);
}
return 0;
}
int
smmu_acpi_foundqcom(struct aml_node *node, void *arg)
{
struct smmu_softc *sc = (struct smmu_softc *)arg;
char cdev[32], dev[32];
if (acpi_parsehid(node, arg, cdev, dev, sizeof(dev)) != 0)
return 0;
if (strcmp(dev, "QCOM0409") == 0 ||
strcmp(dev, "QCOM0609") == 0 ||
strcmp(dev, "QCOM0809") == 0 ||
strcmp(dev, "QCOM0C09") == 0 ||
strcmp(dev, "QCOM0E09") == 0)
sc->sc_is_qcom = 1;
return 0;
}
int
smmu_v3_acpi_attach(struct smmu_acpi_softc *asc, struct acpi_iort_node *node)
{
struct smmu_softc *sc = &asc->sc_smmu;
struct acpi_iort_smmu_v3_node *smmu;
uint64_t span = 0x20000;
smmu = (struct acpi_iort_smmu_v3_node *)&node[1];
printf(" addr 0x%llx/0x%llx", smmu->base_address, span);
if (bus_space_map(sc->sc_iot, smmu->base_address, span,
0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return EIO;
}
switch (smmu->model) {
case ACPI_IORT_SMMU_V3_GENERIC:
break;
default:
printf(": unknown model %u\n", smmu->model);
return ENXIO;
}
if (ACPI_IORT_SMMU_V3_COHACC_OVERRIDE(smmu->flags)) {
bus_dma_tag_t dmat = malloc(sizeof(*sc->sc_dmat), M_DEVBUF,
M_WAITOK | M_ZERO);
memcpy(dmat, sc->sc_dmat, sizeof(*dmat));
dmat->_flags |= BUS_DMA_COHERENT;
sc->sc_dmat = dmat;
sc->sc_coherent = 1;
}
aml_find_node(acpi_softc->sc_root, "_HID", smmu_acpi_foundqcom, sc);
if (sc->sc_is_qcom) {
printf(": disabled\n");
return ENXIO;
}
if (smmu_v3_attach(sc) != 0)
return ENXIO;
if (smmu->event)
asc->v3.sc_eih = acpi_intr_establish(smmu->event,
LR_EXTIRQ_MODE, IPL_TTY, smmu_v3_event_irq,
sc, sc->sc_dev.dv_xname);
if (smmu->gerr)
asc->v3.sc_gih = acpi_intr_establish(smmu->gerr,
LR_EXTIRQ_MODE, IPL_TTY, smmu_v3_gerr_irq,
sc, sc->sc_dev.dv_xname);
if (sc->v3.sc_has_pri) {
if (smmu->pri)
asc->v3.sc_pih = acpi_intr_establish(smmu->pri,
LR_EXTIRQ_MODE, IPL_TTY, smmu_v3_priq_irq,
sc, sc->sc_dev.dv_xname);
}
if (asc->v3.sc_eih == NULL || asc->v3.sc_gih == NULL ||
(sc->v3.sc_has_pri && asc->v3.sc_pih == NULL))
printf("%s: couldn't establish all interrupts:%s%s%s\n",
sc->sc_dev.dv_xname,
asc->v3.sc_eih == NULL ? " event" : "",
asc->v3.sc_gih == NULL ? " gerr" : "",
asc->v3.sc_pih == NULL ? " pri" : "");
return 0;
}