Joel Carnat reported that using both disk slots of the ODROID-HC4 doesn't work with OpenBSD. I never tried that in mine, but could indeed reproduce the issue.
The machine seems to hang on doing disk I/O, so I started looking at interrupts. PCIe interrupts on this machine have always been fishy. These interrupts should always be level triggered, but the only way I could make ahci(4) work on this machine was by using an edge triggered interrupt. So I modified the device tree and changed the interrupt trigger type from level to edge triggered. That wasn't a completely random change as older generations of the Amlogic SoC do have the PCIe interrupt as edge triggered in the device tree. But the use of edge triggered PCIe interrupts is questionable since things like interrupt sharing rely on interrupts being level triggered. And it doesn't surprise me at all that when using multiple ports on ahci(4) things don't quite work with edge triggered interrupts. So I wondered whether using MSIs would fix the issue, and indeed it does. MSIs on this particular PCIe host bridge are retarded. There is a single interrupt and you have to check a register to see which of the 32 possible MSIs fired. But if it is the only way to get this hardware working, so be it. But for now, I think we should only enable this feature on the Amlogic SoCs. So that is what this diff does. ok? Index: dev/fdt/dwpcie.c =================================================================== RCS file: /cvs/src/sys/dev/fdt/dwpcie.c,v retrieving revision 1.38 diff -u -p -r1.38 dwpcie.c --- dev/fdt/dwpcie.c 7 Nov 2022 20:15:44 -0000 1.38 +++ dev/fdt/dwpcie.c 27 Nov 2022 18:43:14 -0000 @@ -18,6 +18,7 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> +#include <sys/evcount.h> #include <sys/extent.h> #include <sys/malloc.h> @@ -56,6 +57,12 @@ #define PCIE_LINK_WIDTH_SPEED_CTRL_LANES_8 (0x8 << 8) #define PCIE_LINK_WIDTH_SPEED_CTRL_CHANGE (1 << 17) +#define PCIE_MSI_ADDR_LO 0x820 +#define PCIE_MSI_ADDR_HI 0x824 +#define PCIE_MSI_INTR0_ENABLE 0x828 +#define PCIE_MSI_INTR0_MASK 0x82c +#define PCIE_MSI_INTR0_STATUS 0x830 + #define MISC_CONTROL_1 0x8bc #define MISC_CONTROL_1_DBI_RO_WR_EN (1 << 0) #define IATU_VIEWPORT 0x900 @@ -179,6 +186,18 @@ struct dwpcie_range { uint64_t size; }; +#define DWPCIE_NUM_MSI 32 + +struct dwpcie_msi { + int (*dm_func)(void *); + void *dm_arg; + int dm_ipl; + int dm_flags; + int dm_vec; + struct evcount dm_count; + char *dm_name; +}; + struct dwpcie_softc { struct device sc_dev; bus_space_tag_t sc_iot; @@ -229,10 +248,15 @@ struct dwpcie_softc { int sc_atu_viewport; void *sc_ih; + + uint64_t sc_msi_addr; + struct dwpcie_msi sc_msi[DWPCIE_NUM_MSI]; }; struct dwpcie_intr_handle { struct machine_intr_handle pih_ih; + struct dwpcie_softc *pih_sc; + struct dwpcie_msi *pih_dm; bus_dma_tag_t pih_dmat; bus_dmamap_t pih_map; }; @@ -649,7 +673,8 @@ dwpcie_attach_deferred(struct device *se pba.pba_domain = pci_ndomains++; pba.pba_bus = sc->sc_bus; if (OF_is_compatible(sc->sc_node, "marvell,armada8k-pcie") || - OF_is_compatible(sc->sc_node, "rockchip,rk3568-pcie")) + OF_is_compatible(sc->sc_node, "rockchip,rk3568-pcie") || + sc->sc_msi_addr) pba.pba_flags |= PCI_FLAGS_MSI_ENABLED; config_found(self, &pba, NULL); @@ -701,6 +726,104 @@ dwpcie_link_config(struct dwpcie_softc * } int +dwpcie_msi_intr(void *arg) +{ + struct dwpcie_softc *sc = arg; + struct dwpcie_msi *dm; + uint32_t status; + int vec, s; + + status = HREAD4(sc, PCIE_MSI_INTR0_STATUS); + if (status == 0) + return 0; + + HWRITE4(sc, PCIE_MSI_INTR0_STATUS, status); + while (status) { + vec = ffs(status) - 1; + status &= ~(1 << vec); + + dm = &sc->sc_msi[vec]; + if (dm->dm_func == NULL) + continue; + + if ((dm->dm_flags & IPL_MPSAFE) == 0) + KERNEL_LOCK(); + s = splraise(dm->dm_ipl); + if (dm->dm_func(dm->dm_arg)) + dm->dm_count.ec_count++; + splx(s); + if ((dm->dm_flags & IPL_MPSAFE) == 0) + KERNEL_UNLOCK(); + } + + return 1; +} + +int +dwpcie_msi_init(struct dwpcie_softc *sc) +{ + bus_dma_segment_t seg; + bus_dmamap_t map; + uint64_t addr; + int error, rseg; + + /* + * Allocate some DMA memory such that we have a "safe" target + * address for MSIs. + */ + error = bus_dmamem_alloc(sc->sc_dmat, sizeof(uint32_t), + sizeof(uint32_t), 0, &seg, 1, &rseg, BUS_DMA_WAITOK); + if (error) + return error; + + /* + * Translate the CPU address into a bus address that we can + * program into the hardware. + */ + error = bus_dmamap_create(sc->sc_dmat, sizeof(uint32_t), 1, + sizeof(uint32_t), 0, BUS_DMA_WAITOK, &map); + if (error) { + bus_dmamem_free(sc->sc_dmat, &seg, 1); + return error; + } + error = bus_dmamap_load_raw(sc->sc_dmat, map, &seg, 1, + sizeof(uint32_t), BUS_DMA_WAITOK); + if (error) { + bus_dmamap_destroy(sc->sc_dmat, map); + bus_dmamem_free(sc->sc_dmat, &seg, 1); + return error; + } + + addr = map->dm_segs[0].ds_addr; + HWRITE4(sc, PCIE_MSI_ADDR_LO, addr); + HWRITE4(sc, PCIE_MSI_ADDR_HI, addr >> 32); + + bus_dmamap_unload(sc->sc_dmat, map); + bus_dmamap_destroy(sc->sc_dmat, map); + + /* Enable, mask and clear all MSIs. */ + HWRITE4(sc, PCIE_MSI_INTR0_ENABLE, 0xffffffff); + HWRITE4(sc, PCIE_MSI_INTR0_MASK, 0xffffffff); + HWRITE4(sc, PCIE_MSI_INTR0_STATUS, 0xffffffff); + + KASSERT(sc->sc_ih == NULL); + sc->sc_ih = fdt_intr_establish(sc->sc_node, IPL_BIO | IPL_MPSAFE, + dwpcie_msi_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + bus_dmamem_free(sc->sc_dmat, &seg, 1); + return EINVAL; + } + + /* + * Hold on to the DMA memory such that nobody can use to + * actually do DMA transfers. + */ + + sc->sc_msi_addr = addr; + return 0; +} + +int dwpcie_armada8k_init(struct dwpcie_softc *sc) { uint32_t reg; @@ -795,7 +918,7 @@ dwpcie_g12a_init(struct dwpcie_softc *sc uint32_t *reset_gpio; ssize_t reset_gpiolen; uint32_t reg; - int timo; + int error, timo; reset_gpiolen = OF_getproplen(sc->sc_node, "reset-gpios"); if (reset_gpiolen <= 0) @@ -843,6 +966,10 @@ dwpcie_g12a_init(struct dwpcie_softc *sc if (timo == 0) return ETIMEDOUT; + error = dwpcie_msi_init(sc); + if (error) + return error; + return 0; } @@ -1346,52 +1473,113 @@ dwpcie_intr_string(void *v, pci_intr_han return "intx"; } +struct dwpcie_msi * +dwpcie_msi_establish(struct dwpcie_softc *sc, int level, + int (*func)(void *), void *arg, char *name) +{ + struct dwpcie_msi *dm; + int vec; + + for (vec = 0; vec < DWPCIE_NUM_MSI; vec++) { + dm = &sc->sc_msi[vec]; + if (dm->dm_func == NULL) + break; + } + if (vec == DWPCIE_NUM_MSI) + return NULL; + + dm->dm_func = func; + dm->dm_arg = arg; + dm->dm_ipl = level & IPL_IRQMASK; + dm->dm_flags = level & IPL_FLAGMASK; + dm->dm_vec = vec; + dm->dm_name = name; + if (name != NULL) + evcount_attach(&dm->dm_count, name, &dm->dm_vec); + + /* Unmask the MSI. */ + HCLR4(sc, PCIE_MSI_INTR0_MASK, (1U << vec)); + + return dm; +} + +void +dwpcie_msi_disestablish(struct dwpcie_softc *sc, struct dwpcie_msi *dm) +{ + /* Mask the MSI. */ + HSET4(sc, PCIE_MSI_INTR0_MASK, (1U << dm->dm_vec)); + + if (dm->dm_name) + evcount_detach(&dm->dm_count); + dm->dm_func = NULL; +} + void * dwpcie_intr_establish(void *v, pci_intr_handle_t ih, int level, struct cpu_info *ci, int (*func)(void *), void *arg, char *name) { struct dwpcie_softc *sc = v; struct dwpcie_intr_handle *pih; - bus_dma_segment_t seg; - void *cookie; + void *cookie = NULL; KASSERT(ih.ih_type != PCI_NONE); if (ih.ih_type != PCI_INTX) { + struct dwpcie_msi *dm = NULL; + bus_dma_tag_t dmat = ih.ih_dmat; + bus_dma_segment_t seg; + bus_dmamap_t map; uint64_t addr, data; - /* Assume hardware passes Requester ID as sideband data. */ - data = pci_requester_id(ih.ih_pc, ih.ih_tag); - cookie = fdt_intr_establish_msi_cpu(sc->sc_node, &addr, - &data, level, ci, func, arg, (void *)name); - if (cookie == NULL) - return NULL; + if (sc->sc_msi_addr) { + dm = dwpcie_msi_establish(sc, level, func, arg, name); + if (dm == NULL) + return NULL; + addr = sc->sc_msi_addr; + data = dm->dm_vec; + } else { + /* + * Assume hardware passes Requester ID as + * sideband data. + */ + data = pci_requester_id(ih.ih_pc, ih.ih_tag); + cookie = fdt_intr_establish_msi_cpu(sc->sc_node, &addr, + &data, level, ci, func, arg, (void *)name); + if (cookie == NULL) + return NULL; + } - pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK); + pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK | M_ZERO); pih->pih_ih.ih_ic = &dwpcie_ic; pih->pih_ih.ih_ih = cookie; - pih->pih_dmat = ih.ih_dmat; + pih->pih_sc = sc; + pih->pih_dm = dm; - if (bus_dmamap_create(pih->pih_dmat, sizeof(uint32_t), 1, - sizeof(uint32_t), 0, BUS_DMA_WAITOK, &pih->pih_map)) { - free(pih, M_DEVBUF, sizeof(*pih)); - fdt_intr_disestablish(cookie); - return NULL; - } - - memset(&seg, 0, sizeof(seg)); - seg.ds_addr = addr; - seg.ds_len = sizeof(uint32_t); - - if (bus_dmamap_load_raw(pih->pih_dmat, pih->pih_map, - &seg, 1, sizeof(uint32_t), BUS_DMA_WAITOK)) { - bus_dmamap_destroy(pih->pih_dmat, pih->pih_map); - free(pih, M_DEVBUF, sizeof(*pih)); - fdt_intr_disestablish(cookie); - return NULL; + if (sc->sc_msi_addr == 0) { + if (bus_dmamap_create(dmat, sizeof(uint32_t), 1, + sizeof(uint32_t), 0, BUS_DMA_WAITOK, &map)) { + free(pih, M_DEVBUF, sizeof(*pih)); + fdt_intr_disestablish(cookie); + return NULL; + } + + memset(&seg, 0, sizeof(seg)); + seg.ds_addr = addr; + seg.ds_len = sizeof(uint32_t); + + if (bus_dmamap_load_raw(dmat, map, &seg, 1, + sizeof(uint32_t), BUS_DMA_WAITOK)) { + bus_dmamap_destroy(dmat, map); + free(pih, M_DEVBUF, sizeof(*pih)); + fdt_intr_disestablish(cookie); + return NULL; + } + + addr = map->dm_segs[0].ds_addr; + pih->pih_dmat = dmat; + pih->pih_map = map; } - addr = pih->pih_map->dm_segs[0].ds_addr; if (ih.ih_type == PCI_MSIX) { pci_msix_enable(ih.ih_pc, ih.ih_tag, &sc->sc_bus_memt, ih.ih_intrpin, addr, data); @@ -1412,10 +1600,9 @@ dwpcie_intr_establish(void *v, pci_intr_ if (cookie == NULL) return NULL; - pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK); + pih = malloc(sizeof(*pih), M_DEVBUF, M_WAITOK | M_ZERO); pih->pih_ih.ih_ic = &dwpcie_ic; pih->pih_ih.ih_ih = cookie; - pih->pih_dmat = NULL; } return pih; @@ -1426,11 +1613,16 @@ dwpcie_intr_disestablish(void *v, void * { struct dwpcie_intr_handle *pih = cookie; - fdt_intr_disestablish(pih->pih_ih.ih_ih); + if (pih->pih_dm) + dwpcie_msi_disestablish(pih->pih_sc, pih->pih_dm); + else + fdt_intr_disestablish(pih->pih_ih.ih_ih); + if (pih->pih_dmat) { bus_dmamap_unload(pih->pih_dmat, pih->pih_map); bus_dmamap_destroy(pih->pih_dmat, pih->pih_map); } + free(pih, M_DEVBUF, sizeof(*pih)); }