diff options
Diffstat (limited to 'usr/src/uts/common/xen/io/xenbus_dev.c')
-rw-r--r-- | usr/src/uts/common/xen/io/xenbus_dev.c | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/usr/src/uts/common/xen/io/xenbus_dev.c b/usr/src/uts/common/xen/io/xenbus_dev.c new file mode 100644 index 0000000000..57c57d886f --- /dev/null +++ b/usr/src/uts/common/xen/io/xenbus_dev.c @@ -0,0 +1,645 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * xenbus_dev.c + * + * Driver giving user-space access to the kernel's xenbus connection + * to xenstore. + * + * Copyright (c) 2005, Christian Limpach + * Copyright (c) 2005, Rusty Russell, IBM Corporation + * + * This file may be distributed separately from the Linux kernel, or + * incorporated into other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this source file (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/sysmacros.h> +#include <sys/conf.h> +#include <sys/stat.h> +#include <sys/modctl.h> +#include <sys/uio.h> +#include <sys/list.h> +#include <sys/file.h> +#include <sys/errno.h> +#include <sys/open.h> +#include <sys/cred.h> +#include <sys/condvar.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/hypervisor.h> +#include <xen/sys/xenbus_comms.h> +#include <xen/sys/xenbus_impl.h> +#include <xen/sys/xenbus.h> +#include <xen/public/io/xs_wire.h> + +#ifdef DEBUG +#define XENBUSDRV_DBPRINT(fmt) { if (xenbusdrv_debug) cmn_err fmt; } +#else +#define XENBUSDRV_DBPRINT(fmt) +#endif /* ifdef DEBUG */ + +/* Some handy macros */ +#define XENBUSDRV_MASK_READ_IDX(idx) ((idx) & (PAGESIZE - 1)) +#define XENBUSDRV_MINOR2INST(minor) ((int)(minor)) +#define XENBUSDRV_NCLONES 256 +#define XENBUSDRV_INST2SOFTS(instance) \ + ((xenbus_dev_t *)ddi_get_soft_state(xenbusdrv_statep, (instance))) + +static int xenbusdrv_debug = 0; +static int xenbusdrv_clone_tab[XENBUSDRV_NCLONES]; +static dev_info_t *xenbusdrv_dip; +static kmutex_t xenbusdrv_clone_tab_mutex; + +struct xenbus_dev_transaction { + list_t list; + xenbus_transaction_t handle; +}; + +/* Soft state data structure for xenbus driver */ +struct xenbus_dev_data { + dev_info_t *dip; + + /* In-progress transaction. */ + list_t transactions; + + /* Partial request. */ + unsigned int len; + union { + struct xsd_sockmsg msg; + char buffer[MMU_PAGESIZE]; + } u; + + /* Response queue. */ + char read_buffer[MMU_PAGESIZE]; + unsigned int read_cons, read_prod; + kcondvar_t read_cv; + kmutex_t read_mutex; + int xenstore_inst; +}; +typedef struct xenbus_dev_data xenbus_dev_t; +static void *xenbusdrv_statep; + +static int xenbusdrv_info(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int xenbusdrv_attach(dev_info_t *, ddi_attach_cmd_t); +static int xenbusdrv_detach(dev_info_t *, ddi_detach_cmd_t); +static int xenbusdrv_open(dev_t *, int, int, cred_t *); +static int xenbusdrv_close(dev_t, int, int, cred_t *); +static int xenbusdrv_read(dev_t, struct uio *, cred_t *); +static int xenbusdrv_write(dev_t, struct uio *, cred_t *); +static int xenbusdrv_devmap(dev_t, devmap_cookie_t, offset_t, size_t, size_t *, + uint_t); +static int xenbusdrv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int xenbusdrv_queue_reply(xenbus_dev_t *, const struct xsd_sockmsg *, + const char *); + +/* Solaris driver framework */ + +static struct cb_ops xenbusdrv_cb_ops = { + xenbusdrv_open, /* cb_open */ + xenbusdrv_close, /* cb_close */ + nodev, /* cb_strategy */ + nodev, /* cb_print */ + nodev, /* cb_dump */ + xenbusdrv_read, /* cb_read */ + xenbusdrv_write, /* cb_write */ + xenbusdrv_ioctl, /* cb_ioctl */ + xenbusdrv_devmap, /* cb_devmap */ + NULL, /* cb_mmap */ + NULL, /* cb_segmap */ + nochpoll, /* cb_chpoll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* cb_stream */ + D_DEVMAP | D_NEW | D_MP, /* cb_flag */ + CB_REV +}; + +static struct dev_ops xenbusdrv_dev_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + xenbusdrv_info, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + xenbusdrv_attach, /* devo_attach */ + xenbusdrv_detach, /* devo_detach */ + nodev, /* devo_reset */ + &xenbusdrv_cb_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + NULL /* power */ +}; + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + "virtual bus driver v%I%", /* Name of the module. */ + &xenbusdrv_dev_ops /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL +}; + +int +_init(void) +{ + int e; + + e = ddi_soft_state_init(&xenbusdrv_statep, sizeof (xenbus_dev_t), 1); + if (e) + return (e); + + e = mod_install(&modlinkage); + if (e) + ddi_soft_state_fini(&xenbusdrv_statep); + + return (e); +} + +int +_fini(void) +{ + int e; + + e = mod_remove(&modlinkage); + if (e) + return (e); + + ddi_soft_state_fini(&xenbusdrv_statep); + + return (0); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* ARGSUSED */ +static int +xenbusdrv_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) +{ + dev_t dev = (dev_t)arg; + minor_t minor = getminor(dev); + int retval; + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + if (minor != 0 || xenbusdrv_dip == NULL) { + *result = (void *)NULL; + retval = DDI_FAILURE; + } else { + *result = (void *)xenbusdrv_dip; + retval = DDI_SUCCESS; + } + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)0; + retval = DDI_SUCCESS; + break; + default: + retval = DDI_FAILURE; + } + return (retval); +} + +static int +xenbusdrv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int error; + int unit = ddi_get_instance(dip); + + + switch (cmd) { + case DDI_ATTACH: + break; + case DDI_RESUME: + return (DDI_SUCCESS); + default: + cmn_err(CE_WARN, "xenbus_attach: unknown cmd 0x%x\n", cmd); + return (DDI_FAILURE); + } + + /* DDI_ATTACH */ + + /* + * only one instance - but we clone using the open routine + */ + if (ddi_get_instance(dip) > 0) + return (DDI_FAILURE); + + mutex_init(&xenbusdrv_clone_tab_mutex, NULL, MUTEX_DRIVER, + NULL); + + error = ddi_create_minor_node(dip, "xenbus", S_IFCHR, unit, + DDI_PSEUDO, NULL); + if (error != DDI_SUCCESS) + goto fail; + + /* + * save dip for getinfo + */ + xenbusdrv_dip = dip; + ddi_report_dev(dip); + + if (DOMAIN_IS_INITDOMAIN(xen_info)) + xs_dom0_init(); + + return (DDI_SUCCESS); + +fail: + (void) xenbusdrv_detach(dip, DDI_DETACH); + return (error); +} + +static int +xenbusdrv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + /* + * again, only one instance + */ + if (ddi_get_instance(dip) > 0) + return (DDI_FAILURE); + + switch (cmd) { + case DDI_DETACH: + ddi_remove_minor_node(dip, NULL); + mutex_destroy(&xenbusdrv_clone_tab_mutex); + xenbusdrv_dip = NULL; + return (DDI_SUCCESS); + case DDI_SUSPEND: + return (DDI_SUCCESS); + default: + cmn_err(CE_WARN, "xenbus_detach: unknown cmd 0x%x\n", cmd); + return (DDI_FAILURE); + } +} + +/* ARGSUSED */ +static int +xenbusdrv_open(dev_t *devp, int flag, int otyp, cred_t *credp) +{ + xenbus_dev_t *xbs; + minor_t minor = getminor(*devp); + + if (otyp == OTYP_BLK) + return (ENXIO); + + /* + * only allow open on minor = 0 - the clone device + */ + if (minor != 0) + return (ENXIO); + + /* + * find a free slot and grab it + */ + mutex_enter(&xenbusdrv_clone_tab_mutex); + for (minor = 1; minor < XENBUSDRV_NCLONES; minor++) { + if (xenbusdrv_clone_tab[minor] == 0) { + xenbusdrv_clone_tab[minor] = 1; + break; + } + } + mutex_exit(&xenbusdrv_clone_tab_mutex); + if (minor == XENBUSDRV_NCLONES) + return (EAGAIN); + + /* Allocate softstate structure */ + if (ddi_soft_state_zalloc(xenbusdrv_statep, + XENBUSDRV_MINOR2INST(minor)) != DDI_SUCCESS) { + mutex_enter(&xenbusdrv_clone_tab_mutex); + xenbusdrv_clone_tab[minor] = 0; + mutex_exit(&xenbusdrv_clone_tab_mutex); + return (EAGAIN); + } + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(minor)); + + /* ... and init it */ + xbs->dip = xenbusdrv_dip; + mutex_init(&xbs->read_mutex, NULL, MUTEX_DRIVER, NULL); + cv_init(&xbs->read_cv, NULL, CV_DEFAULT, NULL); + list_create(&xbs->transactions, sizeof (struct xenbus_dev_transaction), + offsetof(struct xenbus_dev_transaction, list)); + + /* clone driver */ + *devp = makedevice(getmajor(*devp), minor); + XENBUSDRV_DBPRINT((CE_NOTE, "Xenbus drv open succeeded, minor=%d", + minor)); + + return (0); +} + +/* ARGSUSED */ +static int +xenbusdrv_close(dev_t dev, int flag, int otyp, struct cred *credp) +{ + xenbus_dev_t *xbs; + minor_t minor = getminor(dev); + struct xenbus_dev_transaction *trans; + + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(minor)); + if (xbs == NULL) + return (ENXIO); + +#ifdef notyet + /* + * XXPV - would like to be able to notify xenstore down here, but + * as the daemon is currently written, it doesn't leave the device + * open after initial setup, so we have no way of knowing if it has + * gone away. + */ + if (xbs->xenstore_inst) + xs_notify_xenstore_down(); +#endif + /* free pending transaction */ + while (trans = (struct xenbus_dev_transaction *) + list_head(&xbs->transactions)) { + (void) xenbus_transaction_end(trans->handle, 1); + list_remove(&xbs->transactions, (void *)trans); + kmem_free(trans, sizeof (*trans)); + } + + mutex_destroy(&xbs->read_mutex); + cv_destroy(&xbs->read_cv); + ddi_soft_state_free(xenbusdrv_statep, XENBUSDRV_MINOR2INST(minor)); + + /* + * free clone tab slot + */ + mutex_enter(&xenbusdrv_clone_tab_mutex); + xenbusdrv_clone_tab[minor] = 0; + mutex_exit(&xenbusdrv_clone_tab_mutex); + + XENBUSDRV_DBPRINT((CE_NOTE, "Xenbus drv close succeeded, minor=%d", + minor)); + + return (0); +} + +/* ARGSUSED */ +static int +xenbusdrv_read(dev_t dev, struct uio *uiop, cred_t *credp) +{ + xenbus_dev_t *xbs; + size_t len; + int res, ret; + int idx; + + XENBUSDRV_DBPRINT((CE_NOTE, "xenbusdrv_read called")); + + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(getminor(dev))); + + mutex_enter(&xbs->read_mutex); + + /* check if we have something to read */ + while (xbs->read_prod == xbs->read_cons) { + if (cv_wait_sig(&xbs->read_cv, &xbs->read_mutex) == 0) { + mutex_exit(&xbs->read_mutex); + return (EINTR); + } + } + + idx = XENBUSDRV_MASK_READ_IDX(xbs->read_cons); + res = uiop->uio_resid; + + len = xbs->read_prod - xbs->read_cons; + + if (len > (sizeof (xbs->read_buffer) - idx)) + len = sizeof (xbs->read_buffer) - idx; + if (len > res) + len = res; + + ret = uiomove(xbs->read_buffer + idx, len, UIO_READ, uiop); + xbs->read_cons += res - uiop->uio_resid; + mutex_exit(&xbs->read_mutex); + + return (ret); +} + +/* + * prepare data for xenbusdrv_read() + */ +static int +xenbusdrv_queue_reply(xenbus_dev_t *xbs, const struct xsd_sockmsg *msg, + const char *reply) +{ + int i; + int remaining; + + XENBUSDRV_DBPRINT((CE_NOTE, "xenbusdrv_queue_reply called")); + + mutex_enter(&xbs->read_mutex); + + remaining = sizeof (xbs->read_buffer) - + (xbs->read_prod - xbs->read_cons); + + if (sizeof (*msg) + msg->len > remaining) { + mutex_exit(&xbs->read_mutex); + return (EOVERFLOW); + } + + for (i = 0; i < sizeof (*msg); i++, xbs->read_prod++) { + xbs->read_buffer[XENBUSDRV_MASK_READ_IDX(xbs->read_prod)] = + ((char *)msg)[i]; + } + + for (i = 0; i < msg->len; i++, xbs->read_prod++) { + xbs->read_buffer[XENBUSDRV_MASK_READ_IDX(xbs->read_prod)] = + reply[i]; + } + + cv_broadcast(&xbs->read_cv); + + mutex_exit(&xbs->read_mutex); + + XENBUSDRV_DBPRINT((CE_NOTE, "xenbusdrv_queue_reply exited")); + + return (0); +} + +/* ARGSUSED */ +static int +xenbusdrv_write(dev_t dev, struct uio *uiop, cred_t *credp) +{ + xenbus_dev_t *xbs; + struct xenbus_dev_transaction *trans; + void *reply; + size_t len; + int rc = 0; + + XENBUSDRV_DBPRINT((CE_NOTE, "xenbusdrv_write called")); + + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(getminor(dev))); + len = uiop->uio_resid; + + if ((len + xbs->len) > sizeof (xbs->u.buffer)) { + XENBUSDRV_DBPRINT((CE_WARN, "Request is too big")); + rc = EINVAL; + goto out; + } + + if (uiomove(xbs->u.buffer + xbs->len, len, UIO_WRITE, uiop) != 0) { + XENBUSDRV_DBPRINT((CE_WARN, "Uiomove failed")); + rc = EFAULT; + goto out; + } + + xbs->len += len; + + if (xbs->len < (sizeof (xbs->u.msg)) || + xbs->len < (sizeof (xbs->u.msg) + xbs->u.msg.len)) { + XENBUSDRV_DBPRINT((CE_NOTE, "Partial request")); + return (0); + } + + switch (xbs->u.msg.type) { + case XS_TRANSACTION_START: + case XS_TRANSACTION_END: + case XS_DIRECTORY: + case XS_READ: + case XS_GET_PERMS: + case XS_RELEASE: + case XS_GET_DOMAIN_PATH: + case XS_WRITE: + case XS_MKDIR: + case XS_RM: + case XS_SET_PERMS: + /* send the request to xenstore and get feedback */ + rc = xenbus_dev_request_and_reply(&xbs->u.msg, &reply); + if (rc) { + XENBUSDRV_DBPRINT((CE_WARN, + "xenbus_dev_request_and_reply failed")); + goto out; + } + + /* handle transaction start/end */ + if (xbs->u.msg.type == XS_TRANSACTION_START) { + trans = kmem_alloc(sizeof (*trans), KM_SLEEP); + (void) ddi_strtoul((char *)reply, NULL, 0, + (unsigned long *)&trans->handle); + list_insert_tail(&xbs->transactions, (void *)trans); + } else if (xbs->u.msg.type == XS_TRANSACTION_END) { + /* try to find out the ending transaction */ + for (trans = (struct xenbus_dev_transaction *) + list_head(&xbs->transactions); trans; + trans = (struct xenbus_dev_transaction *) + list_next(&xbs->transactions, (void *)trans)) + if (trans->handle == + (xenbus_transaction_t) + xbs->u.msg.tx_id) + break; + ASSERT(trans); + /* free it, if we find it */ + list_remove(&xbs->transactions, (void *)trans); + kmem_free(trans, sizeof (*trans)); + } + + /* prepare data for xenbusdrv_read() to get */ + rc = xenbusdrv_queue_reply(xbs, &xbs->u.msg, reply); + + kmem_free(reply, xbs->u.msg.len + 1); + break; + default: + rc = EINVAL; + } + +out: + xbs->len = 0; + return (rc); +} + +/*ARGSUSED*/ +static int +xenbusdrv_devmap(dev_t dev, devmap_cookie_t dhp, offset_t off, size_t len, + size_t *maplen, uint_t model) +{ + xenbus_dev_t *xbs; + int err; + + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(getminor(dev))); + + if (off != 0 || len != PAGESIZE) + return (-1); + + if (!DOMAIN_IS_INITDOMAIN(xen_info)) + return (-1); + + err = devmap_umem_setup(dhp, xbs->dip, NULL, xb_xenstore_cookie(), + 0, PAGESIZE, PROT_READ | PROT_WRITE | PROT_USER, 0, NULL); + + if (err) + return (err); + + *maplen = PAGESIZE; + + return (0); +} + +/*ARGSUSED*/ +static int +xenbusdrv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + xenbus_dev_t *xbs; + + xbs = XENBUSDRV_INST2SOFTS(XENBUSDRV_MINOR2INST(getminor(dev))); + switch (cmd) { + case IOCTL_XENBUS_XENSTORE_EVTCHN: + *rvalp = xen_info->store_evtchn; + break; + case IOCTL_XENBUS_NOTIFY_UP: + xs_notify_xenstore_up(); + xbs->xenstore_inst = 1; + break; + default: + return (EINVAL); + } + + return (0); +} |