[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH v2 1/2] xen-sndfront: add sound frontend driver
This is Para-virtual sound driver. Frontend driver registers an virtual sound card and sends an PCM streams to the backend driver. Backend driver is an user-space application and uses ALSA with dmix plugin to play audio. Signed-off-by: Oleksandr Dmytryshyn <oleksandr.dmytryshyn@xxxxxxxxxxxxxxx> Signed-off-by: Iurii Konovalenko <iurii.konovalenko@xxxxxxxxxxxxxxx> --- include/xen/interface/io/sndif.h | 208 ++++++ sound/drivers/Kconfig | 10 + sound/drivers/Makefile | 2 + sound/drivers/xen-sndfront.c | 1478 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1698 insertions(+) create mode 100644 include/xen/interface/io/sndif.h create mode 100644 sound/drivers/xen-sndfront.c diff --git a/include/xen/interface/io/sndif.h b/include/xen/interface/io/sndif.h new file mode 100644 index 0000000..38a5f1e --- /dev/null +++ b/include/xen/interface/io/sndif.h @@ -0,0 +1,208 @@ +/****************************************************************************** + * sndif.h + * + * Unified sound-device I/O interface for Xen guest OSes. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (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. + * + * Copyright (C) 2013-2015 GlobalLogic Inc. + */ + +#ifndef __XEN_PUBLIC_IO_SNDIF_H__ +#define __XEN_PUBLIC_IO_SNDIF_H__ + +#include <xen/interface/io/ring.h> +#include <xen/interface/grant_table.h> + +/* + * Feature and Parameter Negotiation + * ================================= + * The two halves of a Xen vsnd driver utilize nodes within the XenStore to + * communicate capabilities and to negotiate operating parameters. This + * section enumerates these nodes which reside in the respective front and + * backend portions of the XenStore, following the XenBus convention. + * + * All data in the XenStore is stored as strings. Nodes specifying numeric + * values are encoded in decimal. Integer value ranges listed below are + * expressed as fixed sized integer types capable of storing the conversion + * of a properly formated node string, without loss of information. + * + * Any specified default value is in effect if the corresponding XenBus node + * is not present in the XenStore. + * + * XenStore nodes in sections marked "PRIVATE" are solely for use by the + * driver side whose XenBus tree contains them. + * + ***************************************************************************** + * Backend XenBus Nodes + ***************************************************************************** + * + *------------------ Backend Device Identification (PRIVATE) ------------------ + * + * stream_id + * Values: <uint32_t> + * + * Virtuelized stream number + * + ***************************************************************************** + * Frontend XenBus Nodes + ***************************************************************************** + * + *----------------------- Request Transport Parameters ----------------------- + * + * event-channel + * Values: <uint32_t> + * + * The identifier of the Xen event channel used to signal activity + * in the ring buffer. + * + * ring-ref + * Values: <uint32_t> + * Notes: 6 + * + * The Xen grant reference granting permission for the backend to map + * the sole page in a single page sized ring buffer. + */ + +/* + * PCM FORMATS. + */ +#define SNDIF_PCM_FORMAT_S8 (0) +#define SNDIF_PCM_FORMAT_U8 (1) +#define SNDIF_PCM_FORMAT_S16_LE (2) +#define SNDIF_PCM_FORMAT_S16_BE (3) +#define SNDIF_PCM_FORMAT_U16_LE (4) +#define SNDIF_PCM_FORMAT_U16_BE (5) + +/* low three bytes */ +#define SNDIF_PCM_FORMAT_S24_LE (6) + +/* low three bytes */ +#define SNDIF_PCM_FORMAT_S24_BE (7) + +/* low three bytes */ +#define SNDIF_PCM_FORMAT_U24_LE (8) + +/* low three bytes */ +#define SNDIF_PCM_FORMAT_U24_BE (9) + +#define SNDIF_PCM_FORMAT_S32_LE (10) +#define SNDIF_PCM_FORMAT_S32_BE (11) +#define SNDIF_PCM_FORMAT_U32_LE (12) +#define SNDIF_PCM_FORMAT_U32_BE (13) + +/* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDIF_PCM_FORMAT_FLOAT_LE (14) + +/* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDIF_PCM_FORMAT_FLOAT_BE (15) + +/* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDIF_PCM_FORMAT_FLOAT64_LE (16) + +/* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDIF_PCM_FORMAT_FLOAT64_BE (17) + +/* IEC-958 subframe, Little Endian */ +#define SNDIF_PCM_FORMAT_IEC958_SUBFRAME_LE (18) + +/* IEC-958 subframe, Big Endian */ +#define SNDIF_PCM_FORMAT_IEC958_SUBFRAME_BE (19) + +#define SNDIF_PCM_FORMAT_MU_LAW (20) +#define SNDIF_PCM_FORMAT_A_LAW (21) +#define SNDIF_PCM_FORMAT_IMA_ADPCM (22) +#define SNDIF_PCM_FORMAT_MPEG (23) +#define SNDIF_PCM_FORMAT_GSM (24) +#define SNDIF_PCM_FORMAT_SPECIAL (31) + +/* + * REQUEST CODES. + */ +#define SNDIF_OP_OPEN 0 +#define SNDIF_OP_CLOSE 1 +#define SNDIF_OP_READ 2 +#define SNDIF_OP_WRITE 3 +#define SNDIF_SET_VOLUME 4 +#define SNDIF_GET_VOLUME 5 + +#define SNDIF_MAX_PAGES_PER_REQUEST 10 + +/* + * STATUS RETURN CODES. + */ + /* Operation failed for some unspecified reason (-EIO). */ +#define SNDIF_RSP_ERROR -1 + /* Operation completed successfully. */ +#define SNDIF_RSP_OKAY 0 + +struct snd_params { + uint32_t format; /* SNDIF_PCM_FORMAT_??? */ + uint32_t channels; /* Channels count */ + uint32_t rate; /* Data rate */ +}; + +struct sndif_request_common { + uint64_t id; /* private guest value, echoed in resp */ + struct snd_params _pad1; + uint32_t _pad2; + uint32_t _pad3; +} __attribute__((__packed__)); + +struct sndif_request_open { + uint64_t id; /* private guest value, echoed in resp */ + struct snd_params snd_params; + uint32_t stream; + uint32_t _pad2; +} __attribute__((__packed__)); + +struct sndif_request_rw { + uint64_t id; /* private guest value, echoed in resp */ + struct snd_params _pad1; + uint32_t len; + uint32_t _pad2; + grant_ref_t gref[SNDIF_MAX_PAGES_PER_REQUEST]; +} __attribute__((__packed__)); + +struct sndif_request_volume { + uint64_t id; /* private guest value, echoed in resp */ + struct snd_params _pad1; + uint32_t left; + uint32_t right; +} __attribute__((__packed__)); + +struct sndif_request { + uint8_t operation; /* SNDIF_OP_??? */ + union { + struct sndif_request_common common; + struct sndif_request_open open; + struct sndif_request_rw rw; + struct sndif_request_volume vol; + } u; +} __attribute__((__packed__)); + +struct sndif_response { + uint64_t id; /* copied from request */ + uint8_t operation; /* copied from request */ + int16_t status; /* SNDIF_RSP_??? */ +}; + +DEFINE_RING_TYPES(sndif, struct sndif_request, struct sndif_response); + +#endif /* __XEN_PUBLIC_IO_SNDIF_H__ */ diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 8545da9..cd3db5a 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -24,6 +24,16 @@ config SND_AC97_CODEC select AC97_BUS select SND_VMASTER +config XEN_SND_FRONTEND + tristate "Xen virtual audio front-end driver support" + depends on SND && XEN_DOMU + default n + select SND_PCM + help + This driver implements the front-end of the Xen virtual + audio driver. It communicates with a back-end + in another domain. + menuconfig SND_DRIVERS bool "Generic sound devices" default y diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index 1a8440c..f9f7e19 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -11,6 +11,7 @@ snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o snd-virmidi-objs := virmidi.o snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o +xen-sndfrontend-objs := xen-sndfront.o # Toplevel Module Dependency obj-$(CONFIG_SND_DUMMY) += snd-dummy.o @@ -21,5 +22,6 @@ obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o obj-$(CONFIG_SND_MTS64) += snd-mts64.o obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o +obj-$(CONFIG_XEN_SND_FRONTEND) += xen-sndfrontend.o obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ diff --git a/sound/drivers/xen-sndfront.c b/sound/drivers/xen-sndfront.c new file mode 100644 index 0000000..d0367c2 --- /dev/null +++ b/sound/drivers/xen-sndfront.c @@ -0,0 +1,1478 @@ +/* + * 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. + * + * Copyright (C) 2013-2015 GlobalLogic Inc. + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/hrtimer.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/pcm.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/initval.h> + +#include <linux/uaccess.h> + +#include <xen/xen.h> +#include <xen/events.h> +#include <xen/page.h> +#include <xen/grant_table.h> +#include <xen/xenbus.h> +#include <xen/interface/grant_table.h> + +#include <xen/interface/io/protocols.h> +#include <xen/interface/io/sndif.h> + +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual soundcard}}"); + +#define VSND_WAIT_ANSWER_TOUT 5000 + +#define MAX_PCM_DEVICES 1 +#define MAX_PCM_SUBSTREAMS 1 +#define MAX_BUFFER_SIZE (64*1024) + +/* defaults */ +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE (4*1024) +#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) +#define USE_RATE (SNDRV_PCM_RATE_CONTINUOUS |\ + SNDRV_PCM_RATE_8000_48000) +#define USE_RATE_MIN 5500 +#define USE_RATE_MAX 48000 +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_PERIODS_MIN 1 +#define USE_PERIODS_MAX 1024 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static char *model[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = NULL}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for virtual soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for virtual soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this virtual soundcard."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Soundcard model."); + +#define MIXER_ADDR_MASTER_IN 0 +#define MIXER_ADDR_MASTER_OUT 1 +#define MIXER_ADDR_LAST MIXER_ADDR_MASTER_OUT + +struct vsnd_card { + struct sndfront_info *fr_info; + unsigned int stream_id; + grant_ref_t grefs[SNDIF_MAX_PAGES_PER_REQUEST]; + unsigned char *buf; +}; + +#define SND_RING_SIZE __CONST_RING_SIZE(sndif, PAGE_SIZE) + +enum sndif_state { + SNDIF_STATE_DISCONNECTED, + SNDIF_STATE_CONNECTED, + SNDIF_STATE_SUSPENDED, +}; + +struct sndfront_info { + struct mutex mutex; /* protect sndfront closing state */ + struct completion completion; + spinlock_t io_lock; /* protect 'connected' member */ + struct xenbus_device *xbdev; + enum sndif_state connected; + int ring_ref; + struct sndif_front_ring ring; + unsigned int evtchn, irq; + struct vsnd_card *vcard; + int bret_code; + struct platform_device *card_dev; +}; + +#define GRANT_INVALID_REF 0 + +struct virtualcard_model { + const char *name; + u64 formats; + size_t buffer_bytes_max; + size_t period_bytes_min; + size_t period_bytes_max; + unsigned int periods_min; + unsigned int periods_max; + unsigned int rates; + unsigned int rate_min; + unsigned int rate_max; + unsigned int channels_min; + unsigned int channels_max; +}; + +struct stream_info { + snd_pcm_uframes_t position; /* Current position */ + snd_pcm_uframes_t crossed; /* Number of crossed writes*/ + snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */ + unsigned int rate; /* rate in Hz */ + unsigned int channels; /* channels */ + bool opened; /* opened status */ +}; + +struct snd_virtualcard { + struct snd_card *card; + struct virtualcard_model *model; + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_hw; + spinlock_t mixer_lock; /* protect mixer settings */ + int mixer_volume[MIXER_ADDR_LAST+1][2]; + int capture_source[MIXER_ADDR_LAST+1][2]; + struct sndfront_info *fr_info; + struct stream_info streams[2]; +}; + +/* + * card models + */ + +struct virtualcard_model model_ac97 = { + .name = "ac97", + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE), + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, +}; + +struct virtualcard_model *virtualcard_models[] = { + &model_ac97, + NULL +}; + +/* + * PCM interface + */ + +static struct snd_pcm_hardware virtualcard_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static inline +struct stream_info *get_vcard_stream(struct snd_virtualcard *virtualcard, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return &virtualcard->streams[0]; + else + return &virtualcard->streams[1]; +} + +static unsigned long vmalloc_to_mfn(void *address) +{ + return pfn_to_mfn(vmalloc_to_pfn(address)); +} + +static inline void flush_requests(struct sndfront_info *info) +{ + int notify; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->ring, notify); + + if (notify) + notify_remote_via_irq(info->irq); +} + +static int sndif_queue_request_open(struct sndfront_info *info, + snd_pcm_format_t format, + unsigned int channels, + unsigned int rate) +{ + struct sndif_request *req; + + if (unlikely(info->connected != SNDIF_STATE_CONNECTED)) + return 1; + + req = RING_GET_REQUEST(&info->ring, info->ring.req_prod_pvt); + + req->operation = SNDIF_OP_OPEN; + req->u.open.id = info->vcard->stream_id; + req->u.open.snd_params.format = format; + req->u.open.snd_params.channels = channels; + req->u.open.snd_params.rate = rate; + info->ring.req_prod_pvt++; + + flush_requests(info); + return 0; +} + +static int sndif_queue_request_close(struct sndfront_info *info) +{ + struct sndif_request *req; + + if (unlikely(info->connected != SNDIF_STATE_CONNECTED)) + return 1; + + req = RING_GET_REQUEST(&info->ring, info->ring.req_prod_pvt); + + req->operation = SNDIF_OP_CLOSE; + req->u.open.id = info->vcard->stream_id; + + info->ring.req_prod_pvt++; + + flush_requests(info); + return 0; +} + +static int sndif_queue_request_write(struct sndfront_info *info, + unsigned int len) +{ + struct sndif_request *req; + grant_ref_t *gref; + int i; + + if (unlikely(info->connected != SNDIF_STATE_CONNECTED)) + return 1; + + req = RING_GET_REQUEST(&info->ring, info->ring.req_prod_pvt); + + req->operation = SNDIF_OP_WRITE; + req->u.rw.id = info->vcard->stream_id; + + req->u.rw.len = len; + + gref = info->vcard->grefs; + + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + req->u.rw.gref[i] = gref[i]; + + info->ring.req_prod_pvt++; + + flush_requests(info); + return 0; +} + +static int alsa_pcm_open(struct sndfront_info *info, + snd_pcm_format_t format, + unsigned int channels, + unsigned int rate) +{ + unsigned long answer_tout; + + reinit_completion(&info->completion); + + if (sndif_queue_request_open(info, format, channels, rate)) + return -EIO; + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) + return -ETIMEDOUT; + + return info->bret_code; +} + +static int alsa_pcm_close(struct sndfront_info *info) +{ + unsigned long answer_tout; + + reinit_completion(&info->completion); + + if (sndif_queue_request_close(info)) + return -EIO; + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) + return -ETIMEDOUT; + + return info->bret_code; +} + +static int alsa_pcm_write(struct sndfront_info *info, char __user *buf, + int len) +{ + unsigned char *shared_data; + unsigned long answer_tout; + + shared_data = info->vcard->buf; + + if (len > PAGE_SIZE * SNDIF_MAX_PAGES_PER_REQUEST) + return -EFAULT; + + if (copy_from_user(shared_data, buf, len)) + return -EFAULT; + + reinit_completion(&info->completion); + + if (sndif_queue_request_write(info, len)) + return -EIO; + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) + return -ETIMEDOUT; + + return info->bret_code; +} + +static int alsa_pcm_silence(struct sndfront_info *info, int len) +{ + unsigned char *shared_data; + unsigned long answer_tout; + + shared_data = info->vcard->buf; + + if (len > PAGE_SIZE * SNDIF_MAX_PAGES_PER_REQUEST) + return -EFAULT; + + memset(shared_data, 0, len); + + reinit_completion(&info->completion); + + if (sndif_queue_request_write(info, len)) + return -EIO; + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) + return -ETIMEDOUT; + + return info->bret_code; +} + +static int sndif_setup_vcard(struct sndfront_info *info) +{ + grant_ref_t gref_head; + unsigned long mfn; + int ref; + int i; + int ret; + + info->vcard->buf = vmalloc(SNDIF_MAX_PAGES_PER_REQUEST * PAGE_SIZE); + if (!info->vcard->buf) { + ret = -ENOMEM; + goto err_ret; + } + + ret = gnttab_alloc_grant_references(SNDIF_MAX_PAGES_PER_REQUEST, + &gref_head); + if (ret) + goto err_vstream_free_buf; + + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) { + ref = gnttab_claim_grant_reference(&gref_head); + BUG_ON(ref == -ENOSPC); + + mfn = vmalloc_to_mfn(info->vcard->buf + PAGE_SIZE * i); + + gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, + mfn, 0); + + info->vcard->grefs[i] = ref; + } + + gnttab_free_grant_references(gref_head); + return ret; + +err_vstream_free_buf: + vfree(info->vcard->buf); + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + gnttab_end_foreign_access(info->vcard->grefs[i], 0, 0UL); + + gnttab_free_grant_references(gref_head); +err_ret: + return ret; +} + +static void sndif_cleanup_vcard(struct sndfront_info *info) +{ + int i; + + if (info->vcard->buf) { + vfree(info->vcard->buf); + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + gnttab_end_foreign_access(info->vcard->grefs[i], + 0, 0UL); + } +} + +static int sndif_add_virt_devices(struct sndfront_info *info, + unsigned int stream_id) +{ + int ret = 0; + + struct vsnd_card *vcard; + + vcard = kmalloc(sizeof(*vcard), GFP_KERNEL); + + if (!vcard) + return -ENOMEM; + + vcard->stream_id = stream_id; + vcard->fr_info = info; + + info->vcard = vcard; + + sndif_setup_vcard(info); + + return ret; +} + +static void sndif_cleanup_virt_devices(struct sndfront_info *info) +{ + if (info->vcard) { + sndif_cleanup_vcard(info); + kfree(info->vcard); + } +} + +static void sndif_free(struct sndfront_info *info, int suspend) +{ + /* Free resources associated with old device channel. */ + if (info->ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->ring_ref, 0, + (unsigned long)info->ring.sring); + info->ring_ref = GRANT_INVALID_REF; + info->ring.sring = NULL; + } + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->evtchn = 0; + info->irq = 0; +} + +static irqreturn_t sndif_interrupt(int irq, void *data) +{ + struct sndif_response *bret; + RING_IDX i, rp; + unsigned long flags; + struct sndfront_info *info = (struct sndfront_info *)data; + int error; + + spin_lock_irqsave(&info->io_lock, flags); + + if (unlikely(info->connected != SNDIF_STATE_CONNECTED)) { + spin_unlock_irqrestore(&info->io_lock, flags); + return IRQ_HANDLED; + } + + again: + rp = info->ring.sring->rsp_prod; + rmb(); /* Ensure we see queued responses up to 'rp'. */ + + for (i = info->ring.rsp_cons; i != rp; i++) { + unsigned long id; + + bret = RING_GET_RESPONSE(&info->ring, i); + id = bret->id; + + error = (bret->status == SNDIF_RSP_OKAY) ? 0 : -EIO; + switch (bret->operation) { + case SNDIF_OP_OPEN: + case SNDIF_OP_CLOSE: + case SNDIF_OP_WRITE: + if (unlikely(bret->status != SNDIF_RSP_OKAY)) + dev_dbg(&info->xbdev->dev, + "snddev data request error: %x\n", + bret->status); + + info->bret_code = bret->status; + complete(&info->completion); + break; + + default: + BUG(); + } + } + + info->ring.rsp_cons = i; + + if (i != info->ring.req_prod_pvt) { + int more_to_do; + + RING_FINAL_CHECK_FOR_RESPONSES(&info->ring, more_to_do); + if (more_to_do) + goto again; + } else { + info->ring.sring->rsp_event = i + 1; + } + + spin_unlock_irqrestore(&info->io_lock, flags); + return IRQ_HANDLED; +} + +static int setup_sndring(struct xenbus_device *dev, + struct sndfront_info *info) +{ + struct sndif_sring *sring; + int err; + + info->ring_ref = GRANT_INVALID_REF; + + sring = (struct sndif_sring *)__get_free_page(GFP_NOIO | __GFP_HIGH); + if (!sring) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating shared ring"); + return -ENOMEM; + } + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&info->ring, sring, PAGE_SIZE); + + err = xenbus_grant_ring(dev, virt_to_mfn(info->ring.sring)); + if (err < 0) { + free_page((unsigned long)sring); + info->ring.sring = NULL; + goto fail; + } + info->ring_ref = err; + + err = xenbus_alloc_evtchn(dev, &info->evtchn); + if (err) + goto fail; + + err = bind_evtchn_to_irqhandler(info->evtchn, sndif_interrupt, 0, + "sndif", info); + if (err <= 0) { + xenbus_dev_fatal(dev, err, + "bind_evtchn_to_irqhandler failed"); + goto fail; + } + info->irq = err; + + return 0; +fail: + sndif_free(info, 0); + return err; +} + +/* Common code used when first setting up, and when resuming. */ +static int talk_to_sndback(struct xenbus_device *dev, + struct sndfront_info *info) +{ + const char *message = NULL; + struct xenbus_transaction xbt; + int err; + + /* Create shared ring, alloc event channel. */ + err = setup_sndring(dev, info); + if (err) + goto out; + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + goto destroy_sndring; + } + + err = xenbus_printf(xbt, dev->nodename, + "ring-ref", "%u", info->ring_ref); + if (err) { + message = "writing ring-ref"; + goto abort_transaction; + } + err = xenbus_printf(xbt, dev->nodename, + "event-channel", "%u", info->evtchn); + if (err) { + message = "writing event-channel"; + goto abort_transaction; + } + + err = xenbus_transaction_end(xbt, 0); + if (err) { + if (err == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, err, "completing transaction"); + goto destroy_sndring; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + + return 0; + + abort_transaction: + xenbus_transaction_end(xbt, 1); + if (message) + xenbus_dev_fatal(dev, err, "%s", message); + destroy_sndring: + sndif_free(info, 0); + out: + return err; +} + +static int virtualcard_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + runtime->stop_threshold = runtime->buffer_size + 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + runtime->stop_threshold = runtime->buffer_size; + break; + } + return 0; +} + +static int virtualcard_pcm_prepare(struct snd_pcm_substream *substream) +{ + int err; + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stream_info *vcard_stream; + + vcard_stream = get_vcard_stream(virtualcard, substream); + + if ((runtime->rate != vcard_stream->rate) || + (runtime->channels != vcard_stream->channels) || + (runtime->format != vcard_stream->format)) { + if (vcard_stream->opened) { + err = alsa_pcm_close(virtualcard->fr_info); + if (err) + return err; + + /* if closed successfully */ + vcard_stream->opened = false; + } + err = alsa_pcm_open(virtualcard->fr_info, runtime->format, + runtime->channels, runtime->rate); + if (err) + return err; + + /* if opened successfully */ + vcard_stream->rate = runtime->rate; + vcard_stream->channels = runtime->channels; + vcard_stream->format = runtime->format; + vcard_stream->opened = true; + } + return 0; +} + +static +snd_pcm_uframes_t virtualcard_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + snd_pcm_uframes_t buff_size = substream->runtime->buffer_size; + struct stream_info *vcard_stream; + + vcard_stream = get_vcard_stream(virtualcard, substream); + + if (vcard_stream->crossed) { + snd_pcm_uframes_t hw_base = substream->runtime->hw_ptr_base; + + hw_base += buff_size; + if (hw_base >= substream->runtime->boundary) + hw_base = 0; + + substream->runtime->hw_ptr_base = hw_base; + vcard_stream->crossed = 0; + } + return vcard_stream->position; +} + +static int virtualcard_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return 0; +} + +static int virtualcard_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int virtualcard_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stream_info *vcard_stream; + + vcard_stream = get_vcard_stream(virtualcard, substream); + + vcard_stream->channels = 0; + vcard_stream->rate = 0; + vcard_stream->position = 0; + vcard_stream->crossed = 0; + vcard_stream->format = 0; + vcard_stream->opened = false; + + runtime->hw = virtualcard->pcm_hw; + runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE); + runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED; + return 0; +} + +static int virtualcard_pcm_close(struct snd_pcm_substream *substream) +{ + int err; + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + + err = alsa_pcm_close(virtualcard->fr_info); + if (err) + return err; + + get_vcard_stream(virtualcard, substream)->opened = false; + + return 0; +} + +static int virtualcard_pcm_playback_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *src, + snd_pcm_uframes_t count) +{ + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stream_info *vcard_stream = &virtualcard->streams[0]; + snd_pcm_uframes_t stream_pos = vcard_stream->position; + + vcard_stream->position = (stream_pos + count) % runtime->buffer_size; + vcard_stream->crossed = count / runtime->buffer_size; + return alsa_pcm_write(virtualcard->fr_info, src, + frames_to_bytes(runtime, count)); +} + +static int virtualcard_pcm_playback_silence(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stream_info *vcard_stream = &virtualcard->streams[0]; + snd_pcm_uframes_t stream_pos = vcard_stream->position; + + vcard_stream->position = (stream_pos + count) % runtime->buffer_size; + vcard_stream->crossed = count / runtime->buffer_size; + + return alsa_pcm_silence(virtualcard->fr_info, + frames_to_bytes(runtime, count)); +} + +static int virtualcard_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *dst, + snd_pcm_uframes_t count) +{ + struct snd_virtualcard *virtualcard = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stream_info *vcard_stream = &virtualcard->streams[1]; + snd_pcm_uframes_t stream_pos = vcard_stream->position; + + vcard_stream->position = (stream_pos + count) % runtime->buffer_size; + vcard_stream->crossed = count / runtime->buffer_size; + + return 0; +} + +static struct snd_pcm_ops virtualcard_pcm_playback_ops = { + .open = virtualcard_pcm_open, + .close = virtualcard_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = virtualcard_pcm_hw_params, + .hw_free = virtualcard_pcm_hw_free, + .prepare = virtualcard_pcm_prepare, + .trigger = virtualcard_pcm_trigger, + .pointer = virtualcard_pcm_pointer, + .copy = virtualcard_pcm_playback_copy, + .silence = virtualcard_pcm_playback_silence, +}; + +static struct snd_pcm_ops virtualcard_pcm_capture_ops = { + .open = virtualcard_pcm_open, + .close = virtualcard_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = virtualcard_pcm_hw_params, + .hw_free = virtualcard_pcm_hw_free, + .prepare = virtualcard_pcm_prepare, + .trigger = virtualcard_pcm_trigger, + .pointer = virtualcard_pcm_pointer, + .copy = virtualcard_pcm_capture_copy, +}; + +static int snd_card_virtualcard_pcm(struct snd_virtualcard *virtualcard, + int device, int substreams) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(virtualcard->card, "Virtual card PCM", device, + substreams, substreams, &pcm); + if (err < 0) + return err; + virtualcard->pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &virtualcard_pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &virtualcard_pcm_capture_ops); + pcm->private_data = virtualcard; + pcm->info_flags = 0; + strcpy(pcm->name, "Virtual card PCM"); + + return 0; +} + +/* + * mixer interface + */ + +#define VIRTUALCARD_VOLUME(xname, xindex, addr) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, \ + .index = xindex, \ + .info = snd_virtualcard_volume_info, \ + .get = snd_virtualcard_volume_get, \ + .put = snd_virtualcard_volume_put, \ + .private_value = addr, \ + .tlv = { .p = db_scale_virtualcard } \ +} + +static int snd_virtualcard_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = -50; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_virtualcard_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_virtualcard *virtualcard = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + spin_lock_irq(&virtualcard->mixer_lock); + ucontrol->value.integer.value[0] = virtualcard->mixer_volume[addr][0]; + ucontrol->value.integer.value[1] = virtualcard->mixer_volume[addr][1]; + spin_unlock_irq(&virtualcard->mixer_lock); + return 0; +} + +static int snd_virtualcard_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_virtualcard *virtualcard = snd_kcontrol_chip(kcontrol); + int change, addr = kcontrol->private_value; + int left, right; + + left = ucontrol->value.integer.value[0]; + if (left < -50) + left = -50; + if (left > 100) + left = 100; + right = ucontrol->value.integer.value[1]; + if (right < -50) + right = -50; + if (right > 100) + right = 100; + spin_lock_irq(&virtualcard->mixer_lock); + change = virtualcard->mixer_volume[addr][0] != left || + virtualcard->mixer_volume[addr][1] != right; + virtualcard->mixer_volume[addr][0] = left; + virtualcard->mixer_volume[addr][1] = right; + spin_unlock_irq(&virtualcard->mixer_lock); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_virtualcard, -4500, 30, 0); + +#define VIRTUALCARD_CAPSRC(xname, xindex, addr) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_virtualcard_capsrc_info, \ + .get = snd_virtualcard_capsrc_get, \ + .put = snd_virtualcard_capsrc_put, \ + .private_value = addr \ +} + +#define snd_virtualcard_capsrc_info snd_ctl_boolean_stereo_info + +static int snd_virtualcard_capsrc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_virtualcard *virtualcard = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + spin_lock_irq(&virtualcard->mixer_lock); + ucontrol->value.integer.value[0] = virtualcard->capture_source[addr][0]; + ucontrol->value.integer.value[1] = virtualcard->capture_source[addr][1]; + spin_unlock_irq(&virtualcard->mixer_lock); + return 0; +} + +static int snd_virtualcard_capsrc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_virtualcard *virtualcard = snd_kcontrol_chip(kcontrol); + int change, addr = kcontrol->private_value; + int left, right; + + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + spin_lock_irq(&virtualcard->mixer_lock); + change = virtualcard->capture_source[addr][0] != left && + virtualcard->capture_source[addr][1] != right; + virtualcard->capture_source[addr][0] = left; + virtualcard->capture_source[addr][1] = right; + spin_unlock_irq(&virtualcard->mixer_lock); + return change; +} + +static struct snd_kcontrol_new snd_virtualcard_controls[] = { +VIRTUALCARD_VOLUME("Master Out Volume", 0, MIXER_ADDR_MASTER_OUT), +VIRTUALCARD_CAPSRC("Master Out Switch", 0, MIXER_ADDR_MASTER_OUT), +VIRTUALCARD_VOLUME("Master In Volume", 0, MIXER_ADDR_MASTER_IN), +VIRTUALCARD_CAPSRC("Master In Switch", 0, MIXER_ADDR_MASTER_IN), +}; + +static int snd_card_virtualcard_new_mixer(struct snd_virtualcard *virtualcard) +{ + struct snd_card *card = virtualcard->card; + struct snd_kcontrol *kcontrol; + unsigned int idx; + int err; + + spin_lock_init(&virtualcard->mixer_lock); + strcpy(card->mixername, "Virtual card Mixer"); + + for (idx = 0; idx < ARRAY_SIZE(snd_virtualcard_controls); idx++) { + kcontrol = snd_ctl_new1(&snd_virtualcard_controls[idx], + virtualcard); + err = snd_ctl_add(card, kcontrol); + if (err < 0) + return err; + } + return 0; +} + +/* #define CONFIG_SND_DEBUG */ +/* #define CONFIG_PROC_FS */ +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_PROC_FS) +/* + * proc interface + */ +static void print_formats(struct snd_virtualcard *virtualcard, + struct snd_info_buffer *buffer) +{ + int i; + + for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) { + if (virtualcard->pcm_hw.formats & (1ULL << i)) + snd_iprintf(buffer, " %s", snd_pcm_format_name(i)); + } +} + +static void print_rates(struct snd_virtualcard *virtualcard, + struct snd_info_buffer *buffer) +{ + static int rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 64000, 88200, 96000, 176400, 192000, + }; + int i; + + if (virtualcard->pcm_hw.rates & SNDRV_PCM_RATE_CONTINUOUS) + snd_iprintf(buffer, " continuous"); + if (virtualcard->pcm_hw.rates & SNDRV_PCM_RATE_KNOT) + snd_iprintf(buffer, " knot"); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (virtualcard->pcm_hw.rates & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); +} + +#define get_virtualcard_int_ptr(virtualcard, ofs) \ + (unsigned int *)((char *)&((virtualcard)->pcm_hw) + (ofs)) +#define get_virtualcard_ll_ptr(virtualcard, ofs) \ + (unsigned long long *)((char *)&((virtualcard)->pcm_hw) + (ofs)) + +struct virtualcard_hw_field { + const char *name; + const char *format; + unsigned int offset; + unsigned int size; +}; + +#define FIELD_ENTRY(item, fmt) { \ + .name = #item, \ + .format = fmt, \ + .offset = offsetof(struct snd_pcm_hardware, item), \ + .size = sizeof(virtualcard_pcm_hardware.item) \ +} + +static struct virtualcard_hw_field fields[] = { + FIELD_ENTRY(formats, "%#llx"), + FIELD_ENTRY(rates, "%#x"), + FIELD_ENTRY(rate_min, "%d"), + FIELD_ENTRY(rate_max, "%d"), + FIELD_ENTRY(channels_min, "%d"), + FIELD_ENTRY(channels_max, "%d"), + FIELD_ENTRY(buffer_bytes_max, "%ld"), + FIELD_ENTRY(period_bytes_min, "%ld"), + FIELD_ENTRY(period_bytes_max, "%ld"), + FIELD_ENTRY(periods_min, "%d"), + FIELD_ENTRY(periods_max, "%d"), +}; + +static void virtualcard_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_virtualcard *virtualcard = entry->private_data; + int i; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + snd_iprintf(buffer, "%s ", fields[i].name); + if (fields[i].size == sizeof(int)) + snd_iprintf(buffer, fields[i].format, + *get_virtualcard_int_ptr(virtualcard, + fields[i].offset)); + else + snd_iprintf(buffer, fields[i].format, + *get_virtualcard_ll_ptr(virtualcard, + fields[i].offset)); + if (!strcmp(fields[i].name, "formats")) + print_formats(virtualcard, buffer); + else if (!strcmp(fields[i].name, "rates")) + print_rates(virtualcard, buffer); + snd_iprintf(buffer, "\n"); + } +} + +static void virtualcard_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_virtualcard *virtualcard = entry->private_data; + char line[64]; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + char item[20]; + const char *ptr; + unsigned long long val; + unsigned int offset; + int i; + + ptr = snd_info_get_str(item, line, sizeof(item)); + for (i = 0; i < ARRAY_SIZE(fields); i++) { + if (!strcmp(item, fields[i].name)) + break; + } + if (i >= ARRAY_SIZE(fields)) + continue; + snd_info_get_str(item, ptr, sizeof(item)); + if (kstrtoull(item, 0, &val)) + continue; + + offset = fields[i].offset; + if (fields[i].size == sizeof(int)) + *get_virtualcard_int_ptr(virtualcard, offset) = val; + else + *get_virtualcard_ll_ptr(virtualcard, offset) = val; + } +} + +static void virtualcard_proc_init(struct snd_virtualcard *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "virtualcard_pcm", &entry)) { + snd_info_set_text_ops(entry, chip, virtualcard_proc_read); + entry->c.text.write = virtualcard_proc_write; + entry->mode |= S_IWUSR; + entry->private_data = chip; + } +} +#else +#define virtualcard_proc_init(x) +#endif /* CONFIG_SND_DEBUG && CONFIG_PROC_FS */ + +static int snd_virtualcard_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_virtualcard *virtualcard; + struct virtualcard_model *m = NULL; + int err; + int dev = devptr->id; + struct snd_pcm_hardware *pcm_hw; + + err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_virtualcard), &card); + if (err < 0) + return err; + virtualcard = card->private_data; + virtualcard->card = card; + virtualcard->fr_info = + (*((struct sndfront_info **)devptr->dev.platform_data)); + + m = virtualcard_models[0]; + + err = snd_card_virtualcard_pcm(virtualcard, 0, 1); + if (err < 0) + goto __nodev; + + virtualcard->pcm_hw = virtualcard_pcm_hardware; + pcm_hw = &virtualcard->pcm_hw; + if (m) { + if (m->formats) + pcm_hw->formats = m->formats; + if (m->buffer_bytes_max) + pcm_hw->buffer_bytes_max = m->buffer_bytes_max; + if (m->period_bytes_min) + pcm_hw->period_bytes_min = m->period_bytes_min; + if (m->period_bytes_max) + pcm_hw->period_bytes_max = m->period_bytes_max; + if (m->periods_min) + pcm_hw->periods_min = m->periods_min; + if (m->periods_max) + pcm_hw->periods_max = m->periods_max; + if (m->rates) + pcm_hw->rates = m->rates; + if (m->rate_min) + pcm_hw->rate_min = m->rate_min; + if (m->rate_max) + pcm_hw->rate_max = m->rate_max; + if (m->channels_min) + pcm_hw->channels_min = m->channels_min; + if (m->channels_max) + pcm_hw->channels_max = m->channels_max; + } + + err = snd_card_virtualcard_new_mixer(virtualcard); + if (err < 0) + goto __nodev; + strcpy(card->driver, "Virtual card"); + strcpy(card->shortname, "Virtual card"); + sprintf(card->longname, "Virtual card %i", dev + 1); + + virtualcard_proc_init(virtualcard); + + snd_card_set_dev(card, &devptr->dev); + + err = snd_card_register(card); + if (err == 0) { + platform_set_drvdata(devptr, card); + return 0; + } +__nodev: + snd_card_free(card); + return err; +} + +static int snd_virtualcard_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + platform_set_drvdata(devptr, NULL); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_virtualcard_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_virtualcard *virtualcard = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(virtualcard->pcm); + return 0; +} + +static int snd_virtualcard_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_virtualcard_pm, snd_virtualcard_suspend, + snd_virtualcard_resume); +#define SND_VIRTUALCARD_PM_OPS (&snd_virtualcard_pm) +#else +#define SND_VIRTUALCARD_PM_OPS NULL +#endif + +#define SND_VIRTUALCARD_DRIVER "snd_virtualcard" + +static struct platform_driver snd_virtualcard_driver = { + .probe = snd_virtualcard_probe, + .remove = snd_virtualcard_remove, + .driver = { + .name = SND_VIRTUALCARD_DRIVER, + .owner = THIS_MODULE, + .pm = SND_VIRTUALCARD_PM_OPS, + }, +}; + +/** + * Entry point to this code when a new device is created. Allocate the basic + * structures and the ring buffer for communication with the backend, and + * inform the backend of the appropriate details for those. Switch to + * Initialised state. + */ +static int sndfront_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int err; + struct sndfront_info *info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure"); + return -ENOMEM; + } + + mutex_init(&info->mutex); + spin_lock_init(&info->io_lock); + init_completion(&info->completion); + info->xbdev = dev; + + dev_set_drvdata(&dev->dev, info); + + err = talk_to_sndback(dev, info); + if (err) + goto err_free_info; + + info->card_dev = platform_device_register_data(NULL, + SND_VIRTUALCARD_DRIVER, + -1, &info, sizeof(info)); + if (IS_ERR(info->card_dev)) { + err = -ENODEV; + goto err_free_info; + } + return 0; + +err_free_info: + kfree(info); + dev_set_drvdata(&dev->dev, NULL); + return err; +} + +/** + * We are reconnecting to the backend, due to a suspend/resume, or a backend + * driver restart. We tear down our blkif structure and recreate it, but + * leave the device-layer structures intact so that this is transparent to the + * rest of the kernel. + */ +static int sndfront_resume(struct xenbus_device *dev) +{ + struct sndfront_info *info = dev_get_drvdata(&dev->dev); + int err; + + dev_dbg(&dev->dev, "sndfront_resume: %s\n", dev->nodename); + + sndif_free(info, info->connected == SNDIF_STATE_CONNECTED); + + err = talk_to_sndback(dev, info); + if (info->connected == SNDIF_STATE_SUSPENDED && !err) + info->connected = SNDIF_STATE_CONNECTED; + + return err; +} + +static void +sndfront_closing(struct sndfront_info *info) +{ + struct xenbus_device *xbdev = info->xbdev; + + mutex_lock(&info->mutex); + + if (xbdev->state == XenbusStateClosing) { + mutex_unlock(&info->mutex); + return; + } + + mutex_unlock(&info->mutex); + + xenbus_frontend_closed(xbdev); + sndif_cleanup_virt_devices(info); +} + +/* + * Invoked when the backend is finally 'ready' (and has told produced + * the details about the physical device - #sectors, size, etc). + */ +static void sndfront_connect(struct sndfront_info *info) +{ + unsigned int stream_id; + int err; + + switch (info->connected) { + case SNDIF_STATE_CONNECTED: + + /* fall through */ + case SNDIF_STATE_SUSPENDED: + return; + + default: + break; + } + + dev_dbg(&info->xbdev->dev, "%s:%s.\n", + __func__, info->xbdev->otherend); + + xenbus_switch_state(info->xbdev, XenbusStateConnected); + + err = xenbus_gather(XBT_NIL, info->xbdev->otherend, "stream_id", "%u", + &stream_id, NULL); + if (err) + return; + + err = sndif_add_virt_devices(info, stream_id); + if (err) + return; + + spin_lock_irq(&info->io_lock); + info->connected = SNDIF_STATE_CONNECTED; + spin_unlock_irq(&info->io_lock); +} + +/** + * Callback received when the backend's state changes. + */ +static void sndback_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + struct sndfront_info *info = dev_get_drvdata(&dev->dev); + + dev_dbg(&dev->dev, "sndfront:sndback_changed to state %d.\n", + backend_state); + + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + case XenbusStateClosed: + break; + + case XenbusStateConnected: + sndfront_connect(info); + break; + + case XenbusStateClosing: + sndfront_closing(info); + break; + } +} + +static int sndfront_remove(struct xenbus_device *xbdev) +{ + struct sndfront_info *info = dev_get_drvdata(&xbdev->dev); + + dev_dbg(&xbdev->dev, "%s removed", xbdev->nodename); + + platform_device_unregister(info->card_dev); + + sndif_free(info, 0); + + return 0; +} + +static const struct xenbus_device_id xen_snd_ids[] = { + { "vsnd" }, + { "" } +}; + +static struct xenbus_driver xen_snd_driver = { + .name = "xensnd", + .ids = xen_snd_ids, + .probe = sndfront_probe, + .remove = sndfront_remove, + .resume = sndfront_resume, + .otherend_changed = sndback_changed, +}; + +static int __init xen_snd_front_init(void) +{ + int ret = 0; + + /*FIXME: xen_pv_domain() should be here, but ARM hardcoded to hvm*/ + if (!xen_domain()) + return -ENODEV; + + /* Nothing to do if running in dom0. */ + if (xen_initial_domain()) + return -ENODEV; + + ret = xenbus_register_frontend(&xen_snd_driver); + if (ret) + return ret; + + ret = platform_driver_register(&snd_virtualcard_driver); + + return ret; +} + +static void __exit xen_snd_front_cleanup(void) +{ + xenbus_unregister_driver(&xen_snd_driver); + platform_driver_unregister(&snd_virtualcard_driver); +} + +module_init(xen_snd_front_init); +module_exit(xen_snd_front_cleanup); + +MODULE_DESCRIPTION("Xen virtual audio device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:vsnd"); -- 1.9.1 _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx http://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |