Author: mmel
Date: Fri Aug 19 10:53:17 2016
New Revision: 304460
URL: https://svnweb.freebsd.org/changeset/base/304460

Log:
  TEGRA: Implement MSI/MSIX interrupts for pcie controller.

Modified:
  head/sys/arm/nvidia/tegra_pcie.c

Modified: head/sys/arm/nvidia/tegra_pcie.c
==============================================================================
--- head/sys/arm/nvidia/tegra_pcie.c    Fri Aug 19 10:52:39 2016        
(r304459)
+++ head/sys/arm/nvidia/tegra_pcie.c    Fri Aug 19 10:53:17 2016        
(r304460)
@@ -33,20 +33,20 @@ __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/devmap.h>
+#include <sys/proc.h>
 #include <sys/kernel.h>
-#include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/module.h>
 #include <sys/mutex.h>
-#include <sys/queue.h>
-#include <sys/bus.h>
 #include <sys/rman.h>
-#include <sys/endian.h>
-#include <sys/devmap.h>
 
 #include <machine/intr.h>
 
 #include <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
 #include <vm/pmap.h>
 
 #include <dev/extres/clk/clk.h>
@@ -68,8 +68,9 @@ __FBSDID("$FreeBSD$");
 #include <arm/nvidia/tegra_pmc.h>
 
 #include "ofw_bus_if.h"
+#include "msi_if.h"
 #include "pcib_if.h"
-
+#include "pic_if.h"
 
 
 #define        AFI_AXI_BAR0_SZ                         0x000
@@ -93,17 +94,10 @@ __FBSDID("$FreeBSD$");
 #define        AFI_MSI_BAR_SZ                          0x060
 #define        AFI_MSI_FPCI_BAR_ST                     0x064
 #define        AFI_MSI_AXI_BAR_ST                      0x068
-
-
-#define        AFI_AXI_BAR6_SZ                         0x134
-#define        AFI_AXI_BAR7_SZ                         0x138
-#define        AFI_AXI_BAR8_SZ                         0x13c
-#define        AFI_AXI_BAR6_START                      0x140
-#define        AFI_AXI_BAR7_START                      0x144
-#define        AFI_AXI_BAR8_START                      0x148
-#define        AFI_FPCI_BAR6                           0x14c
-#define        AFI_FPCI_BAR7                           0x150
-#define        AFI_FPCI_BAR8                           0x154
+#define AFI_MSI_VEC(x)                         (0x06c + 4 * (x))
+#define AFI_MSI_EN_VEC(x)                      (0x08c + 4 * (x))
+#define         AFI_MSI_INTR_IN_REG                            32
+#define         AFI_MSI_REGS                                   8
 
 #define        AFI_CONFIGURATION                       0x0ac
 #define         AFI_CONFIGURATION_EN_FPCI                      (1 << 0)
@@ -209,6 +203,8 @@ __FBSDID("$FreeBSD$");
 
 #define        TEGRA_PCIE_LINKUP_TIMEOUT       200
 
+#define TEGRA_PCIB_MSI_ENABLE
+
 #define        DEBUG
 #ifdef DEBUG
 #define        debugf(fmt, args...) do { printf(fmt,##args); } while (0)
@@ -258,6 +254,13 @@ static struct ofw_compat_data compat_dat
        {NULL,                          0},
 };
 
+#define        TEGRA_FLAG_MSI_USED     0x0001
+struct tegra_pcib_irqsrc {
+       struct intr_irqsrc      isrc;
+       u_int                   irq;
+       u_int                   flags;
+};
+
 struct tegra_pcib_port {
        int             enabled;
        int             port_idx;               /* chip port index */
@@ -271,6 +274,7 @@ struct tegra_pcib_port {
 };
 
 #define        TEGRA_PCIB_MAX_PORTS    3
+#define        TEGRA_PCIB_MAX_MSI      AFI_MSI_INTR_IN_REG * AFI_MSI_REGS
 struct tegra_pcib_softc {
        struct ofw_pci_softc    ofw_pci;
        device_t                dev;
@@ -303,7 +307,7 @@ struct tegra_pcib_softc {
        regulator_t             supply_vddio_pex_ctl;
        regulator_t             supply_avdd_pll_erefe;
 
-       uint32_t                msi_bitmap;
+       vm_offset_t             msi_page;       /* VA of MSI page */
        bus_addr_t              cfg_base_addr;  /* base address of config */
        bus_size_t              cfg_cur_offs;   /* currently mapped window */
        bus_space_handle_t      cfg_handle;     /* handle of config window */
@@ -311,9 +315,9 @@ struct tegra_pcib_softc {
        int                     lanes_cfg;
        int                     num_ports;
        struct tegra_pcib_port *ports[TEGRA_PCIB_MAX_PORTS];
+       struct tegra_pcib_irqsrc *isrcs;
 };
 
-
 static int
 tegra_pcib_maxslots(device_t dev)
 {
@@ -324,13 +328,15 @@ static int
 tegra_pcib_route_interrupt(device_t bus, device_t dev, int pin)
 {
        struct tegra_pcib_softc *sc;
+       u_int irq;
 
        sc = device_get_softc(bus);
-       device_printf(bus, "route pin %d for device %d.%d to %ju\n",
+       irq = intr_map_clone_irq(rman_get_start(sc->irq_res));
+       device_printf(bus, "route pin %d for device %d.%d to %u\n",
                      pin, pci_get_slot(dev), pci_get_function(dev),
-                     rman_get_start(sc->irq_res));
+                     irq);
 
-       return (rman_get_start(sc->irq_res));
+       return (irq);
 }
 
 static int
@@ -471,84 +477,320 @@ static int tegra_pci_intr(void *arg)
        return (FILTER_HANDLED);
 }
 
-#if defined(TEGRA_PCI_MSI)
+/* -----------------------------------------------------------------------
+ *
+ *     PCI MSI interface
+ */
+static int
+tegra_pcib_alloc_msi(device_t pci, device_t child, int count, int maxcount,
+    int *irqs)
+{
+       phandle_t msi_parent;
+
+       /* XXXX ofw_bus_msimap() don't works for Tegra DT.
+       ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), &msi_parent,
+           NULL);
+       */
+       msi_parent = OF_xref_from_node(ofw_bus_get_node(pci));
+       return (intr_alloc_msi(pci, child, msi_parent, count, maxcount,
+           irqs));
+}
+
 static int
-tegra_pcib_map_msi(device_t dev, device_t child, int irq, uint64_t *addr,
+tegra_pcib_release_msi(device_t pci, device_t child, int count, int *irqs)
+{
+       phandle_t msi_parent;
+
+       /* XXXX ofw_bus_msimap() don't works for Tegra DT.
+       ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), &msi_parent,
+           NULL);
+       */
+       msi_parent = OF_xref_from_node(ofw_bus_get_node(pci));
+       return (intr_release_msi(pci, child, msi_parent, count, irqs));
+}
+
+static int
+tegra_pcib_map_msi(device_t pci, device_t child, int irq, uint64_t *addr,
     uint32_t *data)
 {
+       phandle_t msi_parent;
+
+       /* XXXX ofw_bus_msimap() don't works for Tegra DT.
+       ofw_bus_msimap(ofw_bus_get_node(pci), pci_get_rid(child), &msi_parent,
+           NULL);
+       */
+       msi_parent = OF_xref_from_node(ofw_bus_get_node(pci));
+       return (intr_map_msi(pci, child, msi_parent, irq, addr, data));
+}
+
+#ifdef TEGRA_PCIB_MSI_ENABLE
+
+/* --------------------------------------------------------------------------
+ *
+ * Interrupts
+ *
+ */
+
+static inline void
+tegra_pcib_isrc_mask(struct tegra_pcib_softc *sc,
+     struct tegra_pcib_irqsrc *tgi, uint32_t val)
+{
+       uint32_t reg;
+       int offs, bit;
+
+       offs = tgi->irq / AFI_MSI_INTR_IN_REG;
+       bit = 1 << (tgi->irq % AFI_MSI_INTR_IN_REG);
+
+       if (val != 0)
+               AFI_WR4(sc, AFI_MSI_VEC(offs), bit);
+       reg = AFI_RD4(sc, AFI_MSI_EN_VEC(offs));
+       if (val !=  0)
+               reg |= bit;
+       else
+               reg &= ~bit;
+       AFI_WR4(sc, AFI_MSI_EN_VEC(offs), reg);
+}
+
+static int
+tegra_pcib_msi_intr(void *arg)
+{
+       u_int irq, i, bit, reg;
        struct tegra_pcib_softc *sc;
+       struct trapframe *tf;
+       struct tegra_pcib_irqsrc *tgi;
 
-       sc = device_get_softc(dev);
-       irq = irq - MSI_IRQ;
+       sc = (struct tegra_pcib_softc *)arg;
+       tf = curthread->td_intr_frame;
+
+       for (i = 0; i < AFI_MSI_REGS; i++) {
+               reg = AFI_RD4(sc, AFI_MSI_VEC(i));
+               /* Handle one vector. */
+               while (reg != 0) {
+                       bit = ffs(reg) - 1;
+//printf("%s: i: %d, reg: 0x%08X, bit: 0x%08X, addr: 0x%08llX\n", __func__, i, 
reg, bit, rman_get_start(sc->afi_mem_res));
+                       /* Send EOI */
+                       AFI_WR4(sc, AFI_MSI_VEC(i), 1 << bit);
+                       irq = i * AFI_MSI_INTR_IN_REG + bit;
+                       tgi = &sc->isrcs[irq];
+                       if (intr_isrc_dispatch(&tgi->isrc, tf) != 0) {
+                               /* Disable stray. */
+                               tegra_pcib_isrc_mask(sc, tgi, 0);
+                               device_printf(sc->dev,
+                                   "Stray irq %u disabled\n", irq);
+                       }
+                       reg = AFI_RD4(sc, AFI_MSI_VEC(i));
+               }
+       }
+       return (FILTER_HANDLED);
+}
 
-       /* validate parameters */
-       if (isclr(&sc->msi_bitmap, irq)) {
-               device_printf(dev, "invalid MSI 0x%x\n", irq);
-               return (EINVAL);
+static int
+tegra_pcib_msi_attach(struct tegra_pcib_softc *sc)
+{
+       int error;
+       uint32_t irq;
+       const char *name;
+
+       sc->isrcs = malloc(sizeof(*sc->isrcs) * TEGRA_PCIB_MAX_MSI, M_DEVBUF,
+           M_WAITOK | M_ZERO);
+
+       name = device_get_nameunit(sc->dev);
+       for (irq = 0; irq < TEGRA_PCIB_MAX_MSI; irq++) {
+               sc->isrcs[irq].irq = irq;
+               error = intr_isrc_register(&sc->isrcs[irq].isrc,
+                   sc->dev, 0, "%s,%u", name, irq);
+               if (error != 0)
+                       return (error); /* XXX deregister ISRCs */
        }
+       if (intr_msi_register(sc->dev,
+           OF_xref_from_node(ofw_bus_get_node(sc->dev))) != 0)
+               return (ENXIO);
+
+       return (0);
+}
+
+static int
+tegra_pcib_msi_detach(struct tegra_pcib_softc *sc)
+{
+
+       /*
+        *  There has not been established any procedure yet
+        *  how to detach PIC from living system correctly.
+        */
+       device_printf(sc->dev, "%s: not implemented yet\n", __func__);
+       return (EBUSY);
+}
+
+
+static void
+tegra_pcib_msi_disable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+       struct tegra_pcib_softc *sc;
+       struct tegra_pcib_irqsrc *tgi;
+
+       sc = device_get_softc(dev);
+       tgi = (struct tegra_pcib_irqsrc *)isrc;
+       tegra_pcib_isrc_mask(sc, tgi, 0);
+}
+
+static void
+tegra_pcib_msi_enable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+       struct tegra_pcib_softc *sc;
+       struct tegra_pcib_irqsrc *tgi;
+
+       sc = device_get_softc(dev);
+       tgi = (struct tegra_pcib_irqsrc *)isrc;
+       tegra_pcib_isrc_mask(sc, tgi, 1);
+}
+
+/* MSI interrupts are edge trigered -> do nothing */
+static void
+tegra_pcib_msi_post_filter(device_t dev, struct intr_irqsrc *isrc)
+{
+}
+
+static void
+tegra_pcib_msi_post_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+}
+
+static void
+tegra_pcib_msi_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+}
+
+static int
+tegra_pcib_msi_setup_intr(device_t dev, struct intr_irqsrc *isrc,
+    struct resource *res, struct intr_map_data *data)
+{
+       struct tegra_pcib_softc *sc;
+       struct tegra_pcib_irqsrc *tgi;
 
-       tegra_msi_data(irq, addr, data);
+       sc = device_get_softc(dev);
+       tgi = (struct tegra_pcib_irqsrc *)isrc;
 
-       debugf("%s: irq: %d addr: %jx data: %x\n",
-           __func__, irq, *addr, *data);
+       if (data == NULL || data->type != INTR_MAP_DATA_MSI)
+               return (ENOTSUP);
+
+       if (isrc->isrc_handlers == 0)
+               tegra_pcib_msi_enable_intr(dev, isrc);
 
        return (0);
 }
 
 static int
-tegra_pcib_alloc_msi(device_t dev, device_t child, int count,
-    int maxcount __unused, int *irqs)
+tegra_pcib_msi_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
+    struct resource *res, struct intr_map_data *data)
 {
        struct tegra_pcib_softc *sc;
-       u_int start = 0, i;
+       struct tegra_pcib_irqsrc *tgi;
 
-       if (powerof2(count) == 0 || count > MSI_IRQ_NUM)
-               return (EINVAL);
+       sc = device_get_softc(dev);
+       tgi = (struct tegra_pcib_irqsrc *)isrc;
+
+       if (isrc->isrc_handlers == 0)
+               tegra_pcib_isrc_mask(sc, tgi, 0);
+       return (0);
+}
+
+
+static int
+tegra_pcib_msi_alloc_msi(device_t dev, device_t child, int count, int maxcount,
+    device_t *pic, struct intr_irqsrc **srcs)
+{
+       struct tegra_pcib_softc *sc;
+       int i, irq, end_irq;
+       bool found;
+
+       KASSERT(powerof2(count), ("%s: bad count", __func__));
+       KASSERT(powerof2(maxcount), ("%s: bad maxcount", __func__));
 
        sc = device_get_softc(dev);
        mtx_lock(&sc->mtx);
 
-       for (start = 0; (start + count) < MSI_IRQ_NUM; start++) {
-               for (i = start; i < start + count; i++) {
-                       if (isset(&sc->msi_bitmap, i))
+       found = false;
+       for (irq = 0; irq < TEGRA_PCIB_MAX_MSI && !found; irq++) {
+               /* Start on an aligned interrupt */
+               if ((irq & (maxcount - 1)) != 0)
+                       continue;
+
+               /* Assume we found a valid range until shown otherwise */
+               found = true;
+
+               /* Check this range is valid */
+               for (end_irq = irq; end_irq != irq + count - 1; end_irq++) {
+                       /* No free interrupts */
+                       if (end_irq == (TEGRA_PCIB_MAX_MSI - 1)) {
+                               found = false;
+                               break;
+                       }
+
+                       /* This is already used */
+                       if ((sc->isrcs[irq].flags & TEGRA_FLAG_MSI_USED) ==
+                           TEGRA_FLAG_MSI_USED) {
+                               found = false;
                                break;
+                       }
                }
-               if (i == start + count)
-                       break;
        }
 
-       if ((start + count) == MSI_IRQ_NUM) {
+       /* Not enough interrupts were found */
+       if (!found || irq == (TEGRA_PCIB_MAX_MSI - 1)) {
                mtx_unlock(&sc->mtx);
                return (ENXIO);
        }
 
-       for (i = start; i < start + count; i++) {
-               setbit(&sc->msi_bitmap, i);
-               irqs[i] = MSI_IRQ + i;
-       }
-       debugf("%s: start: %x count: %x\n", __func__, start, count);
+       for (i = 0; i < count; i++) {
+               /* Mark the interrupt as used */
+               sc->isrcs[irq + i].flags |= TEGRA_FLAG_MSI_USED;
 
+       }
        mtx_unlock(&sc->mtx);
+
+       for (i = 0; i < count; i++)
+               srcs[i] = (struct intr_irqsrc *)&sc->isrcs[irq + i];
+       *pic = device_get_parent(dev);
        return (0);
 }
 
 static int
-tegra_pcib_release_msi(device_t dev, device_t child, int count, int *irqs)
+tegra_pcib_msi_release_msi(device_t dev, device_t child, int count,
+    struct intr_irqsrc **isrc)
 {
        struct tegra_pcib_softc *sc;
-       u_int i;
+       struct tegra_pcib_irqsrc *ti;
+       int i;
 
        sc = device_get_softc(dev);
        mtx_lock(&sc->mtx);
+       for (i = 0; i < count; i++) {
+               ti = (struct tegra_pcib_irqsrc *)isrc;
 
-       for (i = 0; i < count; i++)
-               clrbit(&sc->msi_bitmap, irqs[i] - MSI_IRQ);
+               KASSERT((ti->flags & TEGRA_FLAG_MSI_USED) == 
TEGRA_FLAG_MSI_USED,
+                   ("%s: Trying to release an unused MSI-X interrupt",
+                   __func__));
 
-       mtx_unlock(&sc->mtx);
+               ti->flags &= ~TEGRA_FLAG_MSI_USED;
+               mtx_unlock(&sc->mtx);
+       }
+       return (0);
+}
+
+static int
+tegra_pcib_msi_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
+    uint64_t *addr, uint32_t *data)
+{
+       struct tegra_pcib_softc *sc = device_get_softc(dev);
+       struct tegra_pcib_irqsrc *ti = (struct tegra_pcib_irqsrc *)isrc;
+
+       *addr = vtophys(sc->msi_page);
+       *data = ti->irq;
        return (0);
 }
 #endif
 
+/* ------------------------------------------------------------------- */
 static bus_size_t
 tegra_pcib_pex_ctrl(struct tegra_pcib_softc *sc, int port)
 {
@@ -1137,6 +1379,52 @@ tegra_pcib_enable(struct tegra_pcib_soft
        return(0);
 }
 
+#ifdef TEGRA_PCIB_MSI_ENABLE
+static int
+tegra_pcib_attach_msi(device_t dev)
+{
+       struct tegra_pcib_softc *sc;
+       uint32_t reg;
+       int i, rv;
+
+       sc = device_get_softc(dev);
+
+       sc->msi_page = kmem_alloc_contig(kernel_arena, PAGE_SIZE, M_WAITOK,
+           0, BUS_SPACE_MAXADDR, PAGE_SIZE, 0, VM_MEMATTR_DEFAULT);
+
+       /* MSI BAR */
+       tegra_pcib_set_bar(sc, 9, vtophys(sc->msi_page), vtophys(sc->msi_page),
+           PAGE_SIZE, 0);
+
+       /* Disble and clear all interrupts. */
+       for (i = 0; i < AFI_MSI_REGS; i++) {
+               AFI_WR4(sc, AFI_MSI_EN_VEC(i), 0);
+               AFI_WR4(sc, AFI_MSI_VEC(i), 0xFFFFFFFF);
+       }
+       rv = bus_setup_intr(dev, sc->msi_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+           tegra_pcib_msi_intr, NULL, sc, &sc->msi_intr_cookie);
+       if (rv != 0) {
+               device_printf(dev, "cannot setup MSI interrupt handler\n");
+               rv = ENXIO;
+               goto out;
+       }
+
+       if (tegra_pcib_msi_attach(sc) != 0) {
+               device_printf(dev, "WARNING: unable to attach PIC\n");
+               tegra_pcib_msi_detach(sc);
+               goto out;
+       }
+
+       /* Unmask  MSI interrupt. */
+       reg = AFI_RD4(sc, AFI_INTR_MASK);
+       reg |= AFI_INTR_MASK_MSI_MASK;
+       AFI_WR4(sc, AFI_INTR_MASK, reg);
+
+out:
+       return (rv);
+}
+#endif
+
 static int
 tegra_pcib_probe(device_t dev)
 {
@@ -1254,7 +1542,7 @@ tegra_pcib_attach(device_t dev)
                goto out;
 
        if (bus_setup_intr(dev, sc->irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
-                          tegra_pci_intr, NULL, sc, &sc->intr_cookie)) {
+                   tegra_pci_intr, NULL, sc, &sc->intr_cookie)) {
                device_printf(dev, "cannot setup interrupt handler\n");
                rv = ENXIO;
                goto out;
@@ -1275,6 +1563,11 @@ tegra_pcib_attach(device_t dev)
                        tegra_pcib_port_disable(sc, i);
        }
 
+#ifdef TEGRA_PCIB_MSI_ENABLE
+       rv = tegra_pcib_attach_msi(dev);
+       if (rv != 0)
+                goto out;
+#endif
        device_add_child(dev, "pci", -1);
 
        return (bus_generic_attach(dev));
@@ -1299,11 +1592,24 @@ static device_method_t tegra_pcib_method
        DEVMETHOD(pcib_read_config,             tegra_pcib_read_config),
        DEVMETHOD(pcib_write_config,            tegra_pcib_write_config),
        DEVMETHOD(pcib_route_interrupt,         tegra_pcib_route_interrupt),
-
-#if defined(TEGRA_PCI_MSI)
        DEVMETHOD(pcib_alloc_msi,               tegra_pcib_alloc_msi),
        DEVMETHOD(pcib_release_msi,             tegra_pcib_release_msi),
        DEVMETHOD(pcib_map_msi,                 tegra_pcib_map_msi),
+
+#ifdef TEGRA_PCIB_MSI_ENABLE
+       /* MSI/MSI-X */
+       DEVMETHOD(msi_alloc_msi,                tegra_pcib_msi_alloc_msi),
+       DEVMETHOD(msi_release_msi,              tegra_pcib_msi_release_msi),
+       DEVMETHOD(msi_map_msi,                  tegra_pcib_msi_map_msi),
+
+       /* Interrupt controller interface */
+       DEVMETHOD(pic_disable_intr,             tegra_pcib_msi_disable_intr),
+       DEVMETHOD(pic_enable_intr,              tegra_pcib_msi_enable_intr),
+       DEVMETHOD(pic_setup_intr,               tegra_pcib_msi_setup_intr),
+       DEVMETHOD(pic_teardown_intr,            tegra_pcib_msi_teardown_intr),
+       DEVMETHOD(pic_post_filter,              tegra_pcib_msi_post_filter),
+       DEVMETHOD(pic_post_ithread,             tegra_pcib_msi_post_ithread),
+       DEVMETHOD(pic_pre_ithread,              tegra_pcib_msi_pre_ithread),
 #endif
 
        /* OFW bus interface */
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to