[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


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.