/*      $NetBSD: pciback.c,v 1.23 2022/09/01 15:33:23 bouyer 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.23 2022/09/01 15:33:23 bouyer 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 comunication 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;
}
