[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH 1/3] xen-sndfront: add sound frontend driver
This is Para-virtual sound driver. This driver creates sound files in /dev/snd/: controlC0, pcmC0D0p, etc. Then it intercepts some IOCTLs and redirects them to the backend driver. Backend driver is build-in the kernel. It issues those IOCTLs on the real sound files and returns the result to the frontend driver. Signed-off-by: Oleksandr Dmytryshyn <oleksandr.dmytryshyn@xxxxxxxxxxxxxxx> --- include/xen/interface/io/sndif.h | 98 ++++ sound/drivers/Kconfig | 9 + sound/drivers/Makefile | 2 + sound/drivers/xen-sndfront.c | 1117 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1226 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..9ef0c41 --- /dev/null +++ b/include/xen/interface/io/sndif.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * sndif.h + * + * Unified sound-device I/O interface for Xen guest OSes. + * + */ +#ifndef __XEN_PUBLIC_IO_SNDIF_H__ +#define __XEN_PUBLIC_IO_SNDIF_H__ + +#include <xen/interface/io/ring.h> +#include <xen/interface/grant_table.h> + +/* + * REQUEST CODES. + */ +#define SNDIF_OP_OPEN 0 +#define SNDIF_OP_CLOSE 1 +#define SNDIF_OP_READ 2 +#define SNDIF_OP_WRITE 3 +#define SNDIF_OP_IOCTL 4 + +#define SNDIF_DEV_TYPE_CONTROL 0 +#define SNDIF_DEV_TYPE_STREAM_PLAY 1 +#define SNDIF_DEV_TYPE_STREAM_CAPTURE 2 + +#define SNDIF_MAX_PAGES_PER_REQUEST 10 + +#define SNDIF_DEV_ID_CNT 5 + +/* + * STATUS RETURN CODES. + */ + /* Operation failed for some unspecified reason (-EIO). */ +#define SNDIF_RSP_ERROR -1 + /* Operation completed successfully. */ +#define SNDIF_RSP_OKAY 0 + +struct sndif_request_open { + unsigned int dev_num; + unsigned int card_num; + unsigned int dev_type; + unsigned int _pad1; + uint64_t id; /* private guest value, echoed in resp */ + unsigned int _pad2; + unsigned int _pad3; +} __attribute__((__packed__)); + +struct sndif_request_ioctl { + unsigned int dev_num; + unsigned int card_num; + unsigned int dev_type; + unsigned int cmd; + uint64_t id; /* private guest value, echoed in resp */ + unsigned int add_len_to; + unsigned int add_len_from; + grant_ref_t gref[SNDIF_MAX_PAGES_PER_REQUEST]; +} __attribute__((__packed__)); + +struct sndif_request_rw { + unsigned int dev_num; + unsigned int card_num; + unsigned int dev_type; + unsigned int _pad1; + uint64_t id; /* private guest value, echoed in resp */ + unsigned int len; + unsigned int is_write; + grant_ref_t gref[SNDIF_MAX_PAGES_PER_REQUEST]; +} __attribute__((__packed__)); + +struct sndif_request_common { + unsigned int dev_num; + unsigned int _pad2; + unsigned int dev_type; + unsigned int _pad3; + uint64_t id; /* private guest value, echoed in resp */ + unsigned int _pad4; + unsigned int _pad5; +} __attribute__((__packed__)); + +struct sndif_request { + uint8_t operation; /* SNDIF_OP_??? */ + union { + struct sndif_request_open open; + struct sndif_request_ioctl ioctl; + struct sndif_request_rw rw; + struct sndif_request_common common; + } 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..7364679 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -24,6 +24,15 @@ 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 + help + This driver implements the back-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..c7f8827 --- /dev/null +++ b/sound/drivers/xen-sndfront.c @@ -0,0 +1,1117 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation; or, when distributed + * separately from the Linux kernel or incorporated into other + * software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this source file (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/stddef.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include <sound/asound.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> + +#define VSND_MAJOR 160 +#define VSND_MINOR_CTRL 0 +#define VSND_MINOR_STREAM 32 + +#define VSND_WAIT_ANSWER_TOUT 5000 + +enum sndif_state { + SNDIF_STATE_DISCONNECTED, + SNDIF_STATE_CONNECTED, + SNDIF_STATE_SUSPENDED, +}; + +struct vsnd_stream { + dev_t dev; + struct vsnd_card *card; + unsigned int sample_bits; + unsigned int channels; + unsigned int stream_p; +}; + +struct vsnd_ctrl { + dev_t dev; + struct vsnd_card *card; +}; + +struct vsnd_card { + struct sndfront_info *fr_info; + unsigned int card_num; + unsigned int dev_num; + unsigned int dev_type; + unsigned int dev_id; + struct cdev cdev; + struct vsnd_stream *vstream; + struct vsnd_ctrl *vctrl; + grant_ref_t grefs[SNDIF_MAX_PAGES_PER_REQUEST]; + unsigned char *buf; +}; + +#define SND_RING_SIZE __CONST_RING_SIZE(sndif, PAGE_SIZE) + +struct sndfront_info { + struct mutex mutex; /* protect sndfront closing state */ + struct mutex tmp_fops_mutex; /* protect file operations */ + 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; + struct class *vsndcl; + int bret_code; + atomic_t file_refcnt; +}; + +static struct class *vsndclass; + +static const struct file_operations vsndcore_fops = { + .owner = THIS_MODULE, +}; + +#define GRANT_INVALID_REF 0 + +static unsigned long vmalloc_to_mfn(void *address) +{ + return pfn_to_mfn(vmalloc_to_pfn(address)); +} + +static char *vsound_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "vsnd/%s", dev_name(dev)); +} + +static inline +struct snd_interval *pcm_param_to_interval(struct snd_pcm_hw_params *p, int n) +{ + return &p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; +} + +static inline int pcm_param_is_interval(int p) +{ + return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && + (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); +} + +static unsigned int pcm_param_get_int(struct snd_pcm_hw_params *p, int n) +{ + if (pcm_param_is_interval(n)) { + struct snd_interval *i = pcm_param_to_interval(p, n); + + if (i->integer) + return i->max; + } + return 0; +} + +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) +{ + 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.dev_type = info->vcard->dev_type; + req->u.open.dev_num = info->vcard->dev_num; + req->u.open.card_num = info->vcard->card_num; + req->u.open.id = info->vcard->dev_id; + + 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.dev_type = info->vcard->dev_type; + req->u.open.dev_num = info->vcard->dev_num; + req->u.open.card_num = info->vcard->card_num; + req->u.open.id = info->vcard->dev_id; + + info->ring.req_prod_pvt++; + + flush_requests(info); + return 0; +} + +static int sndif_queue_request_ioctl(struct sndfront_info *info, + unsigned int cmd, + unsigned int add_len_to, + unsigned int add_len_from) +{ + 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_IOCTL; + req->u.ioctl.dev_type = info->vcard->dev_type; + req->u.ioctl.dev_num = info->vcard->dev_num; + req->u.ioctl.card_num = info->vcard->card_num; + req->u.ioctl.id = info->vcard->dev_id; + + req->u.ioctl.cmd = cmd; + req->u.ioctl.add_len_to = add_len_to; + req->u.ioctl.add_len_from = add_len_from; + + gref = info->vcard->grefs; + + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + req->u.ioctl.gref[i] = gref[i]; + + info->ring.req_prod_pvt++; + + flush_requests(info); + return 0; +} + +static int vsnd_file_open(struct inode *inode, struct file *file) +{ + struct sndfront_info *info; + struct vsnd_card *vcard; + unsigned long answer_tout; + int ret = 0; + + vcard = container_of(inode->i_cdev, struct vsnd_card, cdev); + info = vcard->fr_info; + + mutex_lock(&info->tmp_fops_mutex); + file->private_data = info; + + if (atomic_inc_return(&info->file_refcnt) > 1) + goto end_fops_handler; + + reinit_completion(&info->completion); + + if (sndif_queue_request_open(info)) { + ret = -EIO; + goto end_fops_handler; + } + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) { + ret = -ETIMEDOUT; + goto end_fops_handler; + } + + ret = info->bret_code; + +end_fops_handler: + mutex_unlock(&info->tmp_fops_mutex); + return ret; +} + +static int vsnd_file_release(struct inode *inode, struct file *file) +{ + struct sndfront_info *info; + unsigned long answer_tout; + int ret = 0; + + info = file->private_data; + + if (!info) + return -EINVAL; + + mutex_lock(&info->tmp_fops_mutex); + if (atomic_dec_return(&info->file_refcnt) > 0) + goto end_fops_handler; + + reinit_completion(&info->completion); + + if (sndif_queue_request_close(info)) { + ret = -EIO; + goto end_fops_handler; + } + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) { + ret = -ETIMEDOUT; + goto end_fops_handler; + } + + ret = info->bret_code; + +end_fops_handler: + mutex_unlock(&info->tmp_fops_mutex); + return ret; +} + +static +long vsnd_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned char *shared_data; + struct sndfront_info *info; + int datalen = 0; + int ret = 0; + int ioctl_dir; + unsigned int add_len_to = 0; + unsigned int frames; + struct snd_pcm_hw_params *hw_params; + struct snd_xferi *esnd_xferi; + struct vsnd_stream *vstream; + struct snd_ctl_elem_list *elist = NULL; + unsigned int elist_cnt = 0; + unsigned int add_len_from = 0; + unsigned long answer_tout; + + info = file->private_data; + + if (!info) + return -EINVAL; + + mutex_lock(&info->tmp_fops_mutex); + + shared_data = info->vcard->buf; + + ioctl_dir = (cmd >> _IOC_DIRSHIFT) & _IOC_DIRMASK; + datalen = (cmd >> _IOC_SIZESHIFT) & _IOC_SIZEMASK; + + if (datalen > PAGE_SIZE * SNDIF_MAX_PAGES_PER_REQUEST) { + ret = -EFAULT; + goto end_fops_handler; + } + + if (datalen && (ioctl_dir & _IOC_WRITE)) { + if (copy_from_user(shared_data, (void __user *)arg, datalen)) { + ret = -EIO; + goto end_fops_handler; + } + /* Wait data to be visible to the other end */ + wmb(); + } + + switch (cmd) { + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + esnd_xferi = (struct snd_xferi *)shared_data; + vstream = info->vcard->vstream; + frames = esnd_xferi->frames; + add_len_to = vstream->channels * vstream->sample_bits / 8; + add_len_to *= frames; + if (add_len_to > + PAGE_SIZE * SNDIF_MAX_PAGES_PER_REQUEST - datalen) { + ret = -EFAULT; + goto end_fops_handler; + } + + if (copy_from_user(shared_data + datalen, + esnd_xferi->buf, add_len_to)) { + ret = -EIO; + goto end_fops_handler; + } + /* Wait data to be visible to the other end */ + wmb(); + break; + + case SNDRV_CTL_IOCTL_ELEM_LIST: + elist = (struct snd_ctl_elem_list *)shared_data; + elist_cnt = elist->count; + + if (!elist_cnt) + break; + + add_len_from = elist_cnt * sizeof(struct snd_ctl_elem_id); + + break; + } + + if (add_len_from > PAGE_SIZE * SNDIF_MAX_PAGES_PER_REQUEST - datalen) { + ret = -EFAULT; + goto end_fops_handler; + } + + reinit_completion(&info->completion); + + if (sndif_queue_request_ioctl(info, cmd, add_len_to, 0)) { + ret = -EIO; + goto end_fops_handler; + } + + answer_tout = msecs_to_jiffies(VSND_WAIT_ANSWER_TOUT); + if (wait_for_completion_interruptible_timeout(&info->completion, + answer_tout) <= 0) { + ret = -ETIMEDOUT; + goto end_fops_handler; + } + + ret = info->bret_code; + + if (ret) + goto end_fops_handler; + + if (datalen && (ioctl_dir & _IOC_READ)) { + /* Wait data to be available from the other end */ + rmb(); + if (copy_to_user((void __user *)arg, shared_data, datalen)) { + ret = -EIO; + goto end_fops_handler; + } + } + + switch (cmd) { + case SNDRV_PCM_IOCTL_HW_PARAMS: + /* Get PCM hw parameters */ + hw_params = (struct snd_pcm_hw_params *)shared_data; + vstream = info->vcard->vstream; + vstream->channels = + pcm_param_get_int(hw_params, + SNDRV_PCM_HW_PARAM_CHANNELS); + vstream->sample_bits = + pcm_param_get_int(hw_params, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + break; + + case SNDRV_CTL_IOCTL_ELEM_LIST: + /* copy extra data from additional pages */ + if (!add_len_from) + break; + + /* Wait data to be available from the other end */ + rmb(); + if (copy_to_user((void __user *)elist->pids, + shared_data + datalen, add_len_from)) { + ret = -EIO; + goto end_fops_handler; + } + break; + } + +end_fops_handler: + mutex_unlock(&info->tmp_fops_mutex); + return ret; +} + +static const struct file_operations vsnd_file_ops = { + .open = vsnd_file_open, + .release = vsnd_file_release, + .unlocked_ioctl = vsnd_file_ioctl, + .owner = THIS_MODULE, +}; + +static struct vsnd_ctrl *sndif_allocate_vctrl(struct sndfront_info *info) +{ + struct vsnd_ctrl *vctrl; + unsigned int card_num; + grant_ref_t gref_head; + unsigned long mfn; + int ref; + int i; + + vctrl = kmalloc(sizeof(*vctrl), GFP_KERNEL); + + if (!vctrl) + goto err_ret; + + card_num = info->vcard->card_num; + + vctrl->card = info->vcard; + + vctrl->dev = MKDEV(VSND_MAJOR, VSND_MINOR_CTRL); + if (device_create(info->vsndcl, NULL, vctrl->dev, NULL, + "controlC%u", card_num) == NULL) + goto err_free_vctrl; + + cdev_init(&info->vcard->cdev, &vsnd_file_ops); + + if (cdev_add(&info->vcard->cdev, vctrl->dev, 1) < 0) + goto err_vctrl_dev_destroy; + + info->vcard->buf = vmalloc(SNDIF_MAX_PAGES_PER_REQUEST * PAGE_SIZE); + if (!info->vcard->buf) + goto err_vctrl_cdev_del; + + if (gnttab_alloc_grant_references(SNDIF_MAX_PAGES_PER_REQUEST, + &gref_head)) + goto err_vctrl_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 vctrl; + +err_vctrl_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_vctrl_cdev_del: + cdev_del(&info->vcard->cdev); +err_vctrl_dev_destroy: + device_destroy(info->vsndcl, vctrl->dev); +err_free_vctrl: + kfree(vctrl); +err_ret: + return vctrl; +} + +static void sndif_free_vctrl(struct sndfront_info *info) +{ + int i; + struct vsnd_card *vcard = info->vcard; + struct vsnd_ctrl *vctrl = vcard->vctrl; + + if (vctrl) { + vfree(info->vcard->buf); + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + gnttab_end_foreign_access(vcard->grefs[i], 0, 0UL); + + cdev_del(&info->vcard->cdev); + device_destroy(info->vsndcl, vctrl->dev); + kfree(vctrl); + } +} + +static struct vsnd_stream *sndif_allocate_vstream(struct sndfront_info *info) +{ + struct vsnd_stream *vstream; + unsigned int card_num; + unsigned int dev_num; + unsigned int dev_minor; + grant_ref_t gref_head; + unsigned long mfn; + int ref; + int i; + + vstream = kmalloc(sizeof(*vstream), GFP_KERNEL); + + if (!vstream) + goto err_ret; + + card_num = info->vcard->card_num; + dev_num = info->vcard->dev_num; + vstream->card = info->vcard; + + if (info->vcard->dev_type == SNDIF_DEV_TYPE_STREAM_PLAY) + vstream->stream_p = 1; + else + vstream->stream_p = 0; + + /* set default parameters */ + vstream->channels = 2; + vstream->sample_bits = 16; + + dev_minor = VSND_MINOR_STREAM + dev_num + 20 * (!!vstream->stream_p); + vstream->dev = MKDEV(VSND_MAJOR, dev_minor); + if (device_create(info->vsndcl, NULL, vstream->dev, NULL, + "pcmC%uD%u%c", card_num, dev_num, + vstream->stream_p ? 'p' : 'c') == NULL) + goto err_free_vstream; + + cdev_init(&info->vcard->cdev, &vsnd_file_ops); + + if (cdev_add(&info->vcard->cdev, vstream->dev, 1) < 0) + goto err_vstream_dev_destroy; + + info->vcard->buf = vmalloc(SNDIF_MAX_PAGES_PER_REQUEST * PAGE_SIZE); + if (!info->vcard->buf) + goto err_vstream_cdev_del; + + if (gnttab_alloc_grant_references(SNDIF_MAX_PAGES_PER_REQUEST, + &gref_head)) + 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 vstream; + +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_vstream_cdev_del: + cdev_del(&info->vcard->cdev); +err_vstream_dev_destroy: + device_destroy(info->vsndcl, vstream->dev); +err_free_vstream: + kfree(vstream); +err_ret: + return vstream; +} + +static void sndif_free_vstream(struct sndfront_info *info) +{ + int i; + struct vsnd_card *vcard = info->vcard; + struct vsnd_stream *vstream = vcard->vstream; + + if (vstream) { + vfree(info->vcard->buf); + for (i = 0; i < SNDIF_MAX_PAGES_PER_REQUEST; i++) + gnttab_end_foreign_access(vcard->grefs[i], 0, 0UL); + + cdev_del(&info->vcard->cdev); + device_destroy(info->vsndcl, vstream->dev); + kfree(vstream); + } +} + +static int sndif_add_virt_devices(struct sndfront_info *info, + unsigned int dev_type, + unsigned int dev_num, + unsigned int card_num, + unsigned int dev_id) +{ + int ret; + + struct vsnd_card *vcard; + struct vsnd_ctrl *vctrl = NULL; + struct vsnd_stream *vstream = NULL; + + vcard = kmalloc(sizeof(*vcard), GFP_KERNEL); + + if (!vcard) + return -ENOMEM; + + info->vsndcl = vsndclass; + + vcard->card_num = card_num; + vcard->dev_num = dev_num; + vcard->dev_type = dev_type; + vcard->dev_id = dev_id; + vcard->fr_info = info; + + info->vcard = vcard; + + switch (dev_type) { + case SNDIF_DEV_TYPE_CONTROL: + vctrl = sndif_allocate_vctrl(info); + + if (!vctrl) { + ret = -ENOMEM; + goto err_free_vcard; + } + break; + + case SNDIF_DEV_TYPE_STREAM_PLAY: + case SNDIF_DEV_TYPE_STREAM_CAPTURE: + vstream = sndif_allocate_vstream(info); + + if (!vstream) { + ret = -ENOMEM; + goto err_free_vcard; + } + break; + + default: + ret = -EFAULT; + goto err_free_vcard; + } + + info->vcard->vctrl = vctrl; + info->vcard->vstream = vstream; + + return ret; + +err_free_vcard: + kfree(info->vcard); + return ret; +} + +static void sndif_cleanup_virt_devices(struct sndfront_info *info) +{ + if (info->vcard) { + sndif_free_vstream(info); + sndif_free_vctrl(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_IOCTL: + 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; +} + +/** + * 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); + mutex_init(&info->tmp_fops_mutex); + spin_lock_init(&info->io_lock); + init_completion(&info->completion); + info->xbdev = dev; + atomic_set(&info->file_refcnt, 0); + + dev_set_drvdata(&dev->dev, info); + + err = talk_to_sndback(dev, info); + if (err) { + kfree(info); + dev_set_drvdata(&dev->dev, NULL); + return err; + } + + return 0; +} + +/** + * 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 dev_type; + unsigned int dev_num; + unsigned int card_num; + unsigned int dev_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); + + spin_lock_irq(&info->io_lock); + info->connected = SNDIF_STATE_CONNECTED; + spin_unlock_irq(&info->io_lock); + + err = xenbus_gather(XBT_NIL, info->xbdev->otherend, "dev_type", "%u", + &dev_type, NULL); + if (err) + return; + + err = xenbus_gather(XBT_NIL, info->xbdev->otherend, "dev_num", "%u", + &dev_num, NULL); + if (err) + return; + + err = xenbus_gather(XBT_NIL, info->xbdev->otherend, "card_num", "%u", + &card_num, NULL); + if (err) + return; + + err = xenbus_gather(XBT_NIL, info->xbdev->otherend, "dev_id", "%u", + &dev_id, NULL); + if (err) + return; + + sndif_add_virt_devices(info, dev_type, dev_num, card_num, dev_id); +} + +/** + * 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); + + 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 = register_chrdev(VSND_MAJOR, "vsnd", &vsndcore_fops); + if (ret < 0) + return ret; + + vsndclass = class_create(THIS_MODULE, "vsnd"); + if (!vsndclass) { + ret = -ENOMEM; + goto err_reg_chrdev; + } + + vsndclass->devnode = vsound_devnode; + + ret = xenbus_register_frontend(&xen_snd_driver); + if (ret < 0) + goto err_class_destroy; + + return ret; + +err_class_destroy: + class_destroy(vsndclass); +err_reg_chrdev: + unregister_chrdev(VSND_MAJOR, "vsnd"); + return ret; +} + +static void __exit xen_snd_front_cleanup(void) +{ + class_destroy(vsndclass); + unregister_chrdev(VSND_MAJOR, "vsnd"); + xenbus_unregister_driver(&xen_snd_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 |