Index: sys/arm/annapurna/alpine/alpine_pci_msix.c =================================================================== --- /dev/null +++ sys/arm/annapurna/alpine/alpine_pci_msix.c @@ -0,0 +1,432 @@ +/*- + * Copyright (c) 2015,2016 Annapurna Labs Ltd. and affiliates + * All rights reserved. + * + * Developed by Semihalf. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "msi_if.h" +#include "pic_if.h" + +#define AL_SPI_INTR 0 +#define AL_LOW_TO_HIGH_TRIG 1 +#define ERR_NOT_IN_MAP -1 +#define IRQ_OFFSET 1 +#define SPI_OFFSET 32 +#define INTR_RANGE_COUNT 2 +#define MAX_MSIX_COUNT 160 + +#define OFW_CELL_TO_UINT64(cell) \ + (((uint64_t)(*(cell)) << 32) | (uint64_t)(*((cell) + 1))) + +static int al_msix_attach(device_t); +static int al_msix_probe(device_t); + +static msi_alloc_msi_t al_msix_alloc_msi; +static msi_release_msi_t al_msix_release_msi; +static msi_alloc_msix_t al_msix_alloc_msix; +static msi_release_msix_t al_msix_release_msix; +static msi_map_msi_t al_msix_map_msi; + +static int al_find_intr_pos_in_map(struct intr_irqsrc *); + +static struct ofw_compat_data compat_data[] = { + {"annapurna-labs,al-msix", true}, + {"annapurna-labs,alpine-msix", true}, + {NULL, false} +}; + +/* + * Bus interface definitions. + */ +static device_method_t al_msix_methods[] = { + DEVMETHOD(device_probe, al_msix_probe), + DEVMETHOD(device_attach, al_msix_attach), + + /* Interrupt controller interface */ + DEVMETHOD(msi_alloc_msi, al_msix_alloc_msi), + DEVMETHOD(msi_release_msi, al_msix_release_msi), + DEVMETHOD(msi_alloc_msix, al_msix_alloc_msix), + DEVMETHOD(msi_release_msix, al_msix_release_msix), + DEVMETHOD(msi_map_msi, al_msix_map_msi), + + DEVMETHOD_END +}; + +struct al_msix_softc { + struct intr_pic *sc_pic; + device_t sc_dev; + bus_addr_t base_addr; + struct resource *reg; + uint32_t irq_min; + uint32_t irq_max; + uint32_t irq_count; + struct mtx msi_mtx; + char msi_bitmap[(MAX_MSIX_COUNT-1)/sizeof(char) + 1]; + device_t gic_dev; + /* Table of isrcs maps isrc pointer to msi bitmap index */ + struct intr_irqsrc *isrcs[MAX_MSIX_COUNT]; +}; + +static driver_t al_msix_driver = { + "al_msix", + al_msix_methods, + sizeof(struct al_msix_softc), +}; + +devclass_t al_msix_devclass; + +DRIVER_MODULE(al_msix, ofwbus, al_msix_driver, al_msix_devclass, 0, 0); +DRIVER_MODULE(al_msix, simplebus, al_msix_driver, al_msix_devclass, 0, 0); + +struct al_msix_softc *g_softc; + +static int +al_msix_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Annapurna-Labs MSI-X Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +al_msix_attach(device_t dev) +{ + struct al_msix_softc *sc; + device_t gic_dev; + uint64_t reg_base; + uint64_t reg_size; + uint32_t icells, *intr; + pcell_t reg[4]; + pcell_t par_addr, par_size; + intptr_t xref; + phandle_t iparent; + phandle_t node; + int interrupts[INTR_RANGE_COUNT]; + int nintr, i, rid; + + if (g_softc) { + device_printf(dev, + "Error, only one MSI-X controller is supported\n"); + return (ENXIO); + } + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + sc->sc_dev = dev; + + rid = 0; + sc->reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &rid, RF_ACTIVE); + if (sc->reg == NULL) { + device_printf(dev, "Failed to allocate resource\n"); + return (ENXIO); + } + + OF_getencprop(OF_parent(node), "#address-cells", + &par_addr, sizeof(par_addr)); + + OF_getencprop(OF_parent(node), "#size-cells", + &par_size, sizeof(par_size)); + + OF_getencprop(node, "reg", (pcell_t*)reg, sizeof(reg)); + + if (par_addr == 1) + reg_base = (uint64_t)reg[0]; + else + reg_base = OFW_CELL_TO_UINT64(®[0]); + + if (par_size == 1) + reg_size = (uint64_t)reg[par_addr]; + else + reg_size = OFW_CELL_TO_UINT64(®[par_addr]); + + sc->base_addr = (bus_addr_t)reg_base; + + memset(interrupts, 0, sizeof(interrupts)); + + xref = OF_xref_from_node(node); + + /* Register this device to handle MSI interrupts */ + if (intr_msi_register(dev, xref) != 0) + device_printf(dev, "could not register MSI-X controller\n"); + else + device_printf(dev, "MSI-X controller registered\n"); + + OF_device_register_xref(xref, dev); + + /* Find root interrupt controller */ + iparent = ofw_bus_find_iparent(node); + if (iparent == 0) { + device_printf(dev, "No interrupt-parrent found. " + "Error in DTB\n"); + return (ENXIO); + } else { + /* While at parent - store interrupt cells prop */ + if (OF_searchencprop(OF_node_from_xref(iparent), + "#interrupt-cells", &icells, sizeof(icells)) == -1) { + device_printf(dev, "DTB: Missing #interrupt-cells " + "property in GIC node\n"); + return (ENXIO); + } + } + + gic_dev = OF_device_from_xref(iparent); + if (gic_dev == NULL) { + device_printf(dev, "Cannot find GIC device\n"); + return (ENXIO); + } + sc->gic_dev = gic_dev; + + /* Manually read range of interrupts from DTB */ + nintr = OF_getencprop_alloc(node, "interrupts", sizeof(*intr), + (void **)&intr); + if (nintr == 0) { + device_printf(dev, "Cannot read interrupts prop from DTB\n"); + return (ENXIO); + } else if ((nintr / icells) != INTR_RANGE_COUNT) { + /* Supposed to have min and max value only */ + device_printf(dev, "Unexpected count of interrupts " + "in DTB node\n"); + return (EINVAL); + } + + /* Read interrupt range values */ + for (i = 0; i < INTR_RANGE_COUNT; i++) + interrupts[i] = intr[(i * icells) + IRQ_OFFSET]; + + sc->irq_min = interrupts[0]; + sc->irq_max = interrupts[1]; + sc->irq_count = (sc->irq_max - sc->irq_min + 1); + + if (sc->irq_count > MAX_MSIX_COUNT) { + device_printf(dev, "Available MSI-X count exceeds buffer size." + " Capping to %d\n", MAX_MSIX_COUNT); + sc->irq_count = MAX_MSIX_COUNT; + } + + mtx_init(&sc->msi_mtx, "msi_mtx", NULL, MTX_DEF); + + device_printf(dev, + "MSI-X base address 0x%jx, size 0x%jx, IRQ %d-%d\n", + (uintmax_t)reg_base, (uintmax_t)reg_size, sc->irq_min, sc->irq_max); + + g_softc = sc; + + return (bus_generic_attach(dev)); +} + +static int +al_find_intr_pos_in_map(struct intr_irqsrc *isrc) +{ + int i; + + for (i = 0; i < MAX_MSIX_COUNT; i++) + if (g_softc->isrcs[i] == isrc) + return (i); + return (ERR_NOT_IN_MAP); +} + +static int +al_msix_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc, + uint64_t *addr, uint32_t *data) +{ + int i, irq; + + i = al_find_intr_pos_in_map(isrc); + if (i == ERR_NOT_IN_MAP) + return (EINVAL); + + irq = g_softc->irq_min + i; + /* Validate parameters */ + if (isclr(&g_softc->msi_bitmap, i)) { + device_printf(g_softc->sc_dev, + "SPI %d mapped but not allocated\n", irq); + return (EINVAL); + } + /* + * MSIX message address format: + * [63:20] - MSIx TBAR + * Same value as the MSIx Translation Base Address Register + * [19] - WFE_EXIT + * Once set by MSIx message, an EVENTI is signal to the CPUs + * cluster specified by ‘Local GIC Target List’ + * [18:17] - Target GIC ID + * Specifies which IO-GIC (external shared GIC) is targeted + * 0: Local GIC, as specified by the Local GIC Target List + * 1: IO-GIC 0 + * 2: Reserved + * 3: Reserved + * [16:13] - Local GIC Target List + * Specifies the Local GICs list targeted by this MSIx + * message. + * [16] If set, SPIn is set in Cluster 0 local GIC + * [15:13] Reserved + * [15] If set, SPIn is set in Cluster 1 local GIC + * [14] If set, SPIn is set in Cluster 2 local GIC + * [13] If set, SPIn is set in Cluster 3 local GIC + * [12:3] - SPIn + * Specifies the SPI (Shared Peripheral Interrupt) index to + * be set in target GICs + * Notes: + * If targeting any local GIC than only SPI[249:0] are valid + * [2] - Function vector + * MSI Data vector extension hint + * [1:0] - Reserved + * Must be set to zero + */ + *addr = (uint64_t)g_softc->base_addr + (uint64_t)((1 << 16) + + (irq << 3)); + *data = 0; + + if (bootverbose) + device_printf(g_softc->sc_dev, + "MSI mapping: irq: s%d addr: %jx data: %x\n", + irq, *addr, *data); + return (0); +} + +static int +al_msix_alloc_msi(device_t dev, device_t child, int count, int maxcount, + device_t *pic, struct intr_irqsrc **srcs) +{ + struct intr_map_data_fdt fdt_data; + struct al_msix_softc *sc; + u_int i, start; + int error; + + sc = g_softc; + + if ((powerof2(count) == 0) || (count > 8)) + return (EINVAL); + + mtx_lock(&sc->msi_mtx); + + for (start = 0; (start + count) < (sc->irq_count); start++) { + for (i = start; i < start + count; i++) { + if (isset(&sc->msi_bitmap, i)) + break; + } + if (i == (start + count)) + break; + } + + /* Fabricate OFW data to get ISRC from GIC and return it */ + pcell_t cells[3] = {0, 0, 1}; /* SPI, undefined, trig=edge, pol=high */ + + fdt_data.hdr.type = INTR_MAP_DATA_FDT; + fdt_data.hdr.destruct = NULL; + fdt_data.iparent = 0; + fdt_data.ncells = 3; + fdt_data.cells = cells; + + for (i = start; i < start + count; i++) { + cells[1] = sc->irq_min + i; + error = PIC_MAP_INTR(sc->gic_dev, + (struct intr_map_data *)&fdt_data, srcs); + if (error) + return (error); + + g_softc->isrcs[i] = *srcs; + srcs++; + setbit(&sc->msi_bitmap, i); + } + + if (bootverbose) + device_printf(sc->sc_dev, + "MSI-X allocation: start IRQ %d, count %d\n", + start + sc->irq_min, count); + + mtx_unlock(&sc->msi_mtx); + + *pic = sc->gic_dev; + + return (0); +} + +static int +al_msix_release_msi(device_t dev, device_t child, int count, + struct intr_irqsrc **srcs) +{ + int i, pos; + + mtx_lock(&g_softc->msi_mtx); + + for (i = 0; i < count; i++) { + pos = al_find_intr_pos_in_map(*srcs); + if (pos != ERR_NOT_IN_MAP) + clrbit(&g_softc->msi_bitmap, pos); + srcs++; + } + + mtx_unlock(&g_softc->msi_mtx); + + return (0); +} + +static int +al_msix_alloc_msix(device_t dev, device_t child, device_t *pic, + struct intr_irqsrc **isrcp) +{ + + return (al_msix_alloc_msi(dev, child, 1, 1, pic, isrcp)); +} + +static int +al_msix_release_msix(device_t dev, device_t child, struct intr_irqsrc *isrc) +{ + + return (al_msix_release_msi(dev, child, 1, &isrc)); +} Index: sys/boot/fdt/dts/arm/annapurna-alpine.dts =================================================================== --- sys/boot/fdt/dts/arm/annapurna-alpine.dts +++ sys/boot/fdt/dts/arm/annapurna-alpine.dts @@ -170,6 +170,16 @@ }; }; + /* MSIX Configuration */ + msix: msix { + compatible = "annapurna-labs,al-msix"; + #address-cells = <2>; + #size-cells = <1>; + reg = <0xfbe00000 0x100000>; + interrupts = <0 96 1 0 159 1>; + interrupt-parent = <&MPIC>; + }; + pcie-internal { compatible = "annapurna-labs,al-internal-pcie"; device_type = "pci"; @@ -182,6 +192,7 @@ <0x3800 0 0 1 &MPIC 0 36 4>, <0x4000 0 0 1 &MPIC 0 43 4>, // SATA 0 (PCIe expander) <0x4800 0 0 1 &MPIC 0 44 1>; // SATA 1 (onboard) + msi-parent = <&msix>; // ranges: // - ECAM - non prefetchable config space Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -79,6 +79,7 @@ arm/annapurna/alpine/alpine_ccu.c optional al_ccu arm/annapurna/alpine/alpine_nb_service.c optional al_nb_service arm/annapurna/alpine/alpine_pci.c optional al_pci +arm/annapurna/alpine/alpine_pci_msix.c optional al_pci cam/cam.c optional scbus cam/cam_compat.c optional scbus cam/cam_iosched.c optional scbus