/* $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 __KERNEL_RCSID(0, "$NetBSD: pciback.c,v 1.23 2022/09/01 15:33:23 bouyer Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "locators.h" #include #include 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; }