diff options
author | Giorgos Boutsioukis <gboutsioukis@gmail.com> | 2012-02-26 15:34:09 -0800 |
---|---|---|
committer | Colin Guthrie <colin@mageia.org> | 2012-03-11 21:15:14 +0000 |
commit | 50a7bf1175eaf07521c00bde8eed2f820e64437f (patch) | |
tree | d012df4caf91fcc831b965096e5780aeef92cb3e | |
parent | e02cb7fb2e7865affed612693935c7fd698e3a6b (diff) |
xen: Add Xen paravirtualized sink support.
A part of Xen's paravirtualized audio driver has been developed as a
pulseaudio module. This module acts as a tunnel over Xen's shared memory
mechanism and allows a domU guest to send audio data to a dom0 backend.
Reference: https://bugs.freedesktop.org/show_bug.cgi?id=43503
-rw-r--r-- | configure.ac | 27 | ||||
-rw-r--r-- | src/Makefile.am | 16 | ||||
-rw-r--r-- | src/modules/xen/gntalloc.h | 88 | ||||
-rw-r--r-- | src/modules/xen/gntdev.h | 156 | ||||
-rw-r--r-- | src/modules/xen/module-xenpv-sink.c | 803 |
5 files changed, 1090 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index 5474bc8d..8370546b 100644 --- a/configure.ac +++ b/configure.ac @@ -1079,6 +1079,31 @@ AS_IF([test "x$HAVE_SPEEX" = "x1"], AC_DEFINE([HAVE_SPEEX], 1, [Have speex])) AC_SUBST(LIBSPEEX_CFLAGS) AC_SUBST(LIBSPEEX_LIBS) +#### Xen support (optional) #### + +AC_ARG_ENABLE([xen], + AS_HELP_STRING([--disable-xen],[Disable optional Xen paravirtualized driver])) + +XEN_CFLAGS= +XEN_LIBS= + +AS_IF([test "x$enable_xen" != "xno"], + [ + HAVE_XEN=1 + AC_CHECK_HEADER(xenctrl.h, [], [HAVE_XEN=0]) + AC_CHECK_HEADER(xs.h, [], [HAVE_XEN=0]) + AC_CHECK_LIB(xenctrl, xc_interface_open, [XEN_LIBS="$XEN_LIBS -lxenctrl"], [HAVE_XEN=0]) + AC_CHECK_LIB(xenstore, xs_domain_open, [XEN_LIBS="$XEN_LIBS -lxenstore"], [HAVE_XEN=0]) + ], + HAVE_XEN=0) + +AS_IF([test "x$enable_xen" = "xyes" && test "x$HAVE_XEN" = "x0"], + [AC_MSG_ERROR([*** Xen development headers or libraries not found])]) + +AC_SUBST(XEN_CFLAGS) +AC_SUBST(XEN_LIBS) +AM_CONDITIONAL([HAVE_XEN], [test "x$HAVE_XEN" = x1]) + #### ORC (optional) #### ORC_CHECK([0.4.11]) @@ -1332,6 +1357,7 @@ AS_IF([test "x$HAVE_AVAHI" = "x1"], ENABLE_AVAHI=yes, ENABLE_AVAHI=no) AS_IF([test "x$HAVE_JACK" = "x1"], ENABLE_JACK=yes, ENABLE_JACK=no) AS_IF([test "x$HAVE_LIBASYNCNS" = "x1"], ENABLE_LIBASYNCNS=yes, ENABLE_LIBASYNCNS=no) AS_IF([test "x$HAVE_LIRC" = "x1"], ENABLE_LIRC=yes, ENABLE_LIRC=no) +AS_IF([test "x$HAVE_XEN" = "x1"], ENABLE_XEN=yes, ENABLE_XEN=no) AS_IF([test "x$HAVE_DBUS" = "x1"], ENABLE_DBUS=yes, ENABLE_DBUS=no) AS_IF([test "x$HAVE_HAL" = "x1"], ENABLE_HAL=yes, ENABLE_HAL=no) AS_IF([test "x$HAVE_UDEV" = "x1"], ENABLE_UDEV=yes, ENABLE_UDEV=no) @@ -1383,6 +1409,7 @@ echo " Enable Jack: ${ENABLE_JACK} Enable Async DNS: ${ENABLE_LIBASYNCNS} Enable LIRC: ${ENABLE_LIRC} + Enable Xen PV driver: ${ENABLE_XEN} Enable D-Bus: ${ENABLE_DBUS} Enable HAL: ${ENABLE_HAL} Enable BlueZ: ${ENABLE_BLUEZ} diff --git a/src/Makefile.am b/src/Makefile.am index b07c60a3..532770d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1159,6 +1159,12 @@ modlibexec_LTLIBRARIES += \ module-lirc.la endif +if HAVE_XEN +modlibexec_LTLIBRARIES += \ + module-xenpv-sink.la +endif + + if HAVE_EVDEV modlibexec_LTLIBRARIES += \ module-mmkbd-evdev.la @@ -1269,6 +1275,7 @@ SYMDEF_FILES = \ module-zeroconf-discover-symdef.h \ module-bonjour-publish-symdef.h \ module-lirc-symdef.h \ + module-xenpv-sink-symdef.h \ module-mmkbd-evdev-symdef.h \ module-http-protocol-tcp-symdef.h \ module-http-protocol-unix-symdef.h \ @@ -1668,6 +1675,15 @@ module_lirc_la_LDFLAGS = $(MODULE_LDFLAGS) module_lirc_la_LIBADD = $(MODULE_LIBADD) $(LIRC_LIBS) module_lirc_la_CFLAGS = $(AM_CFLAGS) $(LIRC_CFLAGS) + +# Xen PV driver + +module_xenpv_sink_la_SOURCES = modules/xen/module-xenpv-sink.c modules/xen/gntalloc.h modules/xen/gntdev.h +module_xenpv_sink_la_LDFLAGS = $(MODULE_LDFLAGS) +module_xenpv_sink_la_LIBADD = $(MODULE_LIBADD) $(XEN_LIBS) +module_xenpv_sink_la_CFLAGS = $(AM_CFLAGS) $(XEN_CFLAGS) -I$(top_srcdir)/src/modules/xen + + # Linux evdev module_mmkbd_evdev_la_SOURCES = modules/module-mmkbd-evdev.c diff --git a/src/modules/xen/gntalloc.h b/src/modules/xen/gntalloc.h new file mode 100644 index 00000000..4a9921ce --- /dev/null +++ b/src/modules/xen/gntalloc.h @@ -0,0 +1,88 @@ +/* + * This file is copied from the linux kernel headers. It defines the standard + * interface to the xen/gntalloc device. + * + */ + +/****************************************************************************** + * gntalloc.h + * + * Interface to /dev/xen/gntalloc. + * + * Author: Daniel De Graaf <dgdegra@tycho.nsa.gov> + * + * This file is in the public domain. + */ + +#ifndef __LINUX_PUBLIC_GNTALLOC_H__ +#define __LINUX_PUBLIC_GNTALLOC_H__ + +/* + * Allocates a new page and creates a new grant reference. + */ +#define IOCTL_GNTALLOC_ALLOC_GREF \ +_IOC(_IOC_NONE, 'G', 5, sizeof(struct ioctl_gntalloc_alloc_gref)) +struct ioctl_gntalloc_alloc_gref { + /* IN parameters */ + /* The ID of the domain to be given access to the grants. */ + uint16_t domid; + /* Flags for this mapping */ + uint16_t flags; + /* Number of pages to map */ + uint32_t count; + /* OUT parameters */ + /* The offset to be used on a subsequent call to mmap(). */ + uint64_t index; + /* The grant references of the newly created grant, one per page */ + /* Variable size, depending on count */ + uint32_t gref_ids[1]; +}; + +#define GNTALLOC_FLAG_WRITABLE 1 + +/* + * Deallocates the grant reference, allowing the associated page to be freed if + * no other domains are using it. + */ +#define IOCTL_GNTALLOC_DEALLOC_GREF \ +_IOC(_IOC_NONE, 'G', 6, sizeof(struct ioctl_gntalloc_dealloc_gref)) +struct ioctl_gntalloc_dealloc_gref { + /* IN parameters */ + /* The offset returned in the map operation */ + uint64_t index; + /* Number of references to unmap */ + uint32_t count; +}; + +/* + * Sets up an unmap notification within the page, so that the other side can do + * cleanup if this side crashes. Required to implement cross-domain robust + * mutexes or close notification on communication channels. + * + * Each mapped page only supports one notification; multiple calls referring to + * the same page overwrite the previous notification. You must clear the + * notification prior to the IOCTL_GNTALLOC_DEALLOC_GREF if you do not want it + * to occur. + */ +#define IOCTL_GNTALLOC_SET_UNMAP_NOTIFY \ +_IOC(_IOC_NONE, 'G', 7, sizeof(struct ioctl_gntalloc_unmap_notify)) +struct ioctl_gntalloc_unmap_notify { + /* IN parameters */ + /* Offset in the file descriptor for a byte within the page (same as + * used in mmap). If using UNMAP_NOTIFY_CLEAR_BYTE, this is the byte to + * be cleared. Otherwise, it can be any byte in the page whose + * notification we are adjusting. + */ + uint64_t index; + /* Action(s) to take on unmap */ + uint32_t action; + /* Event channel to notify */ + uint32_t event_channel_port; +}; + +/* Clear (set to zero) the byte specified by index */ +#define UNMAP_NOTIFY_CLEAR_BYTE 0x1 +/* Send an interrupt on the indicated event channel */ +#define UNMAP_NOTIFY_SEND_EVENT 0x2 + +#endif /* __LINUX_PUBLIC_GNTALLOC_H__ */ diff --git a/src/modules/xen/gntdev.h b/src/modules/xen/gntdev.h new file mode 100644 index 00000000..7f65a381 --- /dev/null +++ b/src/modules/xen/gntdev.h @@ -0,0 +1,156 @@ +/* + * This file is copied from the linux kernel headers. It defines the standard + * interface to the xen/gntdev device. + * + */ + +/****************************************************************************** + * gntdev.h + * + * Interface to /dev/xen/gntdev. + * + * Copyright (c) 2007, D G Murray + * + * 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. + */ + +#ifndef __LINUX_PUBLIC_GNTDEV_H__ +#define __LINUX_PUBLIC_GNTDEV_H__ + +struct ioctl_gntdev_grant_ref { + /* The domain ID of the grant to be mapped. */ + uint32_t domid; + /* The grant reference of the grant to be mapped. */ + uint32_t ref; +}; + +/* + * Inserts the grant references into the mapping table of an instance + * of gntdev. N.B. This does not perform the mapping, which is deferred + * until mmap() is called with @index as the offset. + */ +#define IOCTL_GNTDEV_MAP_GRANT_REF \ +_IOC(_IOC_NONE, 'G', 0, sizeof(struct ioctl_gntdev_map_grant_ref)) +struct ioctl_gntdev_map_grant_ref { + /* IN parameters */ + /* The number of grants to be mapped. */ + uint32_t count; + uint32_t pad; + /* OUT parameters */ + /* The offset to be used on a subsequent call to mmap(). */ + uint64_t index; + /* Variable IN parameter. */ + /* Array of grant references, of size @count. */ + struct ioctl_gntdev_grant_ref refs[1]; +}; + +/* + * Removes the grant references from the mapping table of an instance of + * of gntdev. N.B. munmap() must be called on the relevant virtual address(es) + * before this ioctl is called, or an error will result. + */ +#define IOCTL_GNTDEV_UNMAP_GRANT_REF \ +_IOC(_IOC_NONE, 'G', 1, sizeof(struct ioctl_gntdev_unmap_grant_ref)) +struct ioctl_gntdev_unmap_grant_ref { + /* IN parameters */ + /* The offset was returned by the corresponding map operation. */ + uint64_t index; + /* The number of pages to be unmapped. */ + uint32_t count; + uint32_t pad; +}; + +/* + * Returns the offset in the driver's address space that corresponds + * to @vaddr. This can be used to perform a munmap(), followed by an + * UNMAP_GRANT_REF ioctl, where no state about the offset is retained by + * the caller. The number of pages that were allocated at the same time as + * @vaddr is returned in @count. + * + * N.B. Where more than one page has been mapped into a contiguous range, the + * supplied @vaddr must correspond to the start of the range; otherwise + * an error will result. It is only possible to munmap() the entire + * contiguously-allocated range at once, and not any subrange thereof. + */ +#define IOCTL_GNTDEV_GET_OFFSET_FOR_VADDR \ +_IOC(_IOC_NONE, 'G', 2, sizeof(struct ioctl_gntdev_get_offset_for_vaddr)) +struct ioctl_gntdev_get_offset_for_vaddr { + /* IN parameters */ + /* The virtual address of the first mapped page in a range. */ + uint64_t vaddr; + /* OUT parameters */ + /* The offset that was used in the initial mmap() operation. */ + uint64_t offset; + /* The number of pages mapped in the VM area that begins at @vaddr. */ + uint32_t count; + uint32_t pad; +}; + +/* + * Sets the maximum number of grants that may mapped at once by this gntdev + * instance. + * + * N.B. This must be called before any other ioctl is performed on the device. + */ +#define IOCTL_GNTDEV_SET_MAX_GRANTS \ +_IOC(_IOC_NONE, 'G', 3, sizeof(struct ioctl_gntdev_set_max_grants)) +struct ioctl_gntdev_set_max_grants { + /* IN parameter */ + /* The maximum number of grants that may be mapped at once. */ + uint32_t count; +}; + +/* + * Sets up an unmap notification within the page, so that the other side can do + * cleanup if this side crashes. Required to implement cross-domain robust + * mutexes or close notification on communication channels. + * + * Each mapped page only supports one notification; multiple calls referring to + * the same page overwrite the previous notification. You must clear the + * notification prior to the IOCTL_GNTALLOC_DEALLOC_GREF if you do not want it + * to occur. + */ +#define IOCTL_GNTDEV_SET_UNMAP_NOTIFY \ +_IOC(_IOC_NONE, 'G', 7, sizeof(struct ioctl_gntdev_unmap_notify)) +struct ioctl_gntdev_unmap_notify { + /* IN parameters */ + /* Offset in the file descriptor for a byte within the page (same as + * used in mmap). If using UNMAP_NOTIFY_CLEAR_BYTE, this is the byte to + * be cleared. Otherwise, it can be any byte in the page whose + * notification we are adjusting. + */ + uint64_t index; + /* Action(s) to take on unmap */ + uint32_t action; + /* Event channel to notify */ + uint32_t event_channel_port; +}; + +/* Clear (set to zero) the byte specified by index */ +#define UNMAP_NOTIFY_CLEAR_BYTE 0x1 +/* Send an interrupt on the indicated event channel */ +#define UNMAP_NOTIFY_SEND_EVENT 0x2 + +#endif /* __LINUX_PUBLIC_GNTDEV_H__ */ diff --git a/src/modules/xen/module-xenpv-sink.c b/src/modules/xen/module-xenpv-sink.c new file mode 100644 index 00000000..086917c0 --- /dev/null +++ b/src/modules/xen/module-xenpv-sink.c @@ -0,0 +1,803 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2011 George Boutsioukis for Xen + + PulseAudio 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. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "config.h" + +#include <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <sys/ioctl.h> +#include <poll.h> +#include <time.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include <sys/select.h> +#include <sys/mman.h> +#include <xenctrl.h> +#include <xs.h> + +#include "module-xenpv-sink-symdef.h" +#include "gntalloc.h" +#include "gntdev.h" + +PA_MODULE_AUTHOR("Giorgos Boutsioukis"); +PA_MODULE_DESCRIPTION("Xen PV audio sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "format=<sample format> " + "rate=<sample rate>" + "channels=<number of channels> " + "channel_map=<channel map>"); + +#define DEFAULT_SINK_NAME "xenpv_output" +#define DEFAULT_FILE_NAME "xenpv_output" + +#define STATE_UNDEFINED 9999 + +int device_id = -1; +enum xenbus_state { + XenbusStateUnknown = 0, + XenbusStateInitialising = 1, + XenbusStateInitWait = 2, + XenbusStateInitialised = 3, + XenbusStateConnected = 4, + XenbusStateClosing = 5, + XenbusStateClosed = 6, + XenbusStateReconfiguring = 7, + XenbusStateReconfigured = 8 +}; + +static const char* xenbus_names[] = { + "XenbusStateUnknown", + "XenbusStateInitialising", + "XenbusStateInitWait", + "XenbusStateInitialised", + "XenbusStateConnected", + "XenbusStateClosing", + "XenbusStateClosed", + "XenbusStateReconfiguring", + "XenbusStateReconfigured" +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; + + int write_type; +}; + +pa_sample_spec ss; +pa_channel_map map; + +/* just to test non- frame-aligned size */ +#define BUFSIZE 2047 + +struct ring { + uint32_t cons_indx, prod_indx; + uint32_t usable_buffer_space; /* kept here for convenience */ + uint8_t buffer[BUFSIZE]; +} *ioring; + +static const char* const valid_modargs[] = { + "sink_name", + "sink_properties", + "file", + "format", + "rate", + "channels", + "channel_map", + NULL +}; + +/* Xen globals*/ +xc_interface* xch; +xc_evtchn* xce; +evtchn_port_or_error_t xen_evtchn_port; +static struct xs_handle *xsh; +struct ioctl_gntalloc_alloc_gref gref; + +static int register_backend_state_watch(void); +static int wait_for_backend_state_change(void); +static int alloc_gref(struct ioctl_gntalloc_alloc_gref *gref, void **addr); +static int ring_write(struct ring *r, void *src, int length); +static int publish_spec(pa_sample_spec *ss); +static int read_backend_default_spec(pa_sample_spec *ss); +static int publish_param(const char *paramname, const char *value); +static int publish_param_int(const char *paramname, const int value); +static char* read_param(const char *paramname); + +static int set_state(int state) { + static int current_state = 0; + pa_log_debug("State transition %s->%s\n", + xenbus_names[current_state], xenbus_names[state]); + + publish_param_int("state", state); + current_state = state; + return state; +} +#define NEGOTIATION_ERROR 2 +#define NEGOTIATION_OK 1 + +/* negotiation callbacks */ +static int state_unknown_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateUnknown\n"); + set_state(XenbusStateInitialising); + + return 0; +} + +static int state_initialising_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateInitialising\n"); + set_state(XenbusStateInitialised); + return 0; +} + +static int state_initwait_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateInitWait\n"); + return 0; +} + +static int state_initialised_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateInitialised\n"); + /*Remind the backend we are ready*/ + set_state(XenbusStateInitialised); + return 0; +} + +static int state_connected_cb() { + /* The backend accepted our parameters, sweet! */ + set_state(XenbusStateConnected); + pa_log_debug("Xen audio sink: Backend state was XenbusStateConnected\n"); + return NEGOTIATION_OK; +} + +static int state_closing_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateClosing\n"); + return 0; +} + +static int state_closed_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateClosed\n"); + return 0; +} + +static int state_reconfiguring_cb() { + /* The backend rejected our sample spec */ + pa_log_debug("Xen audio sink: Backend state was XenbusStateReconfiguring\n"); + /* fall back to the backend's default parameters*/ + read_backend_default_spec(&ss); + /* backend should accept these now */ + publish_spec(&ss); + set_state(XenbusStateInitialised); + return 0; +} + +static int state_reconfigured_cb() { + pa_log_debug("Xen audio sink: Backend state was XenbusStateReconfigured\n"); + return 0; +} + +int (*state_callbacks[9])(void) = { + state_unknown_cb, + state_initialising_cb, + state_initwait_cb, + state_initialised_cb, + state_connected_cb, + state_closing_cb, + state_closed_cb, + state_reconfiguring_cb, + state_reconfigured_cb +}; + +static void xen_cleanup() { + char keybuf[64]; + /*XXX hardcoded*/ + munmap((void*)gref.index, 4096); + + set_state(XenbusStateClosing); + /* send one last event to unblock the backend */ + xc_evtchn_notify(xce, xen_evtchn_port); + /* close xen interfaces */ + xc_evtchn_close(xce); + xc_interface_close(xch); + + /* delete xenstore keys */ + publish_param_int("state", XenbusStateClosed); + snprintf(keybuf, sizeof(keybuf), "device/audio/%d", device_id); + xs_rm(xsh, 0, keybuf); + xs_daemon_close(xsh); +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + size_t n = 0; + + n += u->memchunk.length; + + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int process_render(struct userdata *u) { + pa_assert(u); + + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, ioring->usable_buffer_space, &u->memchunk); + + + pa_assert(u->memchunk.length > 0); + + xc_evtchn_notify(xce, xen_evtchn_port); + for (;;) { + ssize_t l; + void *p; + + p = pa_memblock_acquire(u->memchunk.memblock); + /* xen: write data to ring buffer & notify backend */ + l = ring_write(ioring, (uint8_t*)p + u->memchunk.index, u->memchunk.length); + + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + if (errno == EINTR) + continue; + else if (errno == EAGAIN) + return 0; + else { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + return -1; + } + + } else { + + u->memchunk.index += (size_t) l; + u->memchunk.length -= (size_t) l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + } + + return 0; + } +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + + for(;;){ + struct pollfd *pollfd; + int ret; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + if (pollfd->revents) { + if (process_render(u) < 0) + goto fail; + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + + pollfd->events = (short) (u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0); + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + pa_log_debug("Shutting down Xen..."); + xen_cleanup(); +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + struct userdata *u; + pa_modargs *ma; + pa_sink_new_data data; + int backend_state; + int ret; + char strbuf[100]; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + map = m->core->default_channel_map; + + /* user arguments override these */ + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + return 1; + } + + /* Xen Basic init */ + xsh = xs_domain_open(); + if (xsh==NULL) { + pa_log("xs_domain_open failed"); + goto fail; + } + set_state(XenbusStateUnknown); + + xch = xc_interface_open(NULL, NULL, 0); + if (xch==0) { + pa_log("xc_interface_open failed"); + goto fail; + } + + xce = xc_evtchn_open(NULL, 0); + if (xce==0) { + pa_log("xc_evtchn_open failed"); + goto fail; + } + + /* use only dom0 as the backend for now */ + xen_evtchn_port = xc_evtchn_bind_unbound_port(xce, 0); + if (xen_evtchn_port == 0) { + pa_log("xc_evtchn_bind_unbound_port failed"); + } + + /* get grant reference & map locally */ + if (alloc_gref(&gref, (void**)&ioring)) { + pa_log("alloc_gref failed"); + }; + device_id = 0; /* hardcoded for now */ + + if (register_backend_state_watch()) { + pa_log("Xen sink: register xenstore watch failed"); + }; + + publish_param_int("event-channel", xen_evtchn_port); + publish_param_int("ring-ref", gref.gref_ids[0]); + + /* let's ask for something absurd and deal with rejection */ + ss.rate = 192000; + + publish_spec(&ss); + + ret=0; + while (!ret) { + backend_state = wait_for_backend_state_change(); + if (backend_state == STATE_UNDEFINED) { + pa_log("Xen Backend is taking long to respond, still waiting..."); + continue; + } else if (backend_state == -1) { + pa_log("Error while waiting for backend: %s", strerror(errno)); + break; + goto fail; + } + ret = state_callbacks[backend_state](); + } + if (ret!=NEGOTIATION_OK) { + pa_log("Negotiation with Xen backend failed!"); + return 1; + } + + pa_sample_spec_snprint(strbuf, 100, &ss); + pa_log_debug("Negotiation ended, the result was: %s", strbuf); + + /* End of Phase 2, begin playback cycle */ + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->write_type = 0; + + /* init ring buffer */ + ioring->prod_indx = ioring->cons_indx = 0; + ioring->usable_buffer_space = BUFSIZE - BUFSIZE % pa_frame_size(&ss); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, "xensink"); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Xen PV audio sink"); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties"); + pa_sink_new_data_done(&data); + goto fail; + } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_max_request(u->sink, ioring->usable_buffer_space); + pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(ioring->usable_buffer_space, &u->sink->sample_spec)); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + + if (!(u->thread = pa_thread_new("xenpv-sink", thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); + + xen_cleanup(); + +} + + +static int alloc_gref(struct ioctl_gntalloc_alloc_gref *gref_, void **addr) { + int alloc_fd, dev_fd, rv; + + alloc_fd = open("/dev/xen/gntalloc", O_RDWR); + if (alloc_fd<=0) { + perror("Could not open /dev/xen/gntalloc! Have you loaded the xen_gntalloc module?"); + return 1; + } + + dev_fd = open("/dev/xen/gntdev", O_RDWR); + if (dev_fd<=0) { + perror("Could not open /dev/xen/gntdev! Have you loaded the xen_gntdev module?"); + return 1; + } + + /* use dom0 */ + gref_->domid = 0; + gref_->flags = GNTALLOC_FLAG_WRITABLE; + gref_->count = 1; + + rv = ioctl(alloc_fd, IOCTL_GNTALLOC_ALLOC_GREF, gref_); + if (rv) { + pa_log_debug("Xen audio sink: src-add error: %s (rv=%d)\n", strerror(errno), rv); + return rv; + } + + /*addr=NULL(default),length, prot, flags, fd, offset*/ + *addr = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, alloc_fd, gref_->index); + if (*addr == MAP_FAILED) { + *addr = 0; + pa_log_debug("Xen audio sink: mmap'ing shared page failed\n"); + return rv; + } + + pa_log_debug("Xen audio sink: Got grant #%d. Mapped locally at %Ld=%p\n", + gref_->gref_ids[0], (long long)gref_->index, *addr); + + /* skip this for now + struct ioctl_gntalloc_unmap_notify uarg = { + .index = gref->index + offsetof(struct shr_page, notifies[0]), + .action = UNMAP_NOTIFY_CLEAR_BYTE + }; + + rv = ioctl(a_fd, IOCTL_GNTALLOC_SET_UNMAP_NOTIFY, &uarg); + if (rv) + pa_log_debug("gntalloc unmap notify error: %s (rv=%d)\n", strerror(errno), rv); + */ + + close(alloc_fd); + close(dev_fd); + + return rv; +} + +#define RING_FREE_BYTES ((r->usable_buffer_space - (r->prod_indx-r->cons_indx) -1) % r->usable_buffer_space) +static int ring_write(struct ring *r, void *src, int length) { + int full = 0; + for (;;) { + /* free space may be split over the end of the buffer */ + int first_chunk_size = (r->usable_buffer_space-r->prod_indx); + int second_chunk_size = (r->cons_indx>=r->prod_indx)? (r->cons_indx) : 0; + int l, fl, sl; + + /* full? */ + if (RING_FREE_BYTES==0) { + /*XXX hardcoded*/ + if (full>=100) { + errno = EINTR; + return -1; + } + /*XXX hardcoded */ + usleep(1000); + /* should return in 100ms max; definitely not midstream */ + full++; + continue; + } + + /* calculate lengths in case of a split buffer */ + l = PA_MIN((int)RING_FREE_BYTES, length); + fl = PA_MIN(l, first_chunk_size); + sl = PA_MIN(l-fl, second_chunk_size); + + memcpy(r->buffer+r->prod_indx, src, fl); + if (sl) + memcpy(r->buffer, ((char*)src)+fl, sl); + r->prod_indx = (r->prod_indx+fl+sl) % r->usable_buffer_space; + + return sl+fl; + } +} + +static int publish_param(const char *paramname, const char *value) { + char keybuf[128], valbuf[32]; + + snprintf(keybuf, sizeof keybuf, "device/audio/%d/%s", device_id, paramname); + snprintf(valbuf, sizeof valbuf, "%s", value); + return xs_write(xsh, 0, keybuf, valbuf, strlen(valbuf)); +} + +static int publish_param_int(const char *paramname, const int value) { + char keybuf[128], valbuf[32]; + snprintf(keybuf, sizeof keybuf, "device/audio/%d/%s", device_id, paramname); + snprintf(valbuf, sizeof valbuf, "%d", value); + return xs_write(xsh, 0, keybuf, valbuf, strlen(valbuf)); +} + +static char* read_param(const char *paramname) { + char keybuf[128]; + unsigned int len; + int my_domid; + + my_domid = atoi(xs_read(xsh, 0, "domid", &len)); + snprintf(keybuf, sizeof(keybuf), "/local/domain/0/backend/audio/%d/%d/%s", my_domid, device_id, paramname); + /* remember to free lvalue! */ + return xs_read(xsh, 0, keybuf, &len); +} + + +static int publish_spec(pa_sample_spec *sample_spec) { + /* Publish spec and set state to XenbusStateInitWait*/ + int ret; + + ret = publish_param("format", pa_sample_format_to_string(sample_spec->format)); + ret += publish_param_int("rate", sample_spec->rate); + ret += publish_param_int("channels", sample_spec->channels); + + return ret; +} + + +static int read_backend_default_spec(pa_sample_spec *sample_spec) { + /* Read spec from backend */ + char *out; + + out = read_param("default-format"); + sample_spec->format = pa_parse_sample_format(out); + free(out); + + out = read_param("default-rate"); + sample_spec->rate = atoi(out); + free(out); + + out = read_param("default-channels"); + sample_spec->channels = atoi(out); + free(out); + + return 0; +} + +static int register_backend_state_watch() { + char keybuf[128]; + int my_domid; + unsigned int len; + + my_domid = atoi(xs_read(xsh, 0, "domid", &len)); + snprintf(keybuf, sizeof(keybuf), "/local/domain/0/backend/audio/%d/%d/state", my_domid, device_id); + if (!xs_watch(xsh, keybuf, "xenpvaudiofrontendsinktoken")) { + perror("xs_watch failed"); + return -EINVAL; + } + return 0; +} + +static int wait_for_backend_state_change() { + char keybuf[128]; + int my_domid; + unsigned int len; + + int backend_state; + int seconds; + char *buf, **vec; + int ret; + + int xs_fd; + struct timeval tv; + fd_set watch_fdset; + int start, now; + + backend_state = STATE_UNDEFINED; + xs_fd = xs_fileno(xsh); + start = now = time(NULL); + + my_domid = atoi(xs_read(xsh, 0, "domid", &len)); + snprintf(keybuf, sizeof(keybuf), "/local/domain/0/backend/audio/%d/%d/state", my_domid, device_id); + + /*XXX: hardcoded */ + seconds = 10; + do { + tv.tv_usec = 0; + tv.tv_sec = (start + seconds) - now; + FD_ZERO(&watch_fdset); + FD_SET(xs_fd, &watch_fdset); + ret=select(xs_fd + 1, &watch_fdset, NULL, NULL, &tv); + + if (ret==-1) + /* error */ + return -1; + else if (ret) { + + /* Read the watch to drain the buffer */ + vec = xs_read_watch(xsh, &len); + + buf = xs_read(xsh, XBT_NULL, vec[0], &len); + if (buf == 0) { + /* usually means that the backend isn't there yet */ + continue; + }; + backend_state = atoi(buf); + + free(buf); + free(vec); + } + /* else: timeout */ + } while (backend_state == STATE_UNDEFINED && \ + (now = time(NULL)) < start + seconds); + + return backend_state; +} |