/* * Copyright (C) 2006-2009 Free Software Foundation * * 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. * * You should have received a copy of the GNU General Public License * along with the program ; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "store.h" #include "ring.h" #include "evt.h" #include "xen.h" /* TODO use events instead of just yielding */ /* Hypervisor part */ decl_simple_lock_data(static, lock); static struct xenstore_domain_interface *store; struct store_req { const char *data; unsigned len; }; /* Send a request */ static void store_put(hyp_store_transaction_t t, uint32_t type, struct store_req *req, unsigned nr_reqs) { struct xsd_sockmsg head = { .type = type, .req_id = 0, .tx_id = t, }; unsigned totlen, len; unsigned i; totlen = 0; for (i = 0; i < nr_reqs; i++) totlen += req[i].len; head.len = totlen; totlen += sizeof(head); if (totlen > sizeof(store->req) - 1) panic("too big store message %d, max %d", totlen, (int) sizeof(store->req)); while (hyp_ring_available(store->req, store->req_prod, store->req_cons) < totlen) hyp_yield(); mb(); hyp_ring_store(&hyp_ring_cell(store->req, store->req_prod), &head, sizeof(head), store->req, store->req + sizeof(store->req)); len = sizeof(head); for (i=0; ireq, store->req_prod + len), req[i].data, req[i].len, store->req, store->req + sizeof(store->req)); len += req[i].len; } wmb(); store->req_prod += totlen; hyp_event_channel_send(boot_info.store_evtchn); } static const char *errors[] = { "EINVAL", "EACCES", "EEXIST", "EISDIR", "ENOENT", "ENOMEM", "ENOSPC", "EIO", "ENOTEMPTY", "ENOSYS", "EROFS", "EBUSY", "EAGAIN", "EISCONN", NULL, }; /* Send a request and wait for a reply, whose header is put in head, and * data is returned (beware, that's in the ring !) * On error, returns NULL. Else takes the lock and return pointer on data and * store_put_wait_end shall be called after reading it. */ static struct xsd_sockmsg head; const char *hyp_store_error; static void *store_put_wait(hyp_store_transaction_t t, uint32_t type, struct store_req *req, unsigned nr_reqs) { unsigned len; const char **error; void *data; simple_lock(&lock); store_put(t, type, req, nr_reqs); again: while (store->rsp_prod - store->rsp_cons < sizeof(head)) hyp_yield(); rmb(); hyp_ring_fetch(&head, &hyp_ring_cell(store->rsp, store->rsp_cons), sizeof(head), store->rsp, store->rsp + sizeof(store->rsp)); len = sizeof(head) + head.len; while (store->rsp_prod - store->rsp_cons < len) hyp_yield(); rmb(); if (head.type == XS_WATCH_EVENT) { /* Spurious watch event, drop */ store->rsp_cons += sizeof(head) + head.len; hyp_event_channel_send(boot_info.store_evtchn); goto again; } data = &hyp_ring_cell(store->rsp, store->rsp_cons + sizeof(head)); if (head.len <= 10) { char c[10]; hyp_ring_fetch(c, data, head.len, store->rsp, store->rsp + sizeof(store->rsp)); for (error = errors; *error; error++) { if (head.len == strlen(*error) + 1 && !memcmp(*error, c, head.len)) { hyp_store_error = *error; store->rsp_cons += len; hyp_event_channel_send(boot_info.store_evtchn); simple_unlock(&lock); return NULL; } } } return data; } /* Must be called after each store_put_wait. Releases lock. */ static void store_put_wait_end(void) { mb(); store->rsp_cons += sizeof(head) + head.len; hyp_event_channel_send(boot_info.store_evtchn); simple_unlock(&lock); } /* Start a transaction. */ hyp_store_transaction_t hyp_store_transaction_start(void) { struct store_req req = { .data = "", .len = 1, }; char *rep; char *s; int i; rep = store_put_wait(0, XS_TRANSACTION_START, &req, 1); if (!rep) panic("couldn't start transaction (%s)", hyp_store_error); s = alloca(head.len); hyp_ring_fetch(s, rep, head.len, store->rsp, store->rsp + sizeof(store->rsp)); mach_atoi((u_char*) s, &i); if (i == MACH_ATOI_DEFAULT) panic("bogus transaction id len %d '%s'", head.len, s); store_put_wait_end(); return i; } /* Stop a transaction. */ int hyp_store_transaction_stop(hyp_store_transaction_t t) { struct store_req req = { .data = "T", .len = 2, }; int ret = 1; void *rep; rep = store_put_wait(t, XS_TRANSACTION_END, &req, 1); if (!rep) return 0; store_put_wait_end(); return ret; } /* List a directory: returns an array to file names, terminated by NULL. Free * with kfree. */ char **hyp_store_ls(hyp_store_transaction_t t, int n, ...) { struct store_req req[n]; va_list listp; int i; char *rep; char *c; char **res, **rsp; va_start (listp, n); for (i = 0; i < n; i++) { req[i].data = va_arg(listp, char *); req[i].len = strlen(req[i].data); } req[n - 1].len++; va_end (listp); rep = store_put_wait(t, XS_DIRECTORY, req, n); if (!rep) return NULL; i = 0; for ( c = rep, n = 0; n < head.len; n += hyp_ring_next_word(&c, store->rsp, store->rsp + sizeof(store->rsp)) + 1) i++; res = (void*) kalloc((i + 1) * sizeof(char*) + head.len); if (!res) hyp_store_error = "ENOMEM"; else { hyp_ring_fetch(res + (i + 1), rep, head.len, store->rsp, store->rsp + sizeof(store->rsp)); rsp = res; for (c = (char*) (res + (i + 1)); i; i--, c += strlen(c) + 1) *rsp++ = c; *rsp = NULL; } store_put_wait_end(); return res; } /* Get the value of an entry, va version. */ static void *hyp_store_read_va(hyp_store_transaction_t t, int n, va_list listp) { struct store_req req[n]; int i; void *rep; char *res; for (i = 0; i < n; i++) { req[i].data = va_arg(listp, char *); req[i].len = strlen(req[i].data); } req[n - 1].len++; rep = store_put_wait(t, XS_READ, req, n); if (!rep) return NULL; res = (void*) kalloc(head.len + 1); if (!res) hyp_store_error = "ENOMEM"; else { hyp_ring_fetch(res, rep, head.len, store->rsp, store->rsp + sizeof(store->rsp)); res[head.len] = 0; } store_put_wait_end(); return res; } /* Get the value of an entry. Free with kfree. */ void *hyp_store_read(hyp_store_transaction_t t, int n, ...) { va_list listp; char *res; va_start(listp, n); res = hyp_store_read_va(t, n, listp); va_end(listp); return res; } /* Get the integer value of an entry, -1 on error. */ int hyp_store_read_int(hyp_store_transaction_t t, int n, ...) { va_list listp; char *res; int i; va_start(listp, n); res = hyp_store_read_va(t, n, listp); va_end(listp); if (!res) return -1; mach_atoi((u_char *) res, &i); if (i == MACH_ATOI_DEFAULT) printf("bogus integer '%s'\n", res); kfree((vm_offset_t) res, strlen(res)+1); return i; } /* Set the value of an entry. */ char *hyp_store_write(hyp_store_transaction_t t, const char *data, int n, ...) { struct store_req req[n + 1]; va_list listp; int i; void *rep; char *res; va_start (listp, n); for (i = 0; i < n; i++) { req[i].data = va_arg(listp, char *); req[i].len = strlen(req[i].data); } req[n - 1].len++; req[n].data = data; req[n].len = strlen (data); va_end (listp); rep = store_put_wait (t, XS_WRITE, req, n + 1); if (!rep) return NULL; res = (void*) kalloc(head.len + 1); if (!res) hyp_store_error = NULL; else { hyp_ring_fetch(res, rep, head.len, store->rsp, store->rsp + sizeof(store->rsp)); res[head.len] = 0; } store_put_wait_end(); return res; } static void hyp_store_handler(int unit) { thread_wakeup(&boot_info.store_evtchn); } /* Map store's shared page. */ void hyp_store_init(void) { if (store) return; simple_lock_init(&lock); store = (void*) mfn_to_kv(boot_info.store_mfn); #ifdef MACH_PV_PAGETABLES pmap_set_page_readwrite(store); #endif /* MACH_PV_PAGETABLES */ /* SPL sched */ hyp_evt_handler(boot_info.store_evtchn, hyp_store_handler, 0, SPL7); }