root/sys/arch/xen/xen/pciback.c
/*      $NetBSD: pciback.c,v 1.24 2026/03/10 21:59:29 andvar Exp $      */

/*
 * Copyright (c) 2009 Manuel Bouyer.
 *
 * 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 ``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 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pciback.c,v 1.24 2026/03/10 21:59:29 andvar Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/queue.h>

#include <uvm/uvm_extern.h>

#include <machine/bus_private.h>

#include <dev/isa/isareg.h>

#include <xen/hypervisor.h>
#include <xen/evtchn.h>
#include <xen/granttables.h>
#include <xen/xen_shm.h>
#include <xen/include/public/io/pciif.h>
#include <xen/xenbus.h>

#include <sys/stat.h>
#include <sys/dirent.h>
#include <miscfs/specfs/specdev.h>
#include <miscfs/kernfs/kernfs.h>
#include <xen/kernfs_machdep.h>

#include "locators.h"

#include <dev/pci/pcivar.h>
#include <machine/i82093var.h>

struct pciback_pci_softc;
struct pb_xenbus_instance;
/* list of devices we handle */
struct pciback_pci_dev {
        SLIST_ENTRY(pciback_pci_dev) pb_devlist_next; /* global list of pbd */
        SLIST_ENTRY(pciback_pci_dev) pb_guest_next; /* per-guest list of pbd */
        u_int pb_bus; /* our location */
        u_int pb_device;
        u_int pb_function;
        pci_chipset_tag_t pb_pc;
        pcitag_t pb_tag;
        struct pciback_pci_softc *pb_pci_softc;
        struct pb_xenbus_instance *pbx_instance;
};

/* list of devices we want to match */
static SLIST_HEAD(pciback_pci_devlist, pciback_pci_dev)
    pciback_pci_devlist_head  =
    SLIST_HEAD_INITIALIZER(pciback_pci_devlist_head);
        
/* PCI-related functions and definitions  */

#define PCI_NBARS       ((PCI_MAPREG_END - PCI_MAPREG_START) / 4)

struct pciback_pci_softc {
        device_t sc_dev;
        void *sc_ih; /* our interrupt; */
        struct pciback_pci_dev *sc_pb; /* our location */
        struct pci_bar {
                bus_space_tag_t b_t;
                bus_space_handle_t b_h;
                bus_addr_t b_addr;
                bus_size_t b_size;
                int b_type;
                int b_valid;
        } sc_bars[PCI_NBARS];
        pci_intr_handle_t sc_intrhandle;
        int  sc_irq;
        pcireg_t sc_id;
        pcireg_t sc_subid;
        char sc_kernfsname[16];
};

int pciback_pci_match(device_t, cfdata_t, void *);
void pciback_pci_attach(device_t, device_t, void *);
static struct pciback_pci_dev* pciback_pci_lookup(u_int, u_int, u_int);
static void pciback_pci_init(void);

static int  pciback_parse_pci(const char *, u_int *, u_int *, u_int *);

/* kernfs-related functions and definitions */

static kernfs_parentdir_t *pciback_kern_pkt;
static int  pciback_kernfs_read(void *);

#define DIR_MODE        (S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#define FILE_MODE       (S_IRUSR)
static const struct kernfs_fileop pciback_dev_fileops[] = {
    { .kf_fileop = KERNFS_FILEOP_READ, .kf_vop = pciback_kernfs_read },
};

/* xenbus-related functions and definitions */

static int  pciback_xenbus_create(struct xenbus_device *);
static int  pciback_xenbus_destroy(void *);
static void pciback_xenbus_frontend_changed(void *, XenbusState);
static bool pbxif_lookup(domid_t);
static void pciback_xenbus_export_device(struct pb_xenbus_instance *, char *);
static void pciback_xenbus_export_roots(struct pb_xenbus_instance *);

static int  pciback_xenbus_evthandler(void *);

/* emulate byte and word access to PCI config space */
static inline u_int8_t
pciback_read_byte(pci_chipset_tag_t pc, pcitag_t pa, int reg)
{

        return (pci_conf_read(pc, pa, (reg & ~0x03)) >>
            ((reg & 0x03) * 8) & 0xff);
}

static inline u_int16_t
pciback_read_word(pci_chipset_tag_t pc, pcitag_t pa, int reg)
{
        return (pci_conf_read(pc, pa, (reg & ~0x03)) >>
            ((reg & 0x03) * 8) & 0xffff);
}

static inline void
pciback_write_byte(pci_chipset_tag_t pc, pcitag_t pa, int reg, uint8_t val)
{
        pcireg_t pcival;

        pcival = pci_conf_read(pc, pa, (reg & ~0x03));
        pcival &= ~(0xff << ((reg & 0x03) * 8));
        pcival |= (val << ((reg & 0x03) * 8));
        pci_conf_write(pc, pa, (reg & ~0x03), pcival);
}

static inline void
pciback_write_word(pci_chipset_tag_t pc, pcitag_t pa, int reg, uint16_t val)
{
        pcireg_t pcival;

        pcival = pci_conf_read(pc, pa, (reg & ~0x03));
        pcival &= ~(0xffff << ((reg & 0x03) * 8));
        pcival |= (val << ((reg & 0x03) * 8));
        pci_conf_write(pc, pa, (reg & ~0x03), pcival);
}



CFATTACH_DECL_NEW(pciback, sizeof(struct pciback_pci_softc),
    pciback_pci_match, pciback_pci_attach, NULL, NULL);

static int pciback_pci_inited = 0;

/* a xenbus PCI backend instance */
struct pb_xenbus_instance {
        SLIST_ENTRY(pb_xenbus_instance) pbx_next; /* list of backend instances*/
        struct xenbus_device *pbx_xbusd;
        domid_t pbx_domid;
        struct pciback_pci_devlist pbx_pb_pci_dev; /* list of exported PCI devices */
        /* communication with the domU */
        unsigned int pbx_evtchn; /* our even channel */
        struct intrhand *pbx_ih;
        struct xen_pci_sharedinfo *pbx_sh_info;
        struct xen_pci_op op;
        grant_handle_t pbx_shinfo_handle; /* to unmap shared page */
};

static SLIST_HEAD(, pb_xenbus_instance) pb_xenbus_instances;
static kmutex_t pb_xenbus_lock;

static struct xenbus_backend_driver pci_backend_driver = {
        .xbakd_create = pciback_xenbus_create,
        .xbakd_type = "pci"
};

int
pciback_pci_match(device_t parent, cfdata_t match, void *aux)
{
        struct pci_attach_args *pa = aux;
        if (pciback_pci_inited == 0) {
                pciback_pci_init();
                pciback_pci_inited = 1;
        }
        if (pciback_pci_lookup(pa->pa_bus, pa->pa_device, pa->pa_function))
                return 500; /* we really want to take over anything else */
        return 0;
}

void
pciback_pci_attach(device_t parent, device_t self, void *aux)
{
        struct pci_attach_args *pa = aux;
        struct pciback_pci_softc *sc = device_private(self);
        char devinfo[256];
        int i;
        const char *intrstr;
        kernfs_entry_t *dkt;
        kfstype kfst;
        pcireg_t reg;
        char buf[PCI_INTRSTR_LEN];

        sc->sc_dev = self;
        sc->sc_pb = pciback_pci_lookup(pa->pa_bus, pa->pa_device, pa->pa_function);
        if (sc->sc_pb == NULL)
                panic("pciback_pci_attach: pciback_lookup");
        pci_devinfo(pa->pa_id, pa->pa_class, 0, devinfo, sizeof(devinfo));
        aprint_normal(": %s (rev. 0x%02x)\n", devinfo,
            PCI_REVISION(pa->pa_class));
        sc->sc_pb->pb_pci_softc = sc;
        sc->sc_pb->pb_pc = pa->pa_pc;
        sc->sc_pb->pb_tag = pa->pa_tag;

        sc->sc_id = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_ID_REG);
        sc->sc_subid = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);

        for (i = 0; i < PCI_NBARS;) {
                sc->sc_bars[i].b_type = pci_mapreg_type(pa->pa_pc, pa->pa_tag,
                    PCI_MAPREG_START + i * 4);
                if (pci_mapreg_map(pa, PCI_MAPREG_START + i * 4,
                    sc->sc_bars[i].b_type, 0,
                    &sc->sc_bars[i].b_t, &sc->sc_bars[i].b_h,
                    &sc->sc_bars[i].b_addr, &sc->sc_bars[i].b_size) == 0)
                        sc->sc_bars[i].b_valid = 1;
                if (sc->sc_bars[i].b_valid) {
                        aprint_verbose_dev(self, "%s: 0x%08jx - 0x%08jx\n",
                            (sc->sc_bars[i].b_type == PCI_MAPREG_TYPE_IO) ?
                            "I/O" : "mem",
                            (uintmax_t)sc->sc_bars[i].b_addr,
                            (uintmax_t)sc->sc_bars[i].b_size);
                }
                            
                if (sc->sc_bars[i].b_type ==
                    (PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_64BIT))
                        i += 2;
                else
                        i += 1;
        }
        /* map the irq so interrupt routing is done */
        if (pci_intr_map(pa, &sc->sc_intrhandle) != 0) {
                aprint_error_dev(self, "couldn't map interrupt\n");
        } else {
                intrstr = pci_intr_string(pa->pa_pc, sc->sc_intrhandle,
                    buf, sizeof(buf));
                aprint_normal_dev(self, "interrupting at %s\n", intrstr);
        }
        unbind_pirq_from_evtch(APIC_IRQ_LEGACY_IRQ(sc->sc_intrhandle));
        sc->sc_irq = APIC_IRQ_LEGACY_IRQ(sc->sc_intrhandle);
        /* XXX should be done elsewhere ? */
        reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_INTERRUPT_REG);
        reg &= ~ (PCI_INTERRUPT_LINE_MASK << PCI_INTERRUPT_LINE_SHIFT);
        reg |= (sc->sc_irq << PCI_INTERRUPT_LINE_SHIFT);
        pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_INTERRUPT_REG, reg);
        printf("irq line %d pin %d sc %p\n",
            PCI_INTERRUPT_LINE(pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_INTERRUPT_REG)),
            PCI_INTERRUPT_PIN(pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_INTERRUPT_REG)), sc);
        /*
         * don't establish the interrupt, we're not interested in
         * getting it here. 
         */
        /* publish the informations about this device to /kern/xen/pci */
        snprintf(sc->sc_kernfsname, sizeof(sc->sc_kernfsname),
            "0000:%02x:%02x.%x", pa->pa_bus, pa->pa_device, pa->pa_function);
        kfst = KERNFS_ALLOCTYPE(pciback_dev_fileops);
        KERNFS_ALLOCENTRY(dkt, KM_SLEEP);
        KERNFS_INITENTRY(dkt, DT_REG, sc->sc_kernfsname, sc,
            kfst, VREG, FILE_MODE);
        kernfs_addentry(pciback_kern_pkt, dkt);
}

static int
pciback_kernfs_read(void *v)
{
        struct vop_read_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                struct ucred *a_cred;
        } */ *ap = v;
        struct kernfs_node *kfs = VTOKERN(ap->a_vp);
        struct uio *uio = ap->a_uio;
        struct pciback_pci_softc *sc = kfs->kfs_kt->kt_data;
#define PCIBACK_KERNFS_SIZE 512
        static char buf[PCIBACK_KERNFS_SIZE];
        off_t off;
        off_t len;
        int error, i;

        off = uio->uio_offset;
        len = 0;
        len += snprintf(&buf[len], sizeof(buf) - len,
            "vendor: 0x%04x\nproduct: 0x%04x\n",
            PCI_VENDOR(sc->sc_id), PCI_PRODUCT(sc->sc_id));
        if (len > sizeof(buf))
                return ENOSPC;
        len += snprintf(&buf[len], sizeof(buf) - len,
            "subsys_vendor: 0x%04x\nsubsys_product: 0x%04x\n",
            PCI_VENDOR(sc->sc_subid), PCI_PRODUCT(sc->sc_subid));
        if (len > sizeof(buf))
                return ENOSPC;
        for(i = 0; i < PCI_NBARS; i++) {
                if (sc->sc_bars[i].b_valid) {
                        len += snprintf(&buf[len], sizeof(buf) - len,
                            "%s: 0x%08jx - 0x%08jx\n",
                            (sc->sc_bars[i].b_type == PCI_MAPREG_TYPE_IO) ?
                            "I/O" : "mem",
                            (uintmax_t)sc->sc_bars[i].b_addr,
                            (uintmax_t)(sc->sc_bars[i].b_addr + sc->sc_bars[i].b_size));
                        if (len > sizeof(buf))
                                return ENOSPC;
                }
        }
        if (len > sizeof(buf))
                return ENOSPC;
        len += snprintf(&buf[len], sizeof(buf) - len,
            "irq: %d\n", sc->sc_irq);
        if (off >= len) {
                error = uiomove(buf, 0, uio);
        } else {
                error = uiomove(&buf[off], len - off, uio);
        }
        return error;
}

static struct pciback_pci_dev*
pciback_pci_lookup(u_int bus, u_int dev, u_int func)
{
        struct pciback_pci_dev *pbd;
        /* Safe without lock, only written during init */
        SLIST_FOREACH(pbd, &pciback_pci_devlist_head, pb_devlist_next) {
                if (pbd->pb_bus == bus &&
                    pbd->pb_device == dev &&
                    pbd->pb_function == func)
                        return pbd;
        }
        return NULL;
}

static void
pciback_pci_init(void)
{
        union xen_cmdline_parseinfo xi;
        char *pcidevs, *c;
        u_int bus, dev, func;
        struct pciback_pci_dev *pb;
        kernfs_entry_t *dkt;

        memset(&xi, 0, sizeof(xi));
        xen_parse_cmdline(XEN_PARSE_PCIBACK, &xi);
        if (strlen(xi.xcp_pcidevs) == 0)
                return;
        pcidevs = xi.xcp_pcidevs;
        for (pcidevs = xi.xcp_pcidevs; *pcidevs != '\0';) {
                if (*pcidevs != '(')
                        goto error;
                pcidevs++;
                /* parse location */
                c = strchr(pcidevs, ')');
                if (c == NULL)
                        goto error;
                *c = '\0';
                if (pciback_parse_pci(pcidevs, &bus, &dev, &func) == 0) {
                        pb = kmem_zalloc(sizeof(*pb), KM_SLEEP);
                        pb->pb_bus = bus;
                        pb->pb_device = dev;
                        pb->pb_function = func;
                        aprint_verbose("pciback_pci_init: hide claim device "
                            "%x:%x:%x\n", bus, dev, func);
                        SLIST_INSERT_HEAD(&pciback_pci_devlist_head, pb,
                            pb_devlist_next);
                }
                pcidevs = c + 1;
        }

        SLIST_INIT(&pb_xenbus_instances);
        mutex_init(&pb_xenbus_lock, MUTEX_DEFAULT, IPL_NONE);

        xenbus_backend_register(&pci_backend_driver);

        KERNFS_ALLOCENTRY(dkt, KM_SLEEP);
        KERNFS_INITENTRY(dkt, DT_DIR, "pci", NULL, KFSsubdir, VDIR, DIR_MODE);
        kernfs_addentry(kernxen_pkt, dkt);
        pciback_kern_pkt = KERNFS_ENTOPARENTDIR(dkt);
        return;
error:
        aprint_error("pciback_pci_init: syntax error at %s\n", pcidevs);
        return;
}

static int
pciback_parse_pci(const char *str, u_int *busp, u_int *devp, u_int *funcp)
{
        char *c;

        /* parse location */
        c = strchr(str, ':');
        if (c == NULL)
                goto error;
        if (strncmp("0000", str, 4) == 0) {
                /* must be domain number, get next */
                str = c + 1;
        }
        *busp = strtoul(str, &c, 16);
        if (*c != ':')
                goto error;
        str = c + 1;
        *devp = strtoul(str, &c, 16);
        if (*c != '.')
                goto error;
        str = c + 1;
        *funcp = strtoul(str, &c, 16);
        if (*c != '\0')
                goto error;
        return 0;
error:
        aprint_error("pciback_pci_init: syntax error at char %c\n", *c);
        return EINVAL;
}

static int
pciback_xenbus_create(struct xenbus_device *xbusd)
{
        struct pb_xenbus_instance *pbxi;
        long domid;
        char path[10];
        int i, err;
        u_long num_devs;

        if ((err = xenbus_read_ul(NULL, xbusd->xbusd_path,
            "frontend-id", &domid, 10)) != 0) {
                aprint_error("pciback: can' read %s/frontend-id: %d\n",
                    xbusd->xbusd_path, err);
                return err;
        }

        if (pbxif_lookup(domid)) {
                return EEXIST;
        }
        pbxi = kmem_zalloc(sizeof(*pbxi), KM_SLEEP);
        pbxi->pbx_domid = domid;

        xbusd->xbusd_u.b.b_cookie = pbxi;
        xbusd->xbusd_u.b.b_detach = pciback_xenbus_destroy;
        pbxi->pbx_xbusd = xbusd;

        SLIST_INIT(&pbxi->pbx_pb_pci_dev);

        mutex_enter(&pb_xenbus_lock);
        SLIST_INSERT_HEAD(&pb_xenbus_instances, pbxi, pbx_next);
        mutex_exit(&pb_xenbus_lock);

        xbusd->xbusd_otherend_changed = pciback_xenbus_frontend_changed;

        err = xenbus_switch_state(xbusd, NULL, XenbusStateInitWait);
        if (err) {
                printf("failed to switch state on %s: %d\n",
                    xbusd->xbusd_path, err);
                goto fail;
        }
        if ((err = xenbus_read_ul(NULL, xbusd->xbusd_path, "num_devs",
            &num_devs, 10)) != 0) {
                aprint_error("pciback: can' read %s/num_devs %d\n",
                    xbusd->xbusd_path, err);
                goto fail;
        }
        for (i = 0; i < num_devs; i++) {
                char dev[64];
                snprintf(path, sizeof(path), "dev-%d", i);
                if ((err = xenbus_read(NULL, xbusd->xbusd_path, path,
                    dev, sizeof(dev))) != 0) {
                        aprint_error("pciback: can' read %s/%s: %d\n",
                            xbusd->xbusd_path, path, err);
                        goto fail;
                }
                pciback_xenbus_export_device(pbxi, dev);
        }
        pciback_xenbus_export_roots(pbxi);
        if ((err = xenbus_switch_state(xbusd, NULL, XenbusStateInitialised))) {
                printf("failed to switch state on %s: %d\n",
                    xbusd->xbusd_path, err);
                goto fail;
        }

        return 0;
fail:
        kmem_free(pbxi, sizeof(*pbxi));
        return err;
}

static int
pciback_xenbus_destroy(void *arg)
{
        struct pb_xenbus_instance *pbxi = arg;
        struct pciback_pci_dev *pbd;

        hypervisor_mask_event(pbxi->pbx_evtchn);
        xen_intr_disestablish(pbxi->pbx_ih);
        mutex_enter(&pb_xenbus_lock);
        SLIST_REMOVE(&pb_xenbus_instances,
            pbxi, pb_xenbus_instance, pbx_next);
        mutex_exit(&pb_xenbus_lock);


        if (pbxi->pbx_sh_info) {
                xen_shm_unmap((vaddr_t)pbxi->pbx_sh_info, 1,
                    &pbxi->pbx_shinfo_handle);
                uvm_km_free(kernel_map, (vaddr_t)pbxi->pbx_sh_info,
                    PAGE_SIZE, UVM_KMF_VAONLY);
        }
        SLIST_FOREACH(pbd, &pbxi->pbx_pb_pci_dev, pb_guest_next) {
                pbd->pbx_instance = NULL;
        }
        kmem_free(pbxi, sizeof(*pbxi));
        return 0;
}

static void
pciback_xenbus_frontend_changed(void *arg, XenbusState new_state)
{
        struct pb_xenbus_instance *pbxi = arg;
        struct xenbus_device *xbusd = pbxi->pbx_xbusd;
        int err;
        evtchn_op_t evop;
        u_long shared_ref;
        grant_ref_t gshared_ref;
        u_long revtchn;

        /* do it only once */
        if (xenbus_read_driver_state(xbusd->xbusd_path) !=
            XenbusStateInitialised)
                return;
                
        switch(new_state) {
        case XenbusStateInitialising:
        case XenbusStateConnected:
                break;
        case XenbusStateInitialised:
                /* read communication informations */
                err = xenbus_read_ul(NULL, xbusd->xbusd_otherend,
                    "pci-op-ref", &shared_ref, 10);
                if (err) {
                        xenbus_dev_fatal(xbusd, err, "reading %s/pci-op-ref",
                            xbusd->xbusd_otherend);
                        break;
                }
                err = xenbus_read_ul(NULL, xbusd->xbusd_otherend,
                    "event-channel", &revtchn, 10);
                if (err) {
                        xenbus_dev_fatal(xbusd, err, "reading %s/event-channel",
                            xbusd->xbusd_otherend);
                        break;
                }
                /* allocate VA space and map rings */
                pbxi->pbx_sh_info = (void *)uvm_km_alloc(kernel_map,
                    PAGE_SIZE, 0, UVM_KMF_VAONLY);
                if (pbxi->pbx_sh_info == 0) {
                        xenbus_dev_fatal(xbusd, ENOMEM,
                            "can't get VA for shared infos",
                            xbusd->xbusd_otherend);
                        break;
                }
                gshared_ref = shared_ref;
                if (xen_shm_map(1, pbxi->pbx_domid, &gshared_ref,
                    (vaddr_t)pbxi->pbx_sh_info,
                    &pbxi->pbx_shinfo_handle, 0) != 0) {
                        aprint_error("pciback: can't map shared grant ref\n");
                        goto err2;
                }

                evop.cmd = EVTCHNOP_bind_interdomain;
                evop.u.bind_interdomain.remote_dom = pbxi->pbx_domid;
                evop.u.bind_interdomain.remote_port = revtchn;
                err = HYPERVISOR_event_channel_op(&evop);
                if (err) {
                        printf("pciback: can't get event channel: %d\n", err);
                        goto err1;
                }
                pbxi->pbx_evtchn = evop.u.bind_interdomain.local_port;
                x86_sfence();
                xenbus_switch_state(xbusd, NULL, XenbusStateConnected);
                x86_sfence();
                pbxi->pbx_ih = xen_intr_establish_xname(-1, &xen_pic, pbxi->pbx_evtchn,
                    IST_LEVEL, IPL_BIO, pciback_xenbus_evthandler, pbxi, true, "pciback");
                KASSERT(pbxi->pbx_ih != NULL);
                hypervisor_unmask_event(pbxi->pbx_evtchn);
                hypervisor_notify_via_evtchn(pbxi->pbx_evtchn);
                break;

        case XenbusStateClosing:
                xenbus_switch_state(xbusd, NULL, XenbusStateClosing);
                break;

        case XenbusStateClosed:
                /* otherend_changed() should handle it for us */
                panic("pciback_xenbus_frontend_changed: closed\n");
        case XenbusStateUnknown:      
        case XenbusStateInitWait:
        default:
                aprint_error("pciback: invalid frontend state %d\n",
                    new_state);
                break;
        }
        return;
err1:
        xen_shm_unmap((vaddr_t)pbxi->pbx_sh_info, 1, &pbxi->pbx_shinfo_handle);
err2:
        uvm_km_free(kernel_map, (vaddr_t)pbxi->pbx_sh_info,
            PAGE_SIZE, UVM_KMF_VAONLY);
}

/* lookup a pbxi based on domain id and interface handle */
static bool
pbxif_lookup(domid_t dom)
{
        struct pb_xenbus_instance *pbxi;
        bool found = false;

        mutex_enter(&pb_xenbus_lock);
        SLIST_FOREACH(pbxi, &pb_xenbus_instances, pbx_next) {
                if (pbxi->pbx_domid == dom) {
                        found = true;
                        break;
                }
        }
        mutex_exit(&pb_xenbus_lock);

        return found;
}

static void
pciback_xenbus_export_device(struct pb_xenbus_instance *pbxi, char *val)
{
        u_int bus, dev, func;
        struct pciback_pci_dev *pbd;
        if (pciback_parse_pci(val, &bus, &dev, &func)) {
                aprint_error("pciback: can't parse %s\n", val);
                return;
        }
        pbd = pciback_pci_lookup(bus, dev, func);
        if (pbd == NULL) {
                aprint_error("pciback: can't locate 0x%02x:0x%02x.0x%02x\n",
                    bus, dev, func);
                return;
        }
        if (pbd->pb_pci_softc == NULL) {
                aprint_error("pciback: 0x%02x:0x%02x.0x%02x not detected\n",
                    bus, dev, func);
                return;
        }
        pbd->pbx_instance = pbxi;
        SLIST_INSERT_HEAD(&pbxi->pbx_pb_pci_dev, pbd, pb_guest_next);
        return;
}

static void
pciback_xenbus_export_roots(struct pb_xenbus_instance *pbxi)
{
        char bus[256];
        char root[10];
        struct pciback_pci_dev *pbd;
        int num_roots = 0;
        int err;

        memset(bus, 0, sizeof(bus));

        SLIST_FOREACH(pbd, &pbxi->pbx_pb_pci_dev, pb_guest_next) {
                if (bus[pbd->pb_bus] == 0) {
                        /* not published yet */
                        snprintf(root, sizeof(root), "root-%d", num_roots);
                        err = xenbus_printf(NULL,
                            pbxi->pbx_xbusd->xbusd_path, 
                            root, "0000:%02x", pbd->pb_bus);
                        if (err) {
                                aprint_error("pciback: can't write to %s/%s: "
                                    "%d\n", pbxi->pbx_xbusd->xbusd_path, root,
                                    err);
                        }
                        num_roots++;
                        bus[pbd->pb_bus]++;
                }
        }
        err = xenbus_printf(NULL, pbxi->pbx_xbusd->xbusd_path, "root_num",
            "%d", num_roots);
        if (err) {
                aprint_error("pciback: can't write to %s/root_num: "
                    "%d\n", pbxi->pbx_xbusd->xbusd_path, err);
        }
}

static int
pciback_xenbus_evthandler(void * arg)
{
        struct pb_xenbus_instance *pbxi = arg;
        struct pciback_pci_dev *pbd;
        struct xen_pci_op *op = &pbxi->op;
        u_int bus, dev, func;

        hypervisor_clear_event(pbxi->pbx_evtchn);
        if (xen_atomic_test_bit(&pbxi->pbx_sh_info->flags,
            _XEN_PCIF_active) == 0)
                return 0;

        memcpy(op, &pbxi->pbx_sh_info->op, sizeof (struct xen_pci_op));
        __insn_barrier();
        if (op->domain != 0) {
                aprint_error("pciback: domain %d != 0", op->domain);
                op->err = XEN_PCI_ERR_dev_not_found;
                goto end;
        }
        bus = op->bus;
        dev = (op->devfn >> 3) & 0xff;
        func = (op->devfn) & 0x7;
        SLIST_FOREACH(pbd, &pbxi->pbx_pb_pci_dev, pb_guest_next) {
                if (pbd->pb_bus == bus &&
                    pbd->pb_device == dev &&
                    pbd->pb_function == func)
                        break;
        }
        if (pbd == NULL) {
                aprint_debug("pciback: %02x:%02x.%x not found\n",
                    bus, dev, func);
                op->err = XEN_PCI_ERR_dev_not_found;
                goto end;
        }
        switch(op->cmd) {
        case XEN_PCI_OP_conf_read:
                op->err = XEN_PCI_ERR_success;
                switch (op->size) {
                case 1:
                        op->value = pciback_read_byte(pbd->pb_pc, pbd->pb_tag,
                            op->offset);
                        break;
                case 2:
                        op->value = pciback_read_word(pbd->pb_pc, pbd->pb_tag,
                            op->offset);
                        break;
                case 4:
                        op->value = pci_conf_read(pbd->pb_pc, pbd->pb_tag,
                            op->offset);
                        break;
                default:
                        aprint_error("pciback: bad size %d\n", op->size);
                        break;
                }
                break;
        case XEN_PCI_OP_conf_write:
                op->err = XEN_PCI_ERR_success;
                switch(op->size) {
                case 1:
                        pciback_write_byte(pbd->pb_pc, pbd->pb_tag,
                            op->offset, op->value);
                        break;
                case 2:
                        pciback_write_word(pbd->pb_pc, pbd->pb_tag,
                            op->offset, op->value);
                        break;
                case 4:
                        pci_conf_write(pbd->pb_pc, pbd->pb_tag,
                            op->offset, op->value);
                        break;
                default:
                        aprint_error("pciback: bad size %d\n", op->size);
                        op->err = XEN_PCI_ERR_invalid_offset;
                        break;
                }
                break;
        default:
                aprint_error("pciback: unknown cmd %d\n", op->cmd);
                op->err = XEN_PCI_ERR_not_implemented;
        }
        pbxi->pbx_sh_info->op.value = op->value;
        pbxi->pbx_sh_info->op.err = op->err;
end:
        xen_atomic_clear_bit(&pbxi->pbx_sh_info->flags, _XEN_PCIF_active);
        hypervisor_notify_via_evtchn(pbxi->pbx_evtchn);
        return 1;
}