diff options
Diffstat (limited to 'tools/libvchan/io.c')
-rw-r--r-- | tools/libvchan/io.c | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/tools/libvchan/io.c b/tools/libvchan/io.c new file mode 100644 index 0000000000..7023add0ec --- /dev/null +++ b/tools/libvchan/io.c @@ -0,0 +1,375 @@ +/** + * @file + * @section AUTHORS + * + * Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com> + * + * Authors: + * Rafal Wojtczuk <rafal@invisiblethingslab.com> + * Daniel De Graaf <dgdegra@tycho.nsa.gov> + * + * @section LICENSE + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @section DESCRIPTION + * + * This file contains the communications interface built on the ring buffer. + */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/uio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +#include <xenctrl.h> +#include <libxenvchan.h> + +#ifndef PAGE_SHIFT +#define PAGE_SHIFT 12 +#endif + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +// allow vchan data to be easily observed in strace by doing a +// writev() to FD -1 with the data being read/written. +#ifndef VCHAN_DEBUG +#define VCHAN_DEBUG 0 +#endif + +#define barrier() asm volatile("" ::: "memory") + + +static inline uint32_t rd_prod(struct libxenvchan *ctrl) +{ + return ctrl->read.shr->prod; +} + +static inline uint32_t* _rd_cons(struct libxenvchan *ctrl) +{ + return &ctrl->read.shr->cons; +} +#define rd_cons(x) (*_rd_cons(x)) + +static inline uint32_t* _wr_prod(struct libxenvchan *ctrl) +{ + return &ctrl->write.shr->prod; +} +#define wr_prod(x) (*_wr_prod(x)) + +static inline uint32_t wr_cons(struct libxenvchan *ctrl) +{ + return ctrl->write.shr->cons; +} + +static inline const void* rd_ring(struct libxenvchan *ctrl) +{ + return ctrl->read.buffer; +} + +static inline void* wr_ring(struct libxenvchan *ctrl) +{ + return ctrl->write.buffer; +} + +static inline uint32_t wr_ring_size(struct libxenvchan *ctrl) +{ + return (1 << ctrl->write.order); +} + +static inline uint32_t rd_ring_size(struct libxenvchan *ctrl) +{ + return (1 << ctrl->read.order); +} + +static inline void request_notify(struct libxenvchan *ctrl, uint8_t bit) +{ + uint8_t *notify = ctrl->is_server ? &ctrl->ring->cli_notify : &ctrl->ring->srv_notify; + __sync_or_and_fetch(notify, bit); +} + +static inline int send_notify(struct libxenvchan *ctrl, uint8_t bit) +{ + uint8_t *notify = ctrl->is_server ? &ctrl->ring->srv_notify : &ctrl->ring->cli_notify; + uint8_t prev = __sync_fetch_and_and(notify, ~bit); + if (prev & bit) + return xc_evtchn_notify(ctrl->event, ctrl->event_port); + else + return 0; +} + +/** + * Get the amount of buffer space available and enable notifications if needed. + */ +static inline int fast_get_data_ready(struct libxenvchan *ctrl, size_t request) +{ + int ready = rd_prod(ctrl) - rd_cons(ctrl); + if (ready >= request) + return ready; + /* We plan to consume all data; please tell us if you send more */ + request_notify(ctrl, VCHAN_NOTIFY_WRITE); + /* + * If the writer moved rd_prod after our read but before request, we + * will not get notified even though the actual amount of data ready is + * above request. Reread rd_prod to cover this case. + */ + return rd_prod(ctrl) - rd_cons(ctrl); +} + +int libxenvchan_data_ready(struct libxenvchan *ctrl) +{ + /* Since this value is being used outside libxenvchan, request notification + * when it changes + */ + request_notify(ctrl, VCHAN_NOTIFY_WRITE); + return rd_prod(ctrl) - rd_cons(ctrl); +} + +/** + * Get the amount of buffer space available and enable notifications if needed. + */ +static inline int fast_get_buffer_space(struct libxenvchan *ctrl, size_t request) +{ + int ready = wr_ring_size(ctrl) - (wr_prod(ctrl) - wr_cons(ctrl)); + if (ready >= request) + return ready; + /* We plan to fill the buffer; please tell us when you've read it */ + request_notify(ctrl, VCHAN_NOTIFY_READ); + /* + * If the reader moved wr_cons after our read but before request, we + * will not get notified even though the actual amount of buffer space + * is above request. Reread wr_cons to cover this case. + */ + return wr_ring_size(ctrl) - (wr_prod(ctrl) - wr_cons(ctrl)); +} + +int libxenvchan_buffer_space(struct libxenvchan *ctrl) +{ + /* Since this value is being used outside libxenvchan, request notification + * when it changes + */ + request_notify(ctrl, VCHAN_NOTIFY_READ); + return wr_ring_size(ctrl) - (wr_prod(ctrl) - wr_cons(ctrl)); +} + +int libxenvchan_wait(struct libxenvchan *ctrl) +{ + int ret = xc_evtchn_pending(ctrl->event); + if (ret < 0) + return -1; + xc_evtchn_unmask(ctrl->event, ret); + return 0; +} + +/** + * returns -1 on error, or size on success + */ +static int do_send(struct libxenvchan *ctrl, const void *data, size_t size) +{ + int real_idx = wr_prod(ctrl) & (wr_ring_size(ctrl) - 1); + int avail_contig = wr_ring_size(ctrl) - real_idx; + if (VCHAN_DEBUG) { + char metainfo[32]; + struct iovec iov[2]; + iov[0].iov_base = metainfo; + iov[0].iov_len = snprintf(metainfo, 32, "vchan@%p wr", ctrl); + iov[1].iov_base = (void *)data; + iov[1].iov_len = size; + writev(-1, iov, 2); + } + if (avail_contig > size) + avail_contig = size; + memcpy(wr_ring(ctrl) + real_idx, data, avail_contig); + if (avail_contig < size) + { + // we rolled across the end of the ring + memcpy(wr_ring(ctrl), data + avail_contig, size - avail_contig); + } + barrier(); // data must be in the ring prior to increment + wr_prod(ctrl) += size; + barrier(); // increment must happen prior to notify + if (send_notify(ctrl, VCHAN_NOTIFY_WRITE)) + return -1; + return size; +} + +/** + * returns 0 if no buffer space is available, -1 on error, or size on success + */ +int libxenvchan_send(struct libxenvchan *ctrl, const void *data, size_t size) +{ + int avail; + while (1) { + if (!libxenvchan_is_open(ctrl)) + return -1; + avail = fast_get_buffer_space(ctrl, size); + if (size <= avail) + return do_send(ctrl, data, size); + if (!ctrl->blocking) + return 0; + if (size > wr_ring_size(ctrl)) + return -1; + if (libxenvchan_wait(ctrl)) + return -1; + } +} + +int libxenvchan_write(struct libxenvchan *ctrl, const void *data, size_t size) +{ + int avail; + if (!libxenvchan_is_open(ctrl)) + return -1; + if (ctrl->blocking) { + size_t pos = 0; + while (1) { + avail = fast_get_buffer_space(ctrl, size - pos); + if (pos + avail > size) + avail = size - pos; + if (avail) + pos += do_send(ctrl, data + pos, avail); + if (pos == size) + return pos; + if (libxenvchan_wait(ctrl)) + return -1; + if (!libxenvchan_is_open(ctrl)) + return -1; + } + } else { + avail = fast_get_buffer_space(ctrl, size); + if (size > avail) + size = avail; + if (size == 0) + return 0; + return do_send(ctrl, data, size); + } +} + +static int do_recv(struct libxenvchan *ctrl, void *data, size_t size) +{ + int real_idx = rd_cons(ctrl) & (rd_ring_size(ctrl) - 1); + int avail_contig = rd_ring_size(ctrl) - real_idx; + if (avail_contig > size) + avail_contig = size; + barrier(); // data read must happen after rd_cons read + memcpy(data, rd_ring(ctrl) + real_idx, avail_contig); + if (avail_contig < size) + { + // we rolled across the end of the ring + memcpy(data + avail_contig, rd_ring(ctrl), size - avail_contig); + } + rd_cons(ctrl) += size; + if (VCHAN_DEBUG) { + char metainfo[32]; + struct iovec iov[2]; + iov[0].iov_base = metainfo; + iov[0].iov_len = snprintf(metainfo, 32, "vchan@%p rd", ctrl); + iov[1].iov_base = data; + iov[1].iov_len = size; + writev(-1, iov, 2); + } + barrier(); // consumption must happen prior to notify of newly freed space + if (send_notify(ctrl, VCHAN_NOTIFY_READ)) + return -1; + return size; +} + +/** + * reads exactly size bytes from the vchan. + * returns 0 if insufficient data is available, -1 on error, or size on success + */ +int libxenvchan_recv(struct libxenvchan *ctrl, void *data, size_t size) +{ + while (1) { + int avail = fast_get_data_ready(ctrl, size); + if (size <= avail) + return do_recv(ctrl, data, size); + if (!libxenvchan_is_open(ctrl)) + return -1; + if (!ctrl->blocking) + return 0; + if (size > rd_ring_size(ctrl)) + return -1; + if (libxenvchan_wait(ctrl)) + return -1; + } +} + +int libxenvchan_read(struct libxenvchan *ctrl, void *data, size_t size) +{ + while (1) { + int avail = fast_get_data_ready(ctrl, size); + if (avail && size > avail) + size = avail; + if (avail) + return do_recv(ctrl, data, size); + if (!libxenvchan_is_open(ctrl)) + return -1; + if (!ctrl->blocking) + return 0; + if (libxenvchan_wait(ctrl)) + return -1; + } +} + +int libxenvchan_is_open(struct libxenvchan* ctrl) +{ + if (ctrl->is_server) + return ctrl->server_persist ? 1 : ctrl->ring->cli_live; + else + return ctrl->ring->srv_live; +} + +int libxenvchan_fd_for_select(struct libxenvchan *ctrl) +{ + return xc_evtchn_fd(ctrl->event); +} + +void libxenvchan_close(struct libxenvchan *ctrl) +{ + if (!ctrl) + return; + if (ctrl->read.order >= PAGE_SHIFT) + munmap(ctrl->read.buffer, 1 << ctrl->read.order); + if (ctrl->write.order >= PAGE_SHIFT) + munmap(ctrl->write.buffer, 1 << ctrl->write.order); + if (ctrl->ring) { + if (ctrl->is_server) { + ctrl->ring->srv_live = 0; + xc_gntshr_munmap(ctrl->gntshr, ctrl->ring, PAGE_SIZE); + } else { + ctrl->ring->cli_live = 0; + xc_gnttab_munmap(ctrl->gnttab, ctrl->ring, PAGE_SIZE); + } + } + if (ctrl->event) { + if (ctrl->event_port >= 0 && ctrl->ring) + xc_evtchn_notify(ctrl->event, ctrl->event_port); + xc_evtchn_close(ctrl->event); + } + if (ctrl->is_server) { + if (ctrl->gntshr) + xc_gntshr_close(ctrl->gntshr); + } else { + if (ctrl->gnttab) + xc_gnttab_close(ctrl->gnttab); + } + free(ctrl); +} |