make msi interrupts available to child devices in a pci card driver

From: John Hay <john_at_sanren.ac.za>
Date: Wed, 27 Sep 2023 14:11:44 UTC
Hi,

I would like to know how to make msi interrupts available to child devices
in a pci card driver?

I'm writing a driver for the OCP TimeCard. Apart from the timers, it also
has 16650 UARTs and IIC devices. All of them are memory mapped in a single
BAR. It also has 32 MSI interrupts, with each "function/device" connected
to a specific MSI interrupt.

I would like to add a child device for each of these "functions" and let
each of them have their own interrupt, but I'm struggling to figure out the
correct way to make the interrupts available to the child device.

The memory mapping, using rman and creating subregions for the different
devices is working and I can get a UART device connected in polled mode.
The basic process in the card's attach() is much like what the puc(4)
driver does (leaving error checking etc. out):

<snip>
        bar->b_rid = PCIR_BAR(0);
        bar->b_type = SYS_RES_MEMORY;
        bar->b_res = bus_alloc_resource_any(sc->sc_dev, bar->b_type,
&bar->b_rid, RF_ACTIVE);
        sc->sc_iomem.rm_type = RMAN_ARRAY;
        error = rman_init(&sc->sc_iomem);
        error = rman_manage_region(&sc->sc_iomem, start, end);
        for (idx = 0; idx < nports; idx++) {
                port->p_rres = rman_reserve_resource(&sc->sc_iomem, start,
end, size, 0, NULL);
                bsh = rman_get_bushandle(bar->b_res);
                bst = rman_get_bustag(bar->b_res);
                port->p_rres = bus_space_subregion(bst, bsh, ofs, size,
&bsh);
                rman_set_bushandle(port->p_rres, bsh);
                rman_set_bustag(port->p_rres, bst);
                port->p_dev = device_add_child(dev, NULL, -1);
                error = device_probe_and_attach(port->p_dev);
</snip>

I can also allocate MSI interrupts for the driver itself as a test, with no
problem:

<snip>
        sc->sc_msi =32;
        pci_alloc_msi(dev, &sc->sc_msi);
        printf("msi irqs %d\n", sc->sc_msi);
        for (idx = 0; idx < sc->sc_msi; idx++) {
                iport->pp_irid = idx + 1;
                iport->pp_ires = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&iport->pp_irid, RF_ACTIVE);
                bus_setup_intr(dev, iport->pp_ires, INTR_TYPE_TTY |
INTR_MPSAFE, timecard_intr, NULL, iport, &iport->p_ihandle);
        error = pci_enable_busmaster(dev);
</snip>

But I have not been able to find a way, similar to the memory to make it
available to a child device, that works. It might be because the child is
not a direct child of the pci driver.

One hackish way that I could get to work was if I used my dev instead of
the child's inside the timecard_bus_alloc_resource() and
timecard_bus_setup_intr() methods, something like this:

<snip>
static struct resource *
timecard_bus_release_resource(device_t dev, device_t child, int type, int
rid, struct resource *res)
{
if (type == SYS_RES_IRQ)
        return BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, type, rid,
res);
}
static int
timecard_bus_setup_intr(device_t dev, device_t child, struct resource *res,
    int flags, driver_filter_t *filt, void (*ihand)(void *), void *arg,
void **cookiep)
{
      return bus_setup_intr(dev, res, flags, filt, ihand, arg, cookiep);
}
</snip>

That works, but does not feel quite right and then the interrupt is not
"owned" by the child, but by the timecard driver:
<snip>
# vmstat -ia | grep timecard
irq41: timecard0                10408538        175
</snip>

Regards

John