/* * (c) 2017 Stefano Stabellini * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #define PVCALLS_VERSIONS "1" #define MAX_RING_ORDER XENBUS_MAX_RING_GRANT_ORDER struct pvcalls_back_global { struct list_head frontends; struct semaphore frontends_lock; } pvcalls_back_global; /* * Per-frontend data structure. It contains pointers to the command * ring, its event channel, a list of active sockets and a tree of * passive sockets. */ struct pvcalls_fedata { struct list_head list; struct xenbus_device *dev; struct xen_pvcalls_sring *sring; struct xen_pvcalls_back_ring ring; int irq; struct list_head socket_mappings; struct radix_tree_root socketpass_mappings; struct semaphore socket_lock; }; static int pvcalls_back_socket(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_connect(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_release(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_bind(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_listen(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_accept(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_poll(struct xenbus_device *dev, struct xen_pvcalls_request *req) { return 0; } static int pvcalls_back_handle_cmd(struct xenbus_device *dev, struct xen_pvcalls_request *req) { int ret = 0; switch (req->cmd) { case PVCALLS_SOCKET: ret = pvcalls_back_socket(dev, req); break; case PVCALLS_CONNECT: ret = pvcalls_back_connect(dev, req); break; case PVCALLS_RELEASE: ret = pvcalls_back_release(dev, req); break; case PVCALLS_BIND: ret = pvcalls_back_bind(dev, req); break; case PVCALLS_LISTEN: ret = pvcalls_back_listen(dev, req); break; case PVCALLS_ACCEPT: ret = pvcalls_back_accept(dev, req); break; case PVCALLS_POLL: ret = pvcalls_back_poll(dev, req); break; default: { struct pvcalls_fedata *fedata; struct xen_pvcalls_response *rsp; fedata = dev_get_drvdata(&dev->dev); rsp = RING_GET_RESPONSE( &fedata->ring, fedata->ring.rsp_prod_pvt++); rsp->req_id = req->req_id; rsp->cmd = req->cmd; rsp->ret = -ENOTSUPP; break; } } return ret; } static void pvcalls_back_work(struct pvcalls_fedata *fedata) { int notify, notify_all = 0, more = 1; struct xen_pvcalls_request req; struct xenbus_device *dev = fedata->dev; while (more) { while (RING_HAS_UNCONSUMED_REQUESTS(&fedata->ring)) { RING_COPY_REQUEST(&fedata->ring, fedata->ring.req_cons++, &req); if (!pvcalls_back_handle_cmd(dev, &req)) { RING_PUSH_RESPONSES_AND_CHECK_NOTIFY( &fedata->ring, notify); notify_all += notify; } } if (notify_all) { notify_remote_via_irq(fedata->irq); notify_all = 0; } RING_FINAL_CHECK_FOR_REQUESTS(&fedata->ring, more); } } static irqreturn_t pvcalls_back_event(int irq, void *dev_id) { struct xenbus_device *dev = dev_id; struct pvcalls_fedata *fedata = NULL; if (dev == NULL) return IRQ_HANDLED; fedata = dev_get_drvdata(&dev->dev); if (fedata == NULL) return IRQ_HANDLED; pvcalls_back_work(fedata); return IRQ_HANDLED; } static int backend_connect(struct xenbus_device *dev) { int err, evtchn; grant_ref_t ring_ref; struct pvcalls_fedata *fedata = NULL; fedata = kzalloc(sizeof(struct pvcalls_fedata), GFP_KERNEL); if (!fedata) return -ENOMEM; fedata->irq = -1; err = xenbus_scanf(XBT_NIL, dev->otherend, "port", "%u", &evtchn); if (err != 1) { err = -EINVAL; xenbus_dev_fatal(dev, err, "reading %s/event-channel", dev->otherend); goto error; } err = xenbus_scanf(XBT_NIL, dev->otherend, "ring-ref", "%u", &ring_ref); if (err != 1) { err = -EINVAL; xenbus_dev_fatal(dev, err, "reading %s/ring-ref", dev->otherend); goto error; } err = bind_interdomain_evtchn_to_irq(dev->otherend_id, evtchn); if (err < 0) goto error; fedata->irq = err; err = request_threaded_irq(fedata->irq, NULL, pvcalls_back_event, IRQF_ONESHOT, "pvcalls-back", dev); if (err < 0) goto error; err = xenbus_map_ring_valloc(dev, &ring_ref, 1, (void **)&fedata->sring); if (err < 0) goto error; BACK_RING_INIT(&fedata->ring, fedata->sring, XEN_PAGE_SIZE * 1); fedata->dev = dev; INIT_LIST_HEAD(&fedata->socket_mappings); INIT_RADIX_TREE(&fedata->socketpass_mappings, GFP_KERNEL); sema_init(&fedata->socket_lock, 1); dev_set_drvdata(&dev->dev, fedata); down(&pvcalls_back_global.frontends_lock); list_add_tail(&fedata->list, &pvcalls_back_global.frontends); up(&pvcalls_back_global.frontends_lock); return 0; error: if (fedata->irq >= 0) unbind_from_irqhandler(fedata->irq, dev); if (fedata->sring != NULL) xenbus_unmap_ring_vfree(dev, fedata->sring); kfree(fedata); return err; } static int backend_disconnect(struct xenbus_device *dev) { return 0; } static int pvcalls_back_probe(struct xenbus_device *dev, const struct xenbus_device_id *id) { int err, abort; struct xenbus_transaction xbt; again: abort = 1; err = xenbus_transaction_start(&xbt); if (err) { pr_warn("%s cannot create xenstore transaction\n", __func__); return err; } err = xenbus_printf(xbt, dev->nodename, "versions", "%s", PVCALLS_VERSIONS); if (err) { pr_warn("%s write out 'versions' failed\n", __func__); goto abort; } err = xenbus_printf(xbt, dev->nodename, "max-page-order", "%u", MAX_RING_ORDER); if (err) { pr_warn("%s write out 'max-page-order' failed\n", __func__); goto abort; } err = xenbus_printf(xbt, dev->nodename, "function-calls", XENBUS_FUNCTIONS_CALLS); if (err) { pr_warn("%s write out 'function-calls' failed\n", __func__); goto abort; } abort = 0; abort: err = xenbus_transaction_end(xbt, abort); if (err) { if (err == -EAGAIN && !abort) goto again; pr_warn("%s cannot complete xenstore transaction\n", __func__); return err; } if (abort) return -EFAULT; xenbus_switch_state(dev, XenbusStateInitWait); return 0; } static void set_backend_state(struct xenbus_device *dev, enum xenbus_state state) { while (dev->state != state) { switch (dev->state) { case XenbusStateClosed: switch (state) { case XenbusStateInitWait: case XenbusStateConnected: xenbus_switch_state(dev, XenbusStateInitWait); break; case XenbusStateClosing: xenbus_switch_state(dev, XenbusStateClosing); break; default: __WARN(); } break; case XenbusStateInitWait: case XenbusStateInitialised: switch (state) { case XenbusStateConnected: backend_connect(dev); xenbus_switch_state(dev, XenbusStateConnected); break; case XenbusStateClosing: case XenbusStateClosed: xenbus_switch_state(dev, XenbusStateClosing); break; default: __WARN(); } break; case XenbusStateConnected: switch (state) { case XenbusStateInitWait: case XenbusStateClosing: case XenbusStateClosed: down(&pvcalls_back_global.frontends_lock); backend_disconnect(dev); up(&pvcalls_back_global.frontends_lock); xenbus_switch_state(dev, XenbusStateClosing); break; default: __WARN(); } break; case XenbusStateClosing: switch (state) { case XenbusStateInitWait: case XenbusStateConnected: case XenbusStateClosed: xenbus_switch_state(dev, XenbusStateClosed); break; default: __WARN(); } break; default: __WARN(); } } } static void pvcalls_back_changed(struct xenbus_device *dev, enum xenbus_state frontend_state) { switch (frontend_state) { case XenbusStateInitialising: set_backend_state(dev, XenbusStateInitWait); break; case XenbusStateInitialised: case XenbusStateConnected: set_backend_state(dev, XenbusStateConnected); break; case XenbusStateClosing: set_backend_state(dev, XenbusStateClosing); break; case XenbusStateClosed: set_backend_state(dev, XenbusStateClosed); if (xenbus_dev_is_online(dev)) break; device_unregister(&dev->dev); break; case XenbusStateUnknown: set_backend_state(dev, XenbusStateClosed); device_unregister(&dev->dev); break; default: xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", frontend_state); break; } } static int pvcalls_back_remove(struct xenbus_device *dev) { return 0; } static int pvcalls_back_uevent(struct xenbus_device *xdev, struct kobj_uevent_env *env) { return 0; } static const struct xenbus_device_id pvcalls_back_ids[] = { { "pvcalls" }, { "" } }; static struct xenbus_driver pvcalls_back_driver = { .ids = pvcalls_back_ids, .probe = pvcalls_back_probe, .remove = pvcalls_back_remove, .uevent = pvcalls_back_uevent, .otherend_changed = pvcalls_back_changed, }; static int __init pvcalls_back_init(void) { int ret; if (!xen_domain()) return -ENODEV; ret = xenbus_register_backend(&pvcalls_back_driver); if (ret < 0) return ret; sema_init(&pvcalls_back_global.frontends_lock, 1); INIT_LIST_HEAD(&pvcalls_back_global.frontends); return 0; } module_init(pvcalls_back_init);