/* Minimal PCI driver for Mini-OS. * Copyright (c) 2007-2008 Samuel Thibault. * Based on blkfront.c. */ #include #include #include #include #include #include #include #include #include #include #include #define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) DECLARE_WAIT_QUEUE_HEAD(pcifront_queue); static struct pcifront_dev *pcidev; struct pcifront_dev { domid_t dom; struct xen_pci_sharedinfo *info; grant_ref_t info_ref; evtchn_port_t evtchn; char *nodename; char *backend; xenbus_event_queue events; }; void pcifront_handler(evtchn_port_t port, struct pt_regs *regs, void *data) { wake_up(&pcifront_queue); } static void free_pcifront(struct pcifront_dev *dev) { if (!dev) dev = pcidev; mask_evtchn(dev->evtchn); gnttab_end_access(dev->info_ref); free_page(dev->info); unbind_evtchn(dev->evtchn); free(dev->backend); free(dev->nodename); free(dev); } void pcifront_watches(void *opaque) { XenbusState state; char *err = NULL, *msg = NULL; char *be_path, *be_state; char* nodename = opaque ? opaque : "device/pci/0"; char path[strlen(nodename) + 9]; char fe_state[strlen(nodename) + 7]; xenbus_event_queue events = NULL; snprintf(path, sizeof(path), "%s/backend", nodename); snprintf(fe_state, sizeof(fe_state), "%s/state", nodename); while (1) { printk("pcifront_watches: waiting for backend path to appear %s\n", path); xenbus_watch_path_token(XBT_NIL, path, path, &events); while ((err = xenbus_read(XBT_NIL, path, &be_path)) != NULL) { free(err); xenbus_wait_for_watch(&events); } xenbus_unwatch_path_token(XBT_NIL, path, path); printk("pcifront_watches: waiting for backend to get into the right state %s\n", be_path); be_state = (char *) malloc(strlen(be_path) + 7); snprintf(be_state, strlen(be_path) + 7, "%s/state", be_path); xenbus_watch_path_token(XBT_NIL, be_state, be_state, &events); while ((err = xenbus_read(XBT_NIL, be_state, &msg)) != NULL || msg[0] > '4') { free(msg); free(err); xenbus_wait_for_watch(&events); } xenbus_unwatch_path_token(XBT_NIL, be_state, be_state); if (init_pcifront(NULL) == NULL) { free(be_state); free(be_path); continue; } xenbus_watch_path_token(XBT_NIL, be_state, be_state, &events); state = XenbusStateConnected; printk("pcifront_watches: waiting for backend events %s\n", be_state); while ((err = xenbus_wait_for_state_change(be_state, &state, &events)) == NULL && (err = xenbus_read(XBT_NIL, pcidev->backend, &msg)) == NULL) { free(msg); printk("pcifront_watches: backend state changed: %s %d\n", be_state, state); if (state == XenbusStateReconfiguring) { printk("pcifront_watches: writing %s %d\n", fe_state, XenbusStateReconfiguring); if ((err = xenbus_switch_state(XBT_NIL, fe_state, XenbusStateReconfiguring)) != NULL) { printk("pcifront_watches: error changing state to %d: %s\n", XenbusStateReconfiguring, err); if (!strcmp(err, "ENOENT")) { xenbus_write(XBT_NIL, fe_state, "7"); free(err); } } } else if (state == XenbusStateReconfigured) { printk("pcifront_watches: writing %s %d\n", fe_state, XenbusStateConnected); printk("pcifront_watches: changing state to %d\n", XenbusStateConnected); if ((err = xenbus_switch_state(XBT_NIL, fe_state, XenbusStateConnected)) != NULL) { printk("pcifront_watches: error changing state to %d: %s\n", XenbusStateConnected, err); if (!strcmp(err, "ENOENT")) { xenbus_write(XBT_NIL, fe_state, "4"); free(err); } } } else if (state == XenbusStateClosing) break; } if (err) { printk("pcifront_watches: done waiting err=%s\n", err); free(err); } else printk("pcifront_watches: done waiting\n"); err = xenbus_unwatch_path_token(XBT_NIL, be_state, be_state); shutdown_pcifront(pcidev); free(be_state); free(be_path); free(err); pcidev = NULL; } xenbus_unwatch_path_token(XBT_NIL, path, path); } struct pcifront_dev *init_pcifront(char *_nodename) { xenbus_transaction_t xbt; char* err; char* message=NULL; int retry=0; char* msg = NULL; char* nodename = _nodename ? _nodename : "device/pci/0"; int dom; struct pcifront_dev *dev; char path[strlen(nodename) + strlen("/backend-id") + 1]; if (!_nodename && pcidev) return pcidev; printk("******************* PCIFRONT for %s **********\n\n\n", nodename); snprintf(path, sizeof(path), "%s/backend-id", nodename); dom = xenbus_read_integer(path); if (dom == -1) { printk("no backend\n"); return NULL; } dev = malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); dev->nodename = strdup(nodename); dev->dom = dom; evtchn_alloc_unbound(dev->dom, pcifront_handler, dev, &dev->evtchn); dev->info = (struct xen_pci_sharedinfo*) alloc_page(); memset(dev->info,0,PAGE_SIZE); dev->info_ref = gnttab_grant_access(dev->dom,virt_to_mfn(dev->info),0); dev->events = NULL; again: err = xenbus_transaction_start(&xbt); if (err) { printk("starting transaction\n"); free(err); } err = xenbus_printf(xbt, nodename, "pci-op-ref","%u", dev->info_ref); if (err) { message = "writing pci-op-ref"; goto abort_transaction; } err = xenbus_printf(xbt, nodename, "event-channel", "%u", dev->evtchn); if (err) { message = "writing event-channel"; goto abort_transaction; } err = xenbus_printf(xbt, nodename, "magic", XEN_PCI_MAGIC); if (err) { message = "writing magic"; goto abort_transaction; } snprintf(path, sizeof(path), "%s/state", nodename); err = xenbus_switch_state(xbt, path, XenbusStateInitialised); if (err) { message = "switching state"; goto abort_transaction; } err = xenbus_transaction_end(xbt, 0, &retry); if (err) free(err); if (retry) { goto again; printk("completing transaction\n"); } goto done; abort_transaction: free(err); err = xenbus_transaction_end(xbt, 1, &retry); printk("Abort transaction %s\n", message); goto error; done: snprintf(path, sizeof(path), "%s/backend", nodename); msg = xenbus_read(XBT_NIL, path, &dev->backend); if (msg) { printk("Error %s when reading the backend path %s\n", msg, path); goto error; } printk("backend at %s\n", dev->backend); { char path[strlen(dev->backend) + strlen("/state") + 1]; char frontpath[strlen(nodename) + strlen("/state") + 1]; XenbusState state; snprintf(path, sizeof(path), "%s/state", dev->backend); xenbus_watch_path_token(XBT_NIL, path, path, &dev->events); err = NULL; state = xenbus_read_integer(path); while (err == NULL && state < XenbusStateConnected) err = xenbus_wait_for_state_change(path, &state, &dev->events); if (state != XenbusStateConnected) { printk("backend not avalable, state=%d\n", state); if (err) free(err); err = xenbus_unwatch_path_token(XBT_NIL, path, path); goto error; } snprintf(frontpath, sizeof(frontpath), "%s/state", nodename); if ((err = xenbus_switch_state(XBT_NIL, frontpath, XenbusStateConnected)) != NULL) { printk("error switching state %s\n", err); free(err); err = xenbus_unwatch_path_token(XBT_NIL, path, path); goto error; } } unmask_evtchn(dev->evtchn); printk("**************************\n"); if (!_nodename) pcidev = dev; return dev; error: free(msg); free(err); free_pcifront(dev); return NULL; } void pcifront_scan(struct pcifront_dev *dev, void (*func)(unsigned int domain, unsigned int bus, unsigned slot, unsigned int fun)) { char *path; int i, n, len; char *s, *msg = NULL; unsigned int domain, bus, slot, fun; if (!dev) dev = pcidev; if (!dev) { printk("pcifront_scan: device or bus\n"); return; } len = strlen(dev->backend) + 1 + 5 + 10 + 1; path = (char *) malloc(len); snprintf(path, len, "%s/num_devs", dev->backend); n = xenbus_read_integer(path); for (i = 0; i < n; i++) { snprintf(path, len, "%s/dev-%d", dev->backend, i); msg = xenbus_read(XBT_NIL, path, &s); if (msg) { printk("Error %s when reading the PCI root name at %s\n", msg, path); free(msg); continue; } if (sscanf(s, "%x:%x:%x.%x", &domain, &bus, &slot, &fun) != 4) { printk("\"%s\" does not look like a PCI device address\n", s); free(s); continue; } free(s); if (func) func(domain, bus, slot, fun); } free(path); } void shutdown_pcifront(struct pcifront_dev *dev) { char* err = NULL, *err2; XenbusState state; char path[strlen(dev->backend) + strlen("/state") + 1]; char nodename[strlen(dev->nodename) + strlen("/event-channel") + 1]; printk("close pci: backend at %s\n",dev->backend); snprintf(path, sizeof(path), "%s/state", dev->backend); snprintf(nodename, sizeof(nodename), "%s/state", dev->nodename); if ((err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosing)) != NULL) { printk("shutdown_pcifront: error changing state to %d: %s\n", XenbusStateClosing, err); goto close_pcifront; } state = xenbus_read_integer(path); while (err == NULL && state < XenbusStateClosing) err = xenbus_wait_for_state_change(path, &state, &dev->events); if (err) free(err); if ((err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosed)) != NULL) { printk("shutdown_pcifront: error changing state to %d: %s\n", XenbusStateClosed, err); goto close_pcifront; } state = xenbus_read_integer(path); while (state < XenbusStateClosed) { err = xenbus_wait_for_state_change(path, &state, &dev->events); free(err); } if ((err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateInitialising)) != NULL) { printk("shutdown_pcifront: error changing state to %d: %s\n", XenbusStateInitialising, err); goto close_pcifront; } state = xenbus_read_integer(path); while (err == NULL && (state < XenbusStateInitWait || state >= XenbusStateClosed)) err = xenbus_wait_for_state_change(path, &state, &dev->events); close_pcifront: if (err) free(err); err2 = xenbus_unwatch_path_token(XBT_NIL, path, path); if (err2) free(err2); snprintf(nodename, sizeof(nodename), "%s/info-ref", dev->nodename); err2 = xenbus_rm(XBT_NIL, nodename); if (err2) free(err2); snprintf(nodename, sizeof(nodename), "%s/event-channel", dev->nodename); err2 = xenbus_rm(XBT_NIL, nodename); if (err2) free(err2); if (!err) free_pcifront(dev); } int pcifront_physical_to_virtual (struct pcifront_dev *dev, unsigned int *dom, unsigned int *bus, unsigned int *slot, unsigned long *fun) { /* FIXME: the buffer sizing is a little lazy here. 10 extra bytes should be enough to hold the paths we need to construct, even if the number of devices is large */ char path[strlen(dev->backend) + strlen("/num_devs") + 10 + 1]; int i, n; char *s, *msg = NULL; unsigned int dom1, bus1, slot1, fun1; if (!dev) dev = pcidev; snprintf(path, sizeof(path), "%s/num_devs", dev->backend); n = xenbus_read_integer(path); for (i = 0; i < n; i++) { snprintf(path, sizeof(path), "%s/dev-%d", dev->backend, i); msg = xenbus_read(XBT_NIL, path, &s); if (msg) { printk("Error %s when reading the PCI root name at %s\n", msg, path); free(msg); continue; } if (sscanf(s, "%x:%x:%x.%x", &dom1, &bus1, &slot1, &fun1) != 4) { printk("\"%s\" does not look like a PCI device address\n", s); free(s); continue; } free(s); if (dom1 == *dom && bus1 == *bus && slot1 == *slot && fun1 == *fun) { snprintf(path, sizeof(path), "%s/vdev-%d", dev->backend, i); msg = xenbus_read(XBT_NIL, path, &s); if (msg) { printk("Error %s when reading the PCI root name at %s\n", msg, path); continue; } if (sscanf(s, "%x:%x:%x.%x", dom, bus, slot, fun) != 4) { printk("\"%s\" does not look like a PCI device address\n", s); free(s); continue; } free(s); return 0; } } return -1; } void pcifront_op(struct pcifront_dev *dev, struct xen_pci_op *op) { if (!dev) dev = pcidev; dev->info->op = *op; /* Make sure info is written before the flag */ wmb(); set_bit(_XEN_PCIF_active, (void*) &dev->info->flags); notify_remote_via_evtchn(dev->evtchn); wait_event(pcifront_queue, !test_bit(_XEN_PCIF_active, (void*) &dev->info->flags)); /* Make sure flag is read before info */ rmb(); *op = dev->info->op; } int pcifront_conf_read(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun, unsigned int off, unsigned int size, unsigned int *val) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_conf_read; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); op.offset = off; op.size = size; pcifront_op(dev, &op); if (op.err) return op.err; *val = op.value; return 0; } int pcifront_conf_write(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun, unsigned int off, unsigned int size, unsigned int val) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_conf_write; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); op.offset = off; op.size = size; op.value = val; pcifront_op(dev, &op); return op.err; } int pcifront_enable_msi(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_enable_msi; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); pcifront_op(dev, &op); if (op.err) return op.err; else return op.value; } int pcifront_disable_msi(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_disable_msi; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); pcifront_op(dev, &op); return op.err; } int pcifront_enable_msix(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun, struct xen_msix_entry *entries, int n) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; if (n > SH_INFO_MAX_VEC) return XEN_PCI_ERR_op_failed; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_enable_msix; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); op.value = n; memcpy(op.msix_entries, entries, n * sizeof(*entries)); pcifront_op(dev, &op); if (op.err) return op.err; memcpy(entries, op.msix_entries, n * sizeof(*entries)); return 0; } int pcifront_disable_msix(struct pcifront_dev *dev, unsigned int dom, unsigned int bus, unsigned int slot, unsigned long fun) { struct xen_pci_op op; if (!dev) dev = pcidev; if (pcifront_physical_to_virtual(dev, &dom, &bus, &slot, &fun) < 0) return XEN_PCI_ERR_dev_not_found; memset(&op, 0, sizeof(op)); op.cmd = XEN_PCI_OP_disable_msix; op.domain = dom; op.bus = bus; op.devfn = PCI_DEVFN(slot, fun); pcifront_op(dev, &op); return op.err; }