X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fxen%2Fxenbus%2Fxenbus_dev.c;fp=drivers%2Fxen%2Fxenbus%2Fxenbus_dev.c;h=02d3b46ee75b8265c014cb17e97be30a422d13d7;hb=1db395853d4f30d6120458bd279ede1f882a8525;hp=0000000000000000000000000000000000000000;hpb=34a75f0025b9cf803b6a88db032e6ad6950c9313;p=linux-2.6.git diff --git a/drivers/xen/xenbus/xenbus_dev.c b/drivers/xen/xenbus/xenbus_dev.c new file mode 100644 index 000000000..02d3b46ee --- /dev/null +++ b/drivers/xen/xenbus/xenbus_dev.c @@ -0,0 +1,359 @@ +/* + * 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 program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation; or, when 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xenbus_comms.h" + +#include +#include +#include +#include +#include + +struct xenbus_dev_transaction { + struct list_head list; + struct xenbus_transaction handle; +}; + +struct xenbus_dev_data { + /* In-progress transaction. */ + struct list_head transactions; + + /* Active watches. */ + struct list_head watches; + + /* Partial request. */ + unsigned int len; + union { + struct xsd_sockmsg msg; + char buffer[PAGE_SIZE]; + } u; + + /* Response queue. */ +#define MASK_READ_IDX(idx) ((idx)&(PAGE_SIZE-1)) + char read_buffer[PAGE_SIZE]; + unsigned int read_cons, read_prod; + wait_queue_head_t read_waitq; + + struct mutex reply_mutex; +}; + +static struct proc_dir_entry *xenbus_dev_intf; + +static ssize_t xenbus_dev_read(struct file *filp, + char __user *ubuf, + size_t len, loff_t *ppos) +{ + struct xenbus_dev_data *u = filp->private_data; + int i; + + if (wait_event_interruptible(u->read_waitq, + u->read_prod != u->read_cons)) + return -EINTR; + + for (i = 0; i < len; i++) { + if (u->read_cons == u->read_prod) + break; + put_user(u->read_buffer[MASK_READ_IDX(u->read_cons)], ubuf+i); + u->read_cons++; + } + + return i; +} + +static void queue_reply(struct xenbus_dev_data *u, + char *data, unsigned int len) +{ + int i; + + mutex_lock(&u->reply_mutex); + + for (i = 0; i < len; i++, u->read_prod++) + u->read_buffer[MASK_READ_IDX(u->read_prod)] = data[i]; + + BUG_ON((u->read_prod - u->read_cons) > sizeof(u->read_buffer)); + + mutex_unlock(&u->reply_mutex); + + wake_up(&u->read_waitq); +} + +struct watch_adapter +{ + struct list_head list; + struct xenbus_watch watch; + struct xenbus_dev_data *dev_data; + char *token; +}; + +static void free_watch_adapter (struct watch_adapter *watch) +{ + kfree(watch->watch.node); + kfree(watch->token); + kfree(watch); +} + +static void watch_fired(struct xenbus_watch *watch, + const char **vec, + unsigned int len) +{ + struct watch_adapter *adap = + container_of(watch, struct watch_adapter, watch); + struct xsd_sockmsg hdr; + const char *path, *token; + int path_len, tok_len, body_len; + + path = vec[XS_WATCH_PATH]; + token = adap->token; + + path_len = strlen(path) + 1; + tok_len = strlen(token) + 1; + body_len = path_len + tok_len; + + hdr.type = XS_WATCH_EVENT; + hdr.len = body_len; + + queue_reply(adap->dev_data, (char *)&hdr, sizeof(hdr)); + queue_reply(adap->dev_data, (char *)path, path_len); + queue_reply(adap->dev_data, (char *)token, tok_len); +} + +static LIST_HEAD(watch_list); + +static ssize_t xenbus_dev_write(struct file *filp, + const char __user *ubuf, + size_t len, loff_t *ppos) +{ + struct xenbus_dev_data *u = filp->private_data; + struct xenbus_dev_transaction *trans = NULL; + uint32_t msg_type; + void *reply; + char *path, *token; + struct watch_adapter *watch, *tmp_watch; + int err; + + if ((len + u->len) > sizeof(u->u.buffer)) + return -EINVAL; + + if (copy_from_user(u->u.buffer + u->len, ubuf, len) != 0) + return -EFAULT; + + u->len += len; + if (u->len < (sizeof(u->u.msg) + u->u.msg.len)) + return len; + + msg_type = u->u.msg.type; + + switch (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: + if (msg_type == XS_TRANSACTION_START) { + trans = kmalloc(sizeof(*trans), GFP_KERNEL); + if (!trans) + return -ENOMEM; + } + + reply = xenbus_dev_request_and_reply(&u->u.msg); + if (IS_ERR(reply)) { + kfree(trans); + return PTR_ERR(reply); + } + + if (msg_type == XS_TRANSACTION_START) { + trans->handle.id = simple_strtoul(reply, NULL, 0); + list_add(&trans->list, &u->transactions); + } else if (msg_type == XS_TRANSACTION_END) { + list_for_each_entry(trans, &u->transactions, list) + if (trans->handle.id == u->u.msg.tx_id) + break; + BUG_ON(&trans->list == &u->transactions); + list_del(&trans->list); + kfree(trans); + } + queue_reply(u, (char *)&u->u.msg, sizeof(u->u.msg)); + queue_reply(u, (char *)reply, u->u.msg.len); + kfree(reply); + break; + + case XS_WATCH: + case XS_UNWATCH: + path = u->u.buffer + sizeof(u->u.msg); + token = memchr(path, 0, u->u.msg.len); + if (token == NULL) + return -EILSEQ; + token++; + + if (msg_type == XS_WATCH) { + static const char * XS_WATCH_RESP = "OK"; + struct xsd_sockmsg hdr; + + watch = kmalloc(sizeof(*watch), GFP_KERNEL); + watch->watch.node = kmalloc(strlen(path)+1, + GFP_KERNEL); + strcpy((char *)watch->watch.node, path); + watch->watch.callback = watch_fired; + watch->token = kmalloc(strlen(token)+1, GFP_KERNEL); + strcpy(watch->token, token); + watch->dev_data = u; + + err = register_xenbus_watch(&watch->watch); + if (err) { + free_watch_adapter(watch); + return err; + } + + list_add(&watch->list, &u->watches); + + hdr.type = XS_WATCH; + hdr.len = strlen(XS_WATCH_RESP) + 1; + queue_reply(u, (char *)&hdr, sizeof(hdr)); + queue_reply(u, (char *)XS_WATCH_RESP, hdr.len); + } else { + list_for_each_entry_safe(watch, tmp_watch, + &u->watches, list) { + if (!strcmp(watch->token, token) && + !strcmp(watch->watch.node, path)) + break; + { + unregister_xenbus_watch(&watch->watch); + list_del(&watch->list); + free_watch_adapter(watch); + break; + } + } + } + + break; + + default: + return -EINVAL; + } + + u->len = 0; + return len; +} + +static int xenbus_dev_open(struct inode *inode, struct file *filp) +{ + struct xenbus_dev_data *u; + + if (xen_start_info->store_evtchn == 0) + return -ENOENT; + + nonseekable_open(inode, filp); + + u = kzalloc(sizeof(*u), GFP_KERNEL); + if (u == NULL) + return -ENOMEM; + + INIT_LIST_HEAD(&u->transactions); + INIT_LIST_HEAD(&u->watches); + init_waitqueue_head(&u->read_waitq); + + mutex_init(&u->reply_mutex); + + filp->private_data = u; + + return 0; +} + +static int xenbus_dev_release(struct inode *inode, struct file *filp) +{ + struct xenbus_dev_data *u = filp->private_data; + struct xenbus_dev_transaction *trans, *tmp; + struct watch_adapter *watch, *tmp_watch; + + list_for_each_entry_safe(trans, tmp, &u->transactions, list) { + xenbus_transaction_end(trans->handle, 1); + list_del(&trans->list); + kfree(trans); + } + + list_for_each_entry_safe(watch, tmp_watch, &u->watches, list) { + unregister_xenbus_watch(&watch->watch); + list_del(&watch->list); + free_watch_adapter(watch); + } + + kfree(u); + + return 0; +} + +static unsigned int xenbus_dev_poll(struct file *file, poll_table *wait) +{ + struct xenbus_dev_data *u = file->private_data; + + poll_wait(file, &u->read_waitq, wait); + if (u->read_cons != u->read_prod) + return POLLIN | POLLRDNORM; + return 0; +} + +static struct file_operations xenbus_dev_file_ops = { + .read = xenbus_dev_read, + .write = xenbus_dev_write, + .open = xenbus_dev_open, + .release = xenbus_dev_release, + .poll = xenbus_dev_poll, +}; + +static int __init +xenbus_dev_init(void) +{ + xenbus_dev_intf = create_xen_proc_entry("xenbus", 0400); + if (xenbus_dev_intf) + xenbus_dev_intf->proc_fops = &xenbus_dev_file_ops; + + return 0; +} + +__initcall(xenbus_dev_init);