summaryrefslogtreecommitdiffstats
path: root/sound/xen
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sound/xen/Kconfig12
-rw-r--r--sound/xen/Makefile8
-rw-r--r--sound/xen/xen_snd_front.c394
-rw-r--r--sound/xen/xen_snd_front.h54
-rw-r--r--sound/xen/xen_snd_front_alsa.c872
-rw-r--r--sound/xen/xen_snd_front_alsa.h23
-rw-r--r--sound/xen/xen_snd_front_cfg.c519
-rw-r--r--sound/xen/xen_snd_front_cfg.h46
-rw-r--r--sound/xen/xen_snd_front_evtchnl.c490
-rw-r--r--sound/xen/xen_snd_front_evtchnl.h95
10 files changed, 2513 insertions, 0 deletions
diff --git a/sound/xen/Kconfig b/sound/xen/Kconfig
new file mode 100644
index 000000000..d812eff65
--- /dev/null
+++ b/sound/xen/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# ALSA Xen drivers
+
+config SND_XEN_FRONTEND
+ tristate "Xen para-virtualized sound frontend driver"
+ depends on XEN
+ select SND_PCM
+ select XEN_XENBUS_FRONTEND
+ select XEN_FRONT_PGDIR_SHBUF
+ help
+ Choose this option if you want to enable a para-virtualized
+ frontend sound driver for Xen guest OSes.
diff --git a/sound/xen/Makefile b/sound/xen/Makefile
new file mode 100644
index 000000000..24031775b
--- /dev/null
+++ b/sound/xen/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0 OR MIT
+
+snd_xen_front-objs := xen_snd_front.o \
+ xen_snd_front_cfg.o \
+ xen_snd_front_evtchnl.o \
+ xen_snd_front_alsa.o
+
+obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o
diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c
new file mode 100644
index 000000000..228d82031
--- /dev/null
+++ b/sound/xen/xen_snd_front.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+
+#include <xen/page.h>
+#include <xen/platform_pci.h>
+#include <xen/xen.h>
+#include <xen/xenbus.h>
+
+#include <xen/xen-front-pgdir-shbuf.h>
+#include <xen/interface/io/sndif.h>
+
+#include "xen_snd_front.h"
+#include "xen_snd_front_alsa.h"
+#include "xen_snd_front_evtchnl.h"
+
+static struct xensnd_req *
+be_stream_prepare_req(struct xen_snd_front_evtchnl *evtchnl, u8 operation)
+{
+ struct xensnd_req *req;
+
+ req = RING_GET_REQUEST(&evtchnl->u.req.ring,
+ evtchnl->u.req.ring.req_prod_pvt);
+ req->operation = operation;
+ req->id = evtchnl->evt_next_id++;
+ evtchnl->evt_id = req->id;
+ return req;
+}
+
+static int be_stream_do_io(struct xen_snd_front_evtchnl *evtchnl)
+{
+ if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED))
+ return -EIO;
+
+ reinit_completion(&evtchnl->u.req.completion);
+ xen_snd_front_evtchnl_flush(evtchnl);
+ return 0;
+}
+
+static int be_stream_wait_io(struct xen_snd_front_evtchnl *evtchnl)
+{
+ if (wait_for_completion_timeout(&evtchnl->u.req.completion,
+ msecs_to_jiffies(VSND_WAIT_BACK_MS)) <= 0)
+ return -ETIMEDOUT;
+
+ return evtchnl->u.req.resp_status;
+}
+
+int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl,
+ struct xensnd_query_hw_param *hw_param_req,
+ struct xensnd_query_hw_param *hw_param_resp)
+{
+ struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_HW_PARAM_QUERY);
+ req->op.hw_param = *hw_param_req;
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ if (ret == 0)
+ *hw_param_resp = evtchnl->u.req.resp.hw_param;
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl,
+ struct xen_front_pgdir_shbuf *shbuf,
+ u8 format, unsigned int channels,
+ unsigned int rate, u32 buffer_sz,
+ u32 period_sz)
+{
+ struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_OPEN);
+ req->op.open.pcm_format = format;
+ req->op.open.pcm_channels = channels;
+ req->op.open.pcm_rate = rate;
+ req->op.open.buffer_sz = buffer_sz;
+ req->op.open.period_sz = period_sz;
+ req->op.open.gref_directory =
+ xen_front_pgdir_shbuf_get_dir_start(shbuf);
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl)
+{
+ __always_unused struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_CLOSE);
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl,
+ unsigned long pos, unsigned long count)
+{
+ struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_WRITE);
+ req->op.rw.length = count;
+ req->op.rw.offset = pos;
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl,
+ unsigned long pos, unsigned long count)
+{
+ struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_READ);
+ req->op.rw.length = count;
+ req->op.rw.offset = pos;
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl,
+ int type)
+{
+ struct xensnd_req *req;
+ int ret;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ mutex_lock(&evtchnl->ring_io_lock);
+ req = be_stream_prepare_req(evtchnl, XENSND_OP_TRIGGER);
+ req->op.trigger.type = type;
+ mutex_unlock(&evtchnl->ring_io_lock);
+
+ ret = be_stream_do_io(evtchnl);
+
+ if (ret == 0)
+ ret = be_stream_wait_io(evtchnl);
+
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ return ret;
+}
+
+static void xen_snd_drv_fini(struct xen_snd_front_info *front_info)
+{
+ xen_snd_front_alsa_fini(front_info);
+ xen_snd_front_evtchnl_free_all(front_info);
+}
+
+static int sndback_initwait(struct xen_snd_front_info *front_info)
+{
+ int num_streams;
+ int ret;
+
+ ret = xen_snd_front_cfg_card(front_info, &num_streams);
+ if (ret < 0)
+ return ret;
+
+ /* create event channels for all streams and publish */
+ ret = xen_snd_front_evtchnl_create_all(front_info, num_streams);
+ if (ret < 0)
+ return ret;
+
+ return xen_snd_front_evtchnl_publish_all(front_info);
+}
+
+static int sndback_connect(struct xen_snd_front_info *front_info)
+{
+ return xen_snd_front_alsa_init(front_info);
+}
+
+static void sndback_disconnect(struct xen_snd_front_info *front_info)
+{
+ xen_snd_drv_fini(front_info);
+ xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising);
+}
+
+static void sndback_changed(struct xenbus_device *xb_dev,
+ enum xenbus_state backend_state)
+{
+ struct xen_snd_front_info *front_info = dev_get_drvdata(&xb_dev->dev);
+ int ret;
+
+ dev_dbg(&xb_dev->dev, "Backend state is %s, front is %s\n",
+ xenbus_strstate(backend_state),
+ xenbus_strstate(xb_dev->state));
+
+ switch (backend_state) {
+ case XenbusStateReconfiguring:
+ case XenbusStateReconfigured:
+ case XenbusStateInitialised:
+ break;
+
+ case XenbusStateInitialising:
+ /* Recovering after backend unexpected closure. */
+ sndback_disconnect(front_info);
+ break;
+
+ case XenbusStateInitWait:
+ /* Recovering after backend unexpected closure. */
+ sndback_disconnect(front_info);
+
+ ret = sndback_initwait(front_info);
+ if (ret < 0)
+ xenbus_dev_fatal(xb_dev, ret, "initializing frontend");
+ else
+ xenbus_switch_state(xb_dev, XenbusStateInitialised);
+ break;
+
+ case XenbusStateConnected:
+ if (xb_dev->state != XenbusStateInitialised)
+ break;
+
+ ret = sndback_connect(front_info);
+ if (ret < 0)
+ xenbus_dev_fatal(xb_dev, ret, "initializing frontend");
+ else
+ xenbus_switch_state(xb_dev, XenbusStateConnected);
+ break;
+
+ case XenbusStateClosing:
+ /*
+ * In this state backend starts freeing resources,
+ * so let it go into closed state first, so we can also
+ * remove ours.
+ */
+ break;
+
+ case XenbusStateUnknown:
+ case XenbusStateClosed:
+ if (xb_dev->state == XenbusStateClosed)
+ break;
+
+ sndback_disconnect(front_info);
+ break;
+ }
+}
+
+static int xen_drv_probe(struct xenbus_device *xb_dev,
+ const struct xenbus_device_id *id)
+{
+ struct xen_snd_front_info *front_info;
+
+ front_info = devm_kzalloc(&xb_dev->dev,
+ sizeof(*front_info), GFP_KERNEL);
+ if (!front_info)
+ return -ENOMEM;
+
+ front_info->xb_dev = xb_dev;
+ dev_set_drvdata(&xb_dev->dev, front_info);
+
+ return xenbus_switch_state(xb_dev, XenbusStateInitialising);
+}
+
+static int xen_drv_remove(struct xenbus_device *dev)
+{
+ struct xen_snd_front_info *front_info = dev_get_drvdata(&dev->dev);
+ int to = 100;
+
+ xenbus_switch_state(dev, XenbusStateClosing);
+
+ /*
+ * On driver removal it is disconnected from XenBus,
+ * so no backend state change events come via .otherend_changed
+ * callback. This prevents us from exiting gracefully, e.g.
+ * signaling the backend to free event channels, waiting for its
+ * state to change to XenbusStateClosed and cleaning at our end.
+ * Normally when front driver removed backend will finally go into
+ * XenbusStateInitWait state.
+ *
+ * Workaround: read backend's state manually and wait with time-out.
+ */
+ while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state",
+ XenbusStateUnknown) != XenbusStateInitWait) &&
+ --to)
+ msleep(10);
+
+ if (!to) {
+ unsigned int state;
+
+ state = xenbus_read_unsigned(front_info->xb_dev->otherend,
+ "state", XenbusStateUnknown);
+ pr_err("Backend state is %s while removing driver\n",
+ xenbus_strstate(state));
+ }
+
+ xen_snd_drv_fini(front_info);
+ xenbus_frontend_closed(dev);
+ return 0;
+}
+
+static const struct xenbus_device_id xen_drv_ids[] = {
+ { XENSND_DRIVER_NAME },
+ { "" }
+};
+
+static struct xenbus_driver xen_driver = {
+ .ids = xen_drv_ids,
+ .probe = xen_drv_probe,
+ .remove = xen_drv_remove,
+ .otherend_changed = sndback_changed,
+};
+
+static int __init xen_drv_init(void)
+{
+ if (!xen_domain())
+ return -ENODEV;
+
+ if (!xen_has_pv_devices())
+ return -ENODEV;
+
+ /* At the moment we only support case with XEN_PAGE_SIZE == PAGE_SIZE */
+ if (XEN_PAGE_SIZE != PAGE_SIZE) {
+ pr_err(XENSND_DRIVER_NAME ": different kernel and Xen page sizes are not supported: XEN_PAGE_SIZE (%lu) != PAGE_SIZE (%lu)\n",
+ XEN_PAGE_SIZE, PAGE_SIZE);
+ return -ENODEV;
+ }
+
+ pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n");
+ return xenbus_register_frontend(&xen_driver);
+}
+
+static void __exit xen_drv_fini(void)
+{
+ pr_info("Unregistering Xen " XENSND_DRIVER_NAME " frontend driver\n");
+ xenbus_unregister_driver(&xen_driver);
+}
+
+module_init(xen_drv_init);
+module_exit(xen_drv_fini);
+
+MODULE_DESCRIPTION("Xen virtual sound device frontend");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("xen:" XENSND_DRIVER_NAME);
+MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual soundcard}}");
diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h
new file mode 100644
index 000000000..05611f113
--- /dev/null
+++ b/sound/xen/xen_snd_front.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#ifndef __XEN_SND_FRONT_H
+#define __XEN_SND_FRONT_H
+
+#include "xen_snd_front_cfg.h"
+
+struct xen_snd_front_card_info;
+struct xen_snd_front_evtchnl;
+struct xen_snd_front_evtchnl_pair;
+struct xen_front_pgdir_shbuf;
+struct xensnd_query_hw_param;
+
+struct xen_snd_front_info {
+ struct xenbus_device *xb_dev;
+
+ struct xen_snd_front_card_info *card_info;
+
+ int num_evt_pairs;
+ struct xen_snd_front_evtchnl_pair *evt_pairs;
+
+ struct xen_front_cfg_card cfg;
+};
+
+int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl,
+ struct xensnd_query_hw_param *hw_param_req,
+ struct xensnd_query_hw_param *hw_param_resp);
+
+int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl,
+ struct xen_front_pgdir_shbuf *shbuf,
+ u8 format, unsigned int channels,
+ unsigned int rate, u32 buffer_sz,
+ u32 period_sz);
+
+int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl);
+
+int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl,
+ unsigned long pos, unsigned long count);
+
+int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl,
+ unsigned long pos, unsigned long count);
+
+int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl,
+ int type);
+
+#endif /* __XEN_SND_FRONT_H */
diff --git a/sound/xen/xen_snd_front_alsa.c b/sound/xen/xen_snd_front_alsa.c
new file mode 100644
index 000000000..db917453a
--- /dev/null
+++ b/sound/xen/xen_snd_front_alsa.c
@@ -0,0 +1,872 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <xen/xenbus.h>
+#include <xen/xen-front-pgdir-shbuf.h>
+
+#include "xen_snd_front.h"
+#include "xen_snd_front_alsa.h"
+#include "xen_snd_front_cfg.h"
+#include "xen_snd_front_evtchnl.h"
+
+struct xen_snd_front_pcm_stream_info {
+ struct xen_snd_front_info *front_info;
+ struct xen_snd_front_evtchnl_pair *evt_pair;
+
+ /* This is the shared buffer with its backing storage. */
+ struct xen_front_pgdir_shbuf shbuf;
+ u8 *buffer;
+ size_t buffer_sz;
+ int num_pages;
+ struct page **pages;
+
+ int index;
+
+ bool is_open;
+ struct snd_pcm_hardware pcm_hw;
+
+ /* Number of processed frames as reported by the backend. */
+ snd_pcm_uframes_t be_cur_frame;
+ /* Current HW pointer to be reported via .period callback. */
+ atomic_t hw_ptr;
+ /* Modulo of the number of processed frames - for period detection. */
+ u32 out_frames;
+};
+
+struct xen_snd_front_pcm_instance_info {
+ struct xen_snd_front_card_info *card_info;
+ struct snd_pcm *pcm;
+ struct snd_pcm_hardware pcm_hw;
+ int num_pcm_streams_pb;
+ struct xen_snd_front_pcm_stream_info *streams_pb;
+ int num_pcm_streams_cap;
+ struct xen_snd_front_pcm_stream_info *streams_cap;
+};
+
+struct xen_snd_front_card_info {
+ struct xen_snd_front_info *front_info;
+ struct snd_card *card;
+ struct snd_pcm_hardware pcm_hw;
+ int num_pcm_instances;
+ struct xen_snd_front_pcm_instance_info *pcm_instances;
+};
+
+struct alsa_sndif_sample_format {
+ u8 sndif;
+ snd_pcm_format_t alsa;
+};
+
+struct alsa_sndif_hw_param {
+ u8 sndif;
+ snd_pcm_hw_param_t alsa;
+};
+
+static const struct alsa_sndif_sample_format ALSA_SNDIF_FORMATS[] = {
+ {
+ .sndif = XENSND_PCM_FORMAT_U8,
+ .alsa = SNDRV_PCM_FORMAT_U8
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S8,
+ .alsa = SNDRV_PCM_FORMAT_S8
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U16_LE,
+ .alsa = SNDRV_PCM_FORMAT_U16_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U16_BE,
+ .alsa = SNDRV_PCM_FORMAT_U16_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S16_LE,
+ .alsa = SNDRV_PCM_FORMAT_S16_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S16_BE,
+ .alsa = SNDRV_PCM_FORMAT_S16_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U24_LE,
+ .alsa = SNDRV_PCM_FORMAT_U24_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U24_BE,
+ .alsa = SNDRV_PCM_FORMAT_U24_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S24_LE,
+ .alsa = SNDRV_PCM_FORMAT_S24_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S24_BE,
+ .alsa = SNDRV_PCM_FORMAT_S24_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U32_LE,
+ .alsa = SNDRV_PCM_FORMAT_U32_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_U32_BE,
+ .alsa = SNDRV_PCM_FORMAT_U32_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S32_LE,
+ .alsa = SNDRV_PCM_FORMAT_S32_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_S32_BE,
+ .alsa = SNDRV_PCM_FORMAT_S32_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_A_LAW,
+ .alsa = SNDRV_PCM_FORMAT_A_LAW
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_MU_LAW,
+ .alsa = SNDRV_PCM_FORMAT_MU_LAW
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_F32_LE,
+ .alsa = SNDRV_PCM_FORMAT_FLOAT_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_F32_BE,
+ .alsa = SNDRV_PCM_FORMAT_FLOAT_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_F64_LE,
+ .alsa = SNDRV_PCM_FORMAT_FLOAT64_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_F64_BE,
+ .alsa = SNDRV_PCM_FORMAT_FLOAT64_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE,
+ .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE,
+ .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_IMA_ADPCM,
+ .alsa = SNDRV_PCM_FORMAT_IMA_ADPCM
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_MPEG,
+ .alsa = SNDRV_PCM_FORMAT_MPEG
+ },
+ {
+ .sndif = XENSND_PCM_FORMAT_GSM,
+ .alsa = SNDRV_PCM_FORMAT_GSM
+ },
+};
+
+static int to_sndif_format(snd_pcm_format_t format)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++)
+ if (ALSA_SNDIF_FORMATS[i].alsa == format)
+ return ALSA_SNDIF_FORMATS[i].sndif;
+
+ return -EINVAL;
+}
+
+static u64 to_sndif_formats_mask(u64 alsa_formats)
+{
+ u64 mask;
+ int i;
+
+ mask = 0;
+ for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++)
+ if (pcm_format_to_bits(ALSA_SNDIF_FORMATS[i].alsa) & alsa_formats)
+ mask |= BIT_ULL(ALSA_SNDIF_FORMATS[i].sndif);
+
+ return mask;
+}
+
+static u64 to_alsa_formats_mask(u64 sndif_formats)
+{
+ u64 mask;
+ int i;
+
+ mask = 0;
+ for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++)
+ if (BIT_ULL(ALSA_SNDIF_FORMATS[i].sndif) & sndif_formats)
+ mask |= pcm_format_to_bits(ALSA_SNDIF_FORMATS[i].alsa);
+
+ return mask;
+}
+
+static void stream_clear(struct xen_snd_front_pcm_stream_info *stream)
+{
+ stream->is_open = false;
+ stream->be_cur_frame = 0;
+ stream->out_frames = 0;
+ atomic_set(&stream->hw_ptr, 0);
+ xen_snd_front_evtchnl_pair_clear(stream->evt_pair);
+ memset(&stream->shbuf, 0, sizeof(stream->shbuf));
+ stream->buffer = NULL;
+ stream->buffer_sz = 0;
+ stream->pages = NULL;
+ stream->num_pages = 0;
+}
+
+static void stream_free(struct xen_snd_front_pcm_stream_info *stream)
+{
+ xen_front_pgdir_shbuf_unmap(&stream->shbuf);
+ xen_front_pgdir_shbuf_free(&stream->shbuf);
+ if (stream->buffer)
+ free_pages_exact(stream->buffer, stream->buffer_sz);
+ kfree(stream->pages);
+ stream_clear(stream);
+}
+
+static struct xen_snd_front_pcm_stream_info *
+stream_get(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_instance_info *pcm_instance =
+ snd_pcm_substream_chip(substream);
+ struct xen_snd_front_pcm_stream_info *stream;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &pcm_instance->streams_pb[substream->number];
+ else
+ stream = &pcm_instance->streams_cap[substream->number];
+
+ return stream;
+}
+
+static int alsa_hw_rule(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct xen_snd_front_pcm_stream_info *stream = rule->private;
+ struct device *dev = &stream->front_info->xb_dev->dev;
+ struct snd_mask *formats =
+ hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_interval *rates =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval *period =
+ hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+ struct snd_interval *buffer =
+ hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
+ struct xensnd_query_hw_param req;
+ struct xensnd_query_hw_param resp;
+ struct snd_interval interval;
+ struct snd_mask mask;
+ u64 sndif_formats;
+ int changed, ret;
+
+ /* Collect all the values we need for the query. */
+
+ req.formats = to_sndif_formats_mask((u64)formats->bits[0] |
+ (u64)(formats->bits[1]) << 32);
+
+ req.rates.min = rates->min;
+ req.rates.max = rates->max;
+
+ req.channels.min = channels->min;
+ req.channels.max = channels->max;
+
+ req.buffer.min = buffer->min;
+ req.buffer.max = buffer->max;
+
+ req.period.min = period->min;
+ req.period.max = period->max;
+
+ ret = xen_snd_front_stream_query_hw_param(&stream->evt_pair->req,
+ &req, &resp);
+ if (ret < 0) {
+ /* Check if this is due to backend communication error. */
+ if (ret == -EIO || ret == -ETIMEDOUT)
+ dev_err(dev, "Failed to query ALSA HW parameters\n");
+ return ret;
+ }
+
+ /* Refine HW parameters after the query. */
+ changed = 0;
+
+ sndif_formats = to_alsa_formats_mask(resp.formats);
+ snd_mask_none(&mask);
+ mask.bits[0] = (u32)sndif_formats;
+ mask.bits[1] = (u32)(sndif_formats >> 32);
+ ret = snd_mask_refine(formats, &mask);
+ if (ret < 0)
+ return ret;
+ changed |= ret;
+
+ interval.openmin = 0;
+ interval.openmax = 0;
+ interval.integer = 1;
+
+ interval.min = resp.rates.min;
+ interval.max = resp.rates.max;
+ ret = snd_interval_refine(rates, &interval);
+ if (ret < 0)
+ return ret;
+ changed |= ret;
+
+ interval.min = resp.channels.min;
+ interval.max = resp.channels.max;
+ ret = snd_interval_refine(channels, &interval);
+ if (ret < 0)
+ return ret;
+ changed |= ret;
+
+ interval.min = resp.buffer.min;
+ interval.max = resp.buffer.max;
+ ret = snd_interval_refine(buffer, &interval);
+ if (ret < 0)
+ return ret;
+ changed |= ret;
+
+ interval.min = resp.period.min;
+ interval.max = resp.period.max;
+ ret = snd_interval_refine(period, &interval);
+ if (ret < 0)
+ return ret;
+ changed |= ret;
+
+ return changed;
+}
+
+static int alsa_open(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_instance_info *pcm_instance =
+ snd_pcm_substream_chip(substream);
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct xen_snd_front_info *front_info =
+ pcm_instance->card_info->front_info;
+ struct device *dev = &front_info->xb_dev->dev;
+ int ret;
+
+ /*
+ * Return our HW properties: override defaults with those configured
+ * via XenStore.
+ */
+ runtime->hw = stream->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;
+
+ stream->evt_pair = &front_info->evt_pairs[stream->index];
+
+ stream->front_info = front_info;
+
+ stream->evt_pair->evt.u.evt.substream = substream;
+
+ stream_clear(stream);
+
+ xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, true);
+
+ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+ alsa_hw_rule, stream,
+ SNDRV_PCM_HW_PARAM_FORMAT, -1);
+ if (ret) {
+ dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_FORMAT\n");
+ return ret;
+ }
+
+ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ alsa_hw_rule, stream,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (ret) {
+ dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_RATE\n");
+ return ret;
+ }
+
+ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ alsa_hw_rule, stream,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (ret) {
+ dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_CHANNELS\n");
+ return ret;
+ }
+
+ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ alsa_hw_rule, stream,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
+ if (ret) {
+ dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_PERIOD_SIZE\n");
+ return ret;
+ }
+
+ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ alsa_hw_rule, stream,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+ if (ret) {
+ dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_BUFFER_SIZE\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int alsa_close(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, false);
+ return 0;
+}
+
+static int shbuf_setup_backstore(struct xen_snd_front_pcm_stream_info *stream,
+ size_t buffer_sz)
+{
+ int i;
+
+ stream->buffer = alloc_pages_exact(buffer_sz, GFP_KERNEL);
+ if (!stream->buffer)
+ return -ENOMEM;
+
+ stream->buffer_sz = buffer_sz;
+ stream->num_pages = DIV_ROUND_UP(stream->buffer_sz, PAGE_SIZE);
+ stream->pages = kcalloc(stream->num_pages, sizeof(struct page *),
+ GFP_KERNEL);
+ if (!stream->pages)
+ return -ENOMEM;
+
+ for (i = 0; i < stream->num_pages; i++)
+ stream->pages[i] = virt_to_page(stream->buffer + i * PAGE_SIZE);
+
+ return 0;
+}
+
+static int alsa_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ struct xen_snd_front_info *front_info = stream->front_info;
+ struct xen_front_pgdir_shbuf_cfg buf_cfg;
+ int ret;
+
+ /*
+ * This callback may be called multiple times,
+ * so free the previously allocated shared buffer if any.
+ */
+ stream_free(stream);
+ ret = shbuf_setup_backstore(stream, params_buffer_bytes(params));
+ if (ret < 0)
+ goto fail;
+
+ memset(&buf_cfg, 0, sizeof(buf_cfg));
+ buf_cfg.xb_dev = front_info->xb_dev;
+ buf_cfg.pgdir = &stream->shbuf;
+ buf_cfg.num_pages = stream->num_pages;
+ buf_cfg.pages = stream->pages;
+
+ ret = xen_front_pgdir_shbuf_alloc(&buf_cfg);
+ if (ret < 0)
+ goto fail;
+
+ ret = xen_front_pgdir_shbuf_map(&stream->shbuf);
+ if (ret < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ stream_free(stream);
+ dev_err(&front_info->xb_dev->dev,
+ "Failed to allocate buffers for stream with index %d\n",
+ stream->index);
+ return ret;
+}
+
+static int alsa_hw_free(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ int ret;
+
+ ret = xen_snd_front_stream_close(&stream->evt_pair->req);
+ stream_free(stream);
+ return ret;
+}
+
+static int alsa_prepare(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ if (!stream->is_open) {
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u8 sndif_format;
+ int ret;
+
+ ret = to_sndif_format(runtime->format);
+ if (ret < 0) {
+ dev_err(&stream->front_info->xb_dev->dev,
+ "Unsupported sample format: %d\n",
+ runtime->format);
+ return ret;
+ }
+ sndif_format = ret;
+
+ ret = xen_snd_front_stream_prepare(&stream->evt_pair->req,
+ &stream->shbuf,
+ sndif_format,
+ runtime->channels,
+ runtime->rate,
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream));
+ if (ret < 0)
+ return ret;
+
+ stream->is_open = true;
+ }
+
+ return 0;
+}
+
+static int alsa_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ int type;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ type = XENSND_OP_TRIGGER_START;
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ type = XENSND_OP_TRIGGER_RESUME;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ type = XENSND_OP_TRIGGER_STOP;
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ type = XENSND_OP_TRIGGER_PAUSE;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return xen_snd_front_stream_trigger(&stream->evt_pair->req, type);
+}
+
+void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl,
+ u64 pos_bytes)
+{
+ struct snd_pcm_substream *substream = evtchnl->u.evt.substream;
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ snd_pcm_uframes_t delta, new_hw_ptr, cur_frame;
+
+ cur_frame = bytes_to_frames(substream->runtime, pos_bytes);
+
+ delta = cur_frame - stream->be_cur_frame;
+ stream->be_cur_frame = cur_frame;
+
+ new_hw_ptr = (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr);
+ new_hw_ptr = (new_hw_ptr + delta) % substream->runtime->buffer_size;
+ atomic_set(&stream->hw_ptr, (int)new_hw_ptr);
+
+ stream->out_frames += delta;
+ if (stream->out_frames > substream->runtime->period_size) {
+ stream->out_frames %= substream->runtime->period_size;
+ snd_pcm_period_elapsed(substream);
+ }
+}
+
+static snd_pcm_uframes_t alsa_pointer(struct snd_pcm_substream *substream)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ return (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr);
+}
+
+static int alsa_pb_copy_user(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos, void __user *src,
+ unsigned long count)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ if (unlikely(pos + count > stream->buffer_sz))
+ return -EINVAL;
+
+ if (copy_from_user(stream->buffer + pos, src, count))
+ return -EFAULT;
+
+ return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count);
+}
+
+static int alsa_pb_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos, void *src,
+ unsigned long count)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ if (unlikely(pos + count > stream->buffer_sz))
+ return -EINVAL;
+
+ memcpy(stream->buffer + pos, src, count);
+
+ return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count);
+}
+
+static int alsa_cap_copy_user(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos, void __user *dst,
+ unsigned long count)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ int ret;
+
+ if (unlikely(pos + count > stream->buffer_sz))
+ return -EINVAL;
+
+ ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count);
+ if (ret < 0)
+ return ret;
+
+ return copy_to_user(dst, stream->buffer + pos, count) ?
+ -EFAULT : 0;
+}
+
+static int alsa_cap_copy_kernel(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos, void *dst,
+ unsigned long count)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+ int ret;
+
+ if (unlikely(pos + count > stream->buffer_sz))
+ return -EINVAL;
+
+ ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count);
+ if (ret < 0)
+ return ret;
+
+ memcpy(dst, stream->buffer + pos, count);
+
+ return 0;
+}
+
+static int alsa_pb_fill_silence(struct snd_pcm_substream *substream,
+ int channel, unsigned long pos,
+ unsigned long count)
+{
+ struct xen_snd_front_pcm_stream_info *stream = stream_get(substream);
+
+ if (unlikely(pos + count > stream->buffer_sz))
+ return -EINVAL;
+
+ memset(stream->buffer + pos, 0, count);
+
+ return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count);
+}
+
+/*
+ * FIXME: The mmaped data transfer is asynchronous and there is no
+ * ack signal from user-space when it is done. This is the
+ * reason it is not implemented in the PV driver as we do need
+ * to know when the buffer can be transferred to the backend.
+ */
+
+static const struct snd_pcm_ops snd_drv_alsa_playback_ops = {
+ .open = alsa_open,
+ .close = alsa_close,
+ .hw_params = alsa_hw_params,
+ .hw_free = alsa_hw_free,
+ .prepare = alsa_prepare,
+ .trigger = alsa_trigger,
+ .pointer = alsa_pointer,
+ .copy_user = alsa_pb_copy_user,
+ .copy_kernel = alsa_pb_copy_kernel,
+ .fill_silence = alsa_pb_fill_silence,
+};
+
+static const struct snd_pcm_ops snd_drv_alsa_capture_ops = {
+ .open = alsa_open,
+ .close = alsa_close,
+ .hw_params = alsa_hw_params,
+ .hw_free = alsa_hw_free,
+ .prepare = alsa_prepare,
+ .trigger = alsa_trigger,
+ .pointer = alsa_pointer,
+ .copy_user = alsa_cap_copy_user,
+ .copy_kernel = alsa_cap_copy_kernel,
+};
+
+static int new_pcm_instance(struct xen_snd_front_card_info *card_info,
+ struct xen_front_cfg_pcm_instance *instance_cfg,
+ struct xen_snd_front_pcm_instance_info *pcm_instance_info)
+{
+ struct snd_pcm *pcm;
+ int ret, i;
+
+ dev_dbg(&card_info->front_info->xb_dev->dev,
+ "New PCM device \"%s\" with id %d playback %d capture %d",
+ instance_cfg->name,
+ instance_cfg->device_id,
+ instance_cfg->num_streams_pb,
+ instance_cfg->num_streams_cap);
+
+ pcm_instance_info->card_info = card_info;
+
+ pcm_instance_info->pcm_hw = instance_cfg->pcm_hw;
+
+ if (instance_cfg->num_streams_pb) {
+ pcm_instance_info->streams_pb =
+ devm_kcalloc(&card_info->card->card_dev,
+ instance_cfg->num_streams_pb,
+ sizeof(struct xen_snd_front_pcm_stream_info),
+ GFP_KERNEL);
+ if (!pcm_instance_info->streams_pb)
+ return -ENOMEM;
+ }
+
+ if (instance_cfg->num_streams_cap) {
+ pcm_instance_info->streams_cap =
+ devm_kcalloc(&card_info->card->card_dev,
+ instance_cfg->num_streams_cap,
+ sizeof(struct xen_snd_front_pcm_stream_info),
+ GFP_KERNEL);
+ if (!pcm_instance_info->streams_cap)
+ return -ENOMEM;
+ }
+
+ pcm_instance_info->num_pcm_streams_pb =
+ instance_cfg->num_streams_pb;
+ pcm_instance_info->num_pcm_streams_cap =
+ instance_cfg->num_streams_cap;
+
+ for (i = 0; i < pcm_instance_info->num_pcm_streams_pb; i++) {
+ pcm_instance_info->streams_pb[i].pcm_hw =
+ instance_cfg->streams_pb[i].pcm_hw;
+ pcm_instance_info->streams_pb[i].index =
+ instance_cfg->streams_pb[i].index;
+ }
+
+ for (i = 0; i < pcm_instance_info->num_pcm_streams_cap; i++) {
+ pcm_instance_info->streams_cap[i].pcm_hw =
+ instance_cfg->streams_cap[i].pcm_hw;
+ pcm_instance_info->streams_cap[i].index =
+ instance_cfg->streams_cap[i].index;
+ }
+
+ ret = snd_pcm_new(card_info->card, instance_cfg->name,
+ instance_cfg->device_id,
+ instance_cfg->num_streams_pb,
+ instance_cfg->num_streams_cap,
+ &pcm);
+ if (ret < 0)
+ return ret;
+
+ pcm->private_data = pcm_instance_info;
+ pcm->info_flags = 0;
+ /* we want to handle all PCM operations in non-atomic context */
+ pcm->nonatomic = true;
+ strncpy(pcm->name, "Virtual card PCM", sizeof(pcm->name));
+
+ if (instance_cfg->num_streams_pb)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_drv_alsa_playback_ops);
+
+ if (instance_cfg->num_streams_cap)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_drv_alsa_capture_ops);
+
+ pcm_instance_info->pcm = pcm;
+ return 0;
+}
+
+int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info)
+{
+ struct device *dev = &front_info->xb_dev->dev;
+ struct xen_front_cfg_card *cfg = &front_info->cfg;
+ struct xen_snd_front_card_info *card_info;
+ struct snd_card *card;
+ int ret, i;
+
+ dev_dbg(dev, "Creating virtual sound card\n");
+
+ ret = snd_card_new(dev, 0, XENSND_DRIVER_NAME, THIS_MODULE,
+ sizeof(struct xen_snd_front_card_info), &card);
+ if (ret < 0)
+ return ret;
+
+ card_info = card->private_data;
+ card_info->front_info = front_info;
+ front_info->card_info = card_info;
+ card_info->card = card;
+ card_info->pcm_instances =
+ devm_kcalloc(dev, cfg->num_pcm_instances,
+ sizeof(struct xen_snd_front_pcm_instance_info),
+ GFP_KERNEL);
+ if (!card_info->pcm_instances) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ card_info->num_pcm_instances = cfg->num_pcm_instances;
+ card_info->pcm_hw = cfg->pcm_hw;
+
+ for (i = 0; i < cfg->num_pcm_instances; i++) {
+ ret = new_pcm_instance(card_info, &cfg->pcm_instances[i],
+ &card_info->pcm_instances[i]);
+ if (ret < 0)
+ goto fail;
+ }
+
+ strncpy(card->driver, XENSND_DRIVER_NAME, sizeof(card->driver));
+ strncpy(card->shortname, cfg->name_short, sizeof(card->shortname));
+ strncpy(card->longname, cfg->name_long, sizeof(card->longname));
+
+ ret = snd_card_register(card);
+ if (ret < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ snd_card_free(card);
+ return ret;
+}
+
+void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info)
+{
+ struct xen_snd_front_card_info *card_info;
+ struct snd_card *card;
+
+ card_info = front_info->card_info;
+ if (!card_info)
+ return;
+
+ card = card_info->card;
+ if (!card)
+ return;
+
+ dev_dbg(&front_info->xb_dev->dev, "Removing virtual sound card %d\n",
+ card->number);
+ snd_card_free(card);
+
+ /* Card_info will be freed when destroying front_info->xb_dev->dev. */
+ card_info->card = NULL;
+}
diff --git a/sound/xen/xen_snd_front_alsa.h b/sound/xen/xen_snd_front_alsa.h
new file mode 100644
index 000000000..18abd9eec
--- /dev/null
+++ b/sound/xen/xen_snd_front_alsa.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#ifndef __XEN_SND_FRONT_ALSA_H
+#define __XEN_SND_FRONT_ALSA_H
+
+struct xen_snd_front_info;
+
+int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info);
+
+void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info);
+
+void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl,
+ u64 pos_bytes);
+
+#endif /* __XEN_SND_FRONT_ALSA_H */
diff --git a/sound/xen/xen_snd_front_cfg.c b/sound/xen/xen_snd_front_cfg.c
new file mode 100644
index 000000000..eda077c80
--- /dev/null
+++ b/sound/xen/xen_snd_front_cfg.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#include <xen/xenbus.h>
+
+#include <xen/interface/io/sndif.h>
+
+#include "xen_snd_front.h"
+#include "xen_snd_front_cfg.h"
+
+/* Maximum number of supported streams. */
+#define VSND_MAX_STREAM 8
+
+struct cfg_hw_sample_rate {
+ const char *name;
+ unsigned int mask;
+ unsigned int value;
+};
+
+static const struct cfg_hw_sample_rate CFG_HW_SUPPORTED_RATES[] = {
+ { .name = "5512", .mask = SNDRV_PCM_RATE_5512, .value = 5512 },
+ { .name = "8000", .mask = SNDRV_PCM_RATE_8000, .value = 8000 },
+ { .name = "11025", .mask = SNDRV_PCM_RATE_11025, .value = 11025 },
+ { .name = "16000", .mask = SNDRV_PCM_RATE_16000, .value = 16000 },
+ { .name = "22050", .mask = SNDRV_PCM_RATE_22050, .value = 22050 },
+ { .name = "32000", .mask = SNDRV_PCM_RATE_32000, .value = 32000 },
+ { .name = "44100", .mask = SNDRV_PCM_RATE_44100, .value = 44100 },
+ { .name = "48000", .mask = SNDRV_PCM_RATE_48000, .value = 48000 },
+ { .name = "64000", .mask = SNDRV_PCM_RATE_64000, .value = 64000 },
+ { .name = "96000", .mask = SNDRV_PCM_RATE_96000, .value = 96000 },
+ { .name = "176400", .mask = SNDRV_PCM_RATE_176400, .value = 176400 },
+ { .name = "192000", .mask = SNDRV_PCM_RATE_192000, .value = 192000 },
+};
+
+struct cfg_hw_sample_format {
+ const char *name;
+ u64 mask;
+};
+
+static const struct cfg_hw_sample_format CFG_HW_SUPPORTED_FORMATS[] = {
+ {
+ .name = XENSND_PCM_FORMAT_U8_STR,
+ .mask = SNDRV_PCM_FMTBIT_U8
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S8_STR,
+ .mask = SNDRV_PCM_FMTBIT_S8
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U16_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U16_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U16_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U16_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S16_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S16_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S16_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S16_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U24_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U24_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U24_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U24_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S24_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S24_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S24_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S24_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U32_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U32_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_U32_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_U32_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S32_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S32_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_S32_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_S32_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_A_LAW_STR,
+ .mask = SNDRV_PCM_FMTBIT_A_LAW
+ },
+ {
+ .name = XENSND_PCM_FORMAT_MU_LAW_STR,
+ .mask = SNDRV_PCM_FMTBIT_MU_LAW
+ },
+ {
+ .name = XENSND_PCM_FORMAT_F32_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_FLOAT_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_F32_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_FLOAT_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_F64_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_FLOAT64_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_F64_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_FLOAT64_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE_STR,
+ .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE_STR,
+ .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE
+ },
+ {
+ .name = XENSND_PCM_FORMAT_IMA_ADPCM_STR,
+ .mask = SNDRV_PCM_FMTBIT_IMA_ADPCM
+ },
+ {
+ .name = XENSND_PCM_FORMAT_MPEG_STR,
+ .mask = SNDRV_PCM_FMTBIT_MPEG
+ },
+ {
+ .name = XENSND_PCM_FORMAT_GSM_STR,
+ .mask = SNDRV_PCM_FMTBIT_GSM
+ },
+};
+
+static void cfg_hw_rates(char *list, unsigned int len,
+ const char *path, struct snd_pcm_hardware *pcm_hw)
+{
+ char *cur_rate;
+ unsigned int cur_mask;
+ unsigned int cur_value;
+ unsigned int rates;
+ unsigned int rate_min;
+ unsigned int rate_max;
+ int i;
+
+ rates = 0;
+ rate_min = -1;
+ rate_max = 0;
+ while ((cur_rate = strsep(&list, XENSND_LIST_SEPARATOR))) {
+ for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_RATES); i++)
+ if (!strncasecmp(cur_rate,
+ CFG_HW_SUPPORTED_RATES[i].name,
+ XENSND_SAMPLE_RATE_MAX_LEN)) {
+ cur_mask = CFG_HW_SUPPORTED_RATES[i].mask;
+ cur_value = CFG_HW_SUPPORTED_RATES[i].value;
+ rates |= cur_mask;
+ if (rate_min > cur_value)
+ rate_min = cur_value;
+ if (rate_max < cur_value)
+ rate_max = cur_value;
+ }
+ }
+
+ if (rates) {
+ pcm_hw->rates = rates;
+ pcm_hw->rate_min = rate_min;
+ pcm_hw->rate_max = rate_max;
+ }
+}
+
+static void cfg_formats(char *list, unsigned int len,
+ const char *path, struct snd_pcm_hardware *pcm_hw)
+{
+ u64 formats;
+ char *cur_format;
+ int i;
+
+ formats = 0;
+ while ((cur_format = strsep(&list, XENSND_LIST_SEPARATOR))) {
+ for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_FORMATS); i++)
+ if (!strncasecmp(cur_format,
+ CFG_HW_SUPPORTED_FORMATS[i].name,
+ XENSND_SAMPLE_FORMAT_MAX_LEN))
+ formats |= CFG_HW_SUPPORTED_FORMATS[i].mask;
+ }
+
+ if (formats)
+ pcm_hw->formats = formats;
+}
+
+#define MAX_BUFFER_SIZE (64 * 1024)
+#define MIN_PERIOD_SIZE 64
+#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE
+#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 5512
+#define USE_RATE_MAX 48000
+#define USE_CHANNELS_MIN 1
+#define USE_CHANNELS_MAX 2
+#define USE_PERIODS_MIN 2
+#define USE_PERIODS_MAX (MAX_BUFFER_SIZE / MIN_PERIOD_SIZE)
+
+static const struct snd_pcm_hardware SND_DRV_PCM_HW_DEFAULT = {
+ .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 void cfg_read_pcm_hw(const char *path,
+ struct snd_pcm_hardware *parent_pcm_hw,
+ struct snd_pcm_hardware *pcm_hw)
+{
+ char *list;
+ int val;
+ size_t buf_sz;
+ unsigned int len;
+
+ /* Inherit parent's PCM HW and read overrides from XenStore. */
+ if (parent_pcm_hw)
+ *pcm_hw = *parent_pcm_hw;
+ else
+ *pcm_hw = SND_DRV_PCM_HW_DEFAULT;
+
+ val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MIN, 0);
+ if (val)
+ pcm_hw->channels_min = val;
+
+ val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MAX, 0);
+ if (val)
+ pcm_hw->channels_max = val;
+
+ list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_RATES, &len);
+ if (!IS_ERR(list)) {
+ cfg_hw_rates(list, len, path, pcm_hw);
+ kfree(list);
+ }
+
+ list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_FORMATS, &len);
+ if (!IS_ERR(list)) {
+ cfg_formats(list, len, path, pcm_hw);
+ kfree(list);
+ }
+
+ buf_sz = xenbus_read_unsigned(path, XENSND_FIELD_BUFFER_SIZE, 0);
+ if (buf_sz)
+ pcm_hw->buffer_bytes_max = buf_sz;
+
+ /* Update configuration to match new values. */
+ if (pcm_hw->channels_min > pcm_hw->channels_max)
+ pcm_hw->channels_min = pcm_hw->channels_max;
+
+ if (pcm_hw->rate_min > pcm_hw->rate_max)
+ pcm_hw->rate_min = pcm_hw->rate_max;
+
+ pcm_hw->period_bytes_max = pcm_hw->buffer_bytes_max;
+
+ pcm_hw->periods_max = pcm_hw->period_bytes_max /
+ pcm_hw->period_bytes_min;
+}
+
+static int cfg_get_stream_type(const char *path, int index,
+ int *num_pb, int *num_cap)
+{
+ char *str = NULL;
+ char *stream_path;
+ int ret;
+
+ *num_pb = 0;
+ *num_cap = 0;
+ stream_path = kasprintf(GFP_KERNEL, "%s/%d", path, index);
+ if (!stream_path) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL);
+ if (IS_ERR(str)) {
+ ret = PTR_ERR(str);
+ str = NULL;
+ goto fail;
+ }
+
+ if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK,
+ sizeof(XENSND_STREAM_TYPE_PLAYBACK))) {
+ (*num_pb)++;
+ } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE,
+ sizeof(XENSND_STREAM_TYPE_CAPTURE))) {
+ (*num_cap)++;
+ } else {
+ ret = -EINVAL;
+ goto fail;
+ }
+ ret = 0;
+
+fail:
+ kfree(stream_path);
+ kfree(str);
+ return ret;
+}
+
+static int cfg_stream(struct xen_snd_front_info *front_info,
+ struct xen_front_cfg_pcm_instance *pcm_instance,
+ const char *path, int index, int *cur_pb, int *cur_cap,
+ int *stream_cnt)
+{
+ char *str = NULL;
+ char *stream_path;
+ struct xen_front_cfg_stream *stream;
+ int ret;
+
+ stream_path = devm_kasprintf(&front_info->xb_dev->dev,
+ GFP_KERNEL, "%s/%d", path, index);
+ if (!stream_path) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL);
+ if (IS_ERR(str)) {
+ ret = PTR_ERR(str);
+ str = NULL;
+ goto fail;
+ }
+
+ if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK,
+ sizeof(XENSND_STREAM_TYPE_PLAYBACK))) {
+ stream = &pcm_instance->streams_pb[(*cur_pb)++];
+ } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE,
+ sizeof(XENSND_STREAM_TYPE_CAPTURE))) {
+ stream = &pcm_instance->streams_cap[(*cur_cap)++];
+ } else {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ /* Get next stream index. */
+ stream->index = (*stream_cnt)++;
+ stream->xenstore_path = stream_path;
+ /*
+ * Check XenStore if PCM HW configuration exists for this stream
+ * and update if so, e.g. we inherit all values from device's PCM HW,
+ * but can still override some of the values for the stream.
+ */
+ cfg_read_pcm_hw(stream->xenstore_path,
+ &pcm_instance->pcm_hw, &stream->pcm_hw);
+ ret = 0;
+
+fail:
+ kfree(str);
+ return ret;
+}
+
+static int cfg_device(struct xen_snd_front_info *front_info,
+ struct xen_front_cfg_pcm_instance *pcm_instance,
+ struct snd_pcm_hardware *parent_pcm_hw,
+ const char *path, int node_index, int *stream_cnt)
+{
+ char *str;
+ char *device_path;
+ int ret, i, num_streams;
+ int num_pb, num_cap;
+ int cur_pb, cur_cap;
+ char node[3];
+
+ device_path = kasprintf(GFP_KERNEL, "%s/%d", path, node_index);
+ if (!device_path)
+ return -ENOMEM;
+
+ str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL);
+ if (!IS_ERR(str)) {
+ strlcpy(pcm_instance->name, str, sizeof(pcm_instance->name));
+ kfree(str);
+ }
+
+ pcm_instance->device_id = node_index;
+
+ /*
+ * Check XenStore if PCM HW configuration exists for this device
+ * and update if so, e.g. we inherit all values from card's PCM HW,
+ * but can still override some of the values for the device.
+ */
+ cfg_read_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw);
+
+ /* Find out how many streams were configured in Xen store. */
+ num_streams = 0;
+ do {
+ snprintf(node, sizeof(node), "%d", num_streams);
+ if (!xenbus_exists(XBT_NIL, device_path, node))
+ break;
+
+ num_streams++;
+ } while (num_streams < VSND_MAX_STREAM);
+
+ pcm_instance->num_streams_pb = 0;
+ pcm_instance->num_streams_cap = 0;
+ /* Get number of playback and capture streams. */
+ for (i = 0; i < num_streams; i++) {
+ ret = cfg_get_stream_type(device_path, i, &num_pb, &num_cap);
+ if (ret < 0)
+ goto fail;
+
+ pcm_instance->num_streams_pb += num_pb;
+ pcm_instance->num_streams_cap += num_cap;
+ }
+
+ if (pcm_instance->num_streams_pb) {
+ pcm_instance->streams_pb =
+ devm_kcalloc(&front_info->xb_dev->dev,
+ pcm_instance->num_streams_pb,
+ sizeof(struct xen_front_cfg_stream),
+ GFP_KERNEL);
+ if (!pcm_instance->streams_pb) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (pcm_instance->num_streams_cap) {
+ pcm_instance->streams_cap =
+ devm_kcalloc(&front_info->xb_dev->dev,
+ pcm_instance->num_streams_cap,
+ sizeof(struct xen_front_cfg_stream),
+ GFP_KERNEL);
+ if (!pcm_instance->streams_cap) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ cur_pb = 0;
+ cur_cap = 0;
+ for (i = 0; i < num_streams; i++) {
+ ret = cfg_stream(front_info, pcm_instance, device_path, i,
+ &cur_pb, &cur_cap, stream_cnt);
+ if (ret < 0)
+ goto fail;
+ }
+ ret = 0;
+
+fail:
+ kfree(device_path);
+ return ret;
+}
+
+int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info,
+ int *stream_cnt)
+{
+ struct xenbus_device *xb_dev = front_info->xb_dev;
+ struct xen_front_cfg_card *cfg = &front_info->cfg;
+ int ret, num_devices, i;
+ char node[3];
+
+ *stream_cnt = 0;
+ num_devices = 0;
+ do {
+ snprintf(node, sizeof(node), "%d", num_devices);
+ if (!xenbus_exists(XBT_NIL, xb_dev->nodename, node))
+ break;
+
+ num_devices++;
+ } while (num_devices < SNDRV_PCM_DEVICES);
+
+ if (!num_devices) {
+ dev_warn(&xb_dev->dev,
+ "No devices configured for sound card at %s\n",
+ xb_dev->nodename);
+ return -ENODEV;
+ }
+
+ /* Start from default PCM HW configuration for the card. */
+ cfg_read_pcm_hw(xb_dev->nodename, NULL, &cfg->pcm_hw);
+
+ cfg->pcm_instances =
+ devm_kcalloc(&front_info->xb_dev->dev, num_devices,
+ sizeof(struct xen_front_cfg_pcm_instance),
+ GFP_KERNEL);
+ if (!cfg->pcm_instances)
+ return -ENOMEM;
+
+ for (i = 0; i < num_devices; i++) {
+ ret = cfg_device(front_info, &cfg->pcm_instances[i],
+ &cfg->pcm_hw, xb_dev->nodename, i, stream_cnt);
+ if (ret < 0)
+ return ret;
+ }
+ cfg->num_pcm_instances = num_devices;
+ return 0;
+}
+
diff --git a/sound/xen/xen_snd_front_cfg.h b/sound/xen/xen_snd_front_cfg.h
new file mode 100644
index 000000000..2353fcc74
--- /dev/null
+++ b/sound/xen/xen_snd_front_cfg.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#ifndef __XEN_SND_FRONT_CFG_H
+#define __XEN_SND_FRONT_CFG_H
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+struct xen_snd_front_info;
+
+struct xen_front_cfg_stream {
+ int index;
+ char *xenstore_path;
+ struct snd_pcm_hardware pcm_hw;
+};
+
+struct xen_front_cfg_pcm_instance {
+ char name[80];
+ int device_id;
+ struct snd_pcm_hardware pcm_hw;
+ int num_streams_pb;
+ struct xen_front_cfg_stream *streams_pb;
+ int num_streams_cap;
+ struct xen_front_cfg_stream *streams_cap;
+};
+
+struct xen_front_cfg_card {
+ char name_short[32];
+ char name_long[80];
+ struct snd_pcm_hardware pcm_hw;
+ int num_pcm_instances;
+ struct xen_front_cfg_pcm_instance *pcm_instances;
+};
+
+int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info,
+ int *stream_cnt);
+
+#endif /* __XEN_SND_FRONT_CFG_H */
diff --git a/sound/xen/xen_snd_front_evtchnl.c b/sound/xen/xen_snd_front_evtchnl.c
new file mode 100644
index 000000000..29e0f0ea6
--- /dev/null
+++ b/sound/xen/xen_snd_front_evtchnl.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#include <xen/events.h>
+#include <xen/grant_table.h>
+#include <xen/xen.h>
+#include <xen/xenbus.h>
+
+#include "xen_snd_front.h"
+#include "xen_snd_front_alsa.h"
+#include "xen_snd_front_cfg.h"
+#include "xen_snd_front_evtchnl.h"
+
+static irqreturn_t evtchnl_interrupt_req(int irq, void *dev_id)
+{
+ struct xen_snd_front_evtchnl *channel = dev_id;
+ struct xen_snd_front_info *front_info = channel->front_info;
+ struct xensnd_resp *resp;
+ RING_IDX i, rp;
+
+ if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED))
+ return IRQ_HANDLED;
+
+ mutex_lock(&channel->ring_io_lock);
+
+again:
+ rp = channel->u.req.ring.sring->rsp_prod;
+ /* Ensure we see queued responses up to rp. */
+ rmb();
+
+ /*
+ * Assume that the backend is trusted to always write sane values
+ * to the ring counters, so no overflow checks on frontend side
+ * are required.
+ */
+ for (i = channel->u.req.ring.rsp_cons; i != rp; i++) {
+ resp = RING_GET_RESPONSE(&channel->u.req.ring, i);
+ if (resp->id != channel->evt_id)
+ continue;
+ switch (resp->operation) {
+ case XENSND_OP_OPEN:
+ case XENSND_OP_CLOSE:
+ case XENSND_OP_READ:
+ case XENSND_OP_WRITE:
+ case XENSND_OP_TRIGGER:
+ channel->u.req.resp_status = resp->status;
+ complete(&channel->u.req.completion);
+ break;
+ case XENSND_OP_HW_PARAM_QUERY:
+ channel->u.req.resp_status = resp->status;
+ channel->u.req.resp.hw_param =
+ resp->resp.hw_param;
+ complete(&channel->u.req.completion);
+ break;
+
+ default:
+ dev_err(&front_info->xb_dev->dev,
+ "Operation %d is not supported\n",
+ resp->operation);
+ break;
+ }
+ }
+
+ channel->u.req.ring.rsp_cons = i;
+ if (i != channel->u.req.ring.req_prod_pvt) {
+ int more_to_do;
+
+ RING_FINAL_CHECK_FOR_RESPONSES(&channel->u.req.ring,
+ more_to_do);
+ if (more_to_do)
+ goto again;
+ } else {
+ channel->u.req.ring.sring->rsp_event = i + 1;
+ }
+
+ mutex_unlock(&channel->ring_io_lock);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id)
+{
+ struct xen_snd_front_evtchnl *channel = dev_id;
+ struct xensnd_event_page *page = channel->u.evt.page;
+ u32 cons, prod;
+
+ if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED))
+ return IRQ_HANDLED;
+
+ mutex_lock(&channel->ring_io_lock);
+
+ prod = page->in_prod;
+ /* Ensure we see ring contents up to prod. */
+ virt_rmb();
+ if (prod == page->in_cons)
+ goto out;
+
+ /*
+ * Assume that the backend is trusted to always write sane values
+ * to the ring counters, so no overflow checks on frontend side
+ * are required.
+ */
+ for (cons = page->in_cons; cons != prod; cons++) {
+ struct xensnd_evt *event;
+
+ event = &XENSND_IN_RING_REF(page, cons);
+ if (unlikely(event->id != channel->evt_id++))
+ continue;
+
+ switch (event->type) {
+ case XENSND_EVT_CUR_POS:
+ xen_snd_front_alsa_handle_cur_pos(channel,
+ event->op.cur_pos.position);
+ break;
+ }
+ }
+
+ page->in_cons = cons;
+ /* Ensure ring contents. */
+ virt_wmb();
+
+out:
+ mutex_unlock(&channel->ring_io_lock);
+ return IRQ_HANDLED;
+}
+
+void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *channel)
+{
+ int notify;
+
+ channel->u.req.ring.req_prod_pvt++;
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&channel->u.req.ring, notify);
+ if (notify)
+ notify_remote_via_irq(channel->irq);
+}
+
+static void evtchnl_free(struct xen_snd_front_info *front_info,
+ struct xen_snd_front_evtchnl *channel)
+{
+ unsigned long page = 0;
+
+ if (channel->type == EVTCHNL_TYPE_REQ)
+ page = (unsigned long)channel->u.req.ring.sring;
+ else if (channel->type == EVTCHNL_TYPE_EVT)
+ page = (unsigned long)channel->u.evt.page;
+
+ if (!page)
+ return;
+
+ channel->state = EVTCHNL_STATE_DISCONNECTED;
+ if (channel->type == EVTCHNL_TYPE_REQ) {
+ /* Release all who still waits for response if any. */
+ channel->u.req.resp_status = -EIO;
+ complete_all(&channel->u.req.completion);
+ }
+
+ if (channel->irq)
+ unbind_from_irqhandler(channel->irq, channel);
+
+ if (channel->port)
+ xenbus_free_evtchn(front_info->xb_dev, channel->port);
+
+ /* End access and free the page. */
+ if (channel->gref != GRANT_INVALID_REF)
+ gnttab_end_foreign_access(channel->gref, 0, page);
+ else
+ free_page(page);
+
+ memset(channel, 0, sizeof(*channel));
+}
+
+void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info)
+{
+ int i;
+
+ if (!front_info->evt_pairs)
+ return;
+
+ for (i = 0; i < front_info->num_evt_pairs; i++) {
+ evtchnl_free(front_info, &front_info->evt_pairs[i].req);
+ evtchnl_free(front_info, &front_info->evt_pairs[i].evt);
+ }
+
+ kfree(front_info->evt_pairs);
+ front_info->evt_pairs = NULL;
+}
+
+static int evtchnl_alloc(struct xen_snd_front_info *front_info, int index,
+ struct xen_snd_front_evtchnl *channel,
+ enum xen_snd_front_evtchnl_type type)
+{
+ struct xenbus_device *xb_dev = front_info->xb_dev;
+ unsigned long page;
+ grant_ref_t gref;
+ irq_handler_t handler;
+ char *handler_name = NULL;
+ int ret;
+
+ memset(channel, 0, sizeof(*channel));
+ channel->type = type;
+ channel->index = index;
+ channel->front_info = front_info;
+ channel->state = EVTCHNL_STATE_DISCONNECTED;
+ channel->gref = GRANT_INVALID_REF;
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ handler_name = kasprintf(GFP_KERNEL, "%s-%s", XENSND_DRIVER_NAME,
+ type == EVTCHNL_TYPE_REQ ?
+ XENSND_FIELD_RING_REF :
+ XENSND_FIELD_EVT_RING_REF);
+ if (!handler_name) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ mutex_init(&channel->ring_io_lock);
+
+ if (type == EVTCHNL_TYPE_REQ) {
+ struct xen_sndif_sring *sring = (struct xen_sndif_sring *)page;
+
+ init_completion(&channel->u.req.completion);
+ mutex_init(&channel->u.req.req_io_lock);
+ SHARED_RING_INIT(sring);
+ FRONT_RING_INIT(&channel->u.req.ring, sring, XEN_PAGE_SIZE);
+
+ ret = xenbus_grant_ring(xb_dev, sring, 1, &gref);
+ if (ret < 0) {
+ channel->u.req.ring.sring = NULL;
+ goto fail;
+ }
+
+ handler = evtchnl_interrupt_req;
+ } else {
+ ret = gnttab_grant_foreign_access(xb_dev->otherend_id,
+ virt_to_gfn((void *)page), 0);
+ if (ret < 0)
+ goto fail;
+
+ channel->u.evt.page = (struct xensnd_event_page *)page;
+ gref = ret;
+ handler = evtchnl_interrupt_evt;
+ }
+
+ channel->gref = gref;
+
+ ret = xenbus_alloc_evtchn(xb_dev, &channel->port);
+ if (ret < 0)
+ goto fail;
+
+ ret = bind_evtchn_to_irq(channel->port);
+ if (ret < 0) {
+ dev_err(&xb_dev->dev,
+ "Failed to bind IRQ for domid %d port %d: %d\n",
+ front_info->xb_dev->otherend_id, channel->port, ret);
+ goto fail;
+ }
+
+ channel->irq = ret;
+
+ ret = request_threaded_irq(channel->irq, NULL, handler,
+ IRQF_ONESHOT, handler_name, channel);
+ if (ret < 0) {
+ dev_err(&xb_dev->dev, "Failed to request IRQ %d: %d\n",
+ channel->irq, ret);
+ goto fail;
+ }
+
+ kfree(handler_name);
+ return 0;
+
+fail:
+ if (page)
+ free_page(page);
+ kfree(handler_name);
+ dev_err(&xb_dev->dev, "Failed to allocate ring: %d\n", ret);
+ return ret;
+}
+
+int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info,
+ int num_streams)
+{
+ struct xen_front_cfg_card *cfg = &front_info->cfg;
+ struct device *dev = &front_info->xb_dev->dev;
+ int d, ret = 0;
+
+ front_info->evt_pairs =
+ kcalloc(num_streams,
+ sizeof(struct xen_snd_front_evtchnl_pair),
+ GFP_KERNEL);
+ if (!front_info->evt_pairs)
+ return -ENOMEM;
+
+ /* Iterate over devices and their streams and create event channels. */
+ for (d = 0; d < cfg->num_pcm_instances; d++) {
+ struct xen_front_cfg_pcm_instance *pcm_instance;
+ int s, index;
+
+ pcm_instance = &cfg->pcm_instances[d];
+
+ for (s = 0; s < pcm_instance->num_streams_pb; s++) {
+ index = pcm_instance->streams_pb[s].index;
+
+ ret = evtchnl_alloc(front_info, index,
+ &front_info->evt_pairs[index].req,
+ EVTCHNL_TYPE_REQ);
+ if (ret < 0) {
+ dev_err(dev, "Error allocating control channel\n");
+ goto fail;
+ }
+
+ ret = evtchnl_alloc(front_info, index,
+ &front_info->evt_pairs[index].evt,
+ EVTCHNL_TYPE_EVT);
+ if (ret < 0) {
+ dev_err(dev, "Error allocating in-event channel\n");
+ goto fail;
+ }
+ }
+
+ for (s = 0; s < pcm_instance->num_streams_cap; s++) {
+ index = pcm_instance->streams_cap[s].index;
+
+ ret = evtchnl_alloc(front_info, index,
+ &front_info->evt_pairs[index].req,
+ EVTCHNL_TYPE_REQ);
+ if (ret < 0) {
+ dev_err(dev, "Error allocating control channel\n");
+ goto fail;
+ }
+
+ ret = evtchnl_alloc(front_info, index,
+ &front_info->evt_pairs[index].evt,
+ EVTCHNL_TYPE_EVT);
+ if (ret < 0) {
+ dev_err(dev, "Error allocating in-event channel\n");
+ goto fail;
+ }
+ }
+ }
+
+ front_info->num_evt_pairs = num_streams;
+ return 0;
+
+fail:
+ xen_snd_front_evtchnl_free_all(front_info);
+ return ret;
+}
+
+static int evtchnl_publish(struct xenbus_transaction xbt,
+ struct xen_snd_front_evtchnl *channel,
+ const char *path, const char *node_ring,
+ const char *node_chnl)
+{
+ struct xenbus_device *xb_dev = channel->front_info->xb_dev;
+ int ret;
+
+ /* Write control channel ring reference. */
+ ret = xenbus_printf(xbt, path, node_ring, "%u", channel->gref);
+ if (ret < 0) {
+ dev_err(&xb_dev->dev, "Error writing ring-ref: %d\n", ret);
+ return ret;
+ }
+
+ /* Write event channel ring reference. */
+ ret = xenbus_printf(xbt, path, node_chnl, "%u", channel->port);
+ if (ret < 0) {
+ dev_err(&xb_dev->dev, "Error writing event channel: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info)
+{
+ struct xen_front_cfg_card *cfg = &front_info->cfg;
+ struct xenbus_transaction xbt;
+ int ret, d;
+
+again:
+ ret = xenbus_transaction_start(&xbt);
+ if (ret < 0) {
+ xenbus_dev_fatal(front_info->xb_dev, ret,
+ "starting transaction");
+ return ret;
+ }
+
+ for (d = 0; d < cfg->num_pcm_instances; d++) {
+ struct xen_front_cfg_pcm_instance *pcm_instance;
+ int s, index;
+
+ pcm_instance = &cfg->pcm_instances[d];
+
+ for (s = 0; s < pcm_instance->num_streams_pb; s++) {
+ index = pcm_instance->streams_pb[s].index;
+
+ ret = evtchnl_publish(xbt,
+ &front_info->evt_pairs[index].req,
+ pcm_instance->streams_pb[s].xenstore_path,
+ XENSND_FIELD_RING_REF,
+ XENSND_FIELD_EVT_CHNL);
+ if (ret < 0)
+ goto fail;
+
+ ret = evtchnl_publish(xbt,
+ &front_info->evt_pairs[index].evt,
+ pcm_instance->streams_pb[s].xenstore_path,
+ XENSND_FIELD_EVT_RING_REF,
+ XENSND_FIELD_EVT_EVT_CHNL);
+ if (ret < 0)
+ goto fail;
+ }
+
+ for (s = 0; s < pcm_instance->num_streams_cap; s++) {
+ index = pcm_instance->streams_cap[s].index;
+
+ ret = evtchnl_publish(xbt,
+ &front_info->evt_pairs[index].req,
+ pcm_instance->streams_cap[s].xenstore_path,
+ XENSND_FIELD_RING_REF,
+ XENSND_FIELD_EVT_CHNL);
+ if (ret < 0)
+ goto fail;
+
+ ret = evtchnl_publish(xbt,
+ &front_info->evt_pairs[index].evt,
+ pcm_instance->streams_cap[s].xenstore_path,
+ XENSND_FIELD_EVT_RING_REF,
+ XENSND_FIELD_EVT_EVT_CHNL);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+ ret = xenbus_transaction_end(xbt, 0);
+ if (ret < 0) {
+ if (ret == -EAGAIN)
+ goto again;
+
+ xenbus_dev_fatal(front_info->xb_dev, ret,
+ "completing transaction");
+ goto fail_to_end;
+ }
+ return 0;
+fail:
+ xenbus_transaction_end(xbt, 1);
+fail_to_end:
+ xenbus_dev_fatal(front_info->xb_dev, ret, "writing XenStore");
+ return ret;
+}
+
+void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair,
+ bool is_connected)
+{
+ enum xen_snd_front_evtchnl_state state;
+
+ if (is_connected)
+ state = EVTCHNL_STATE_CONNECTED;
+ else
+ state = EVTCHNL_STATE_DISCONNECTED;
+
+ mutex_lock(&evt_pair->req.ring_io_lock);
+ evt_pair->req.state = state;
+ mutex_unlock(&evt_pair->req.ring_io_lock);
+
+ mutex_lock(&evt_pair->evt.ring_io_lock);
+ evt_pair->evt.state = state;
+ mutex_unlock(&evt_pair->evt.ring_io_lock);
+}
+
+void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair)
+{
+ mutex_lock(&evt_pair->req.ring_io_lock);
+ evt_pair->req.evt_next_id = 0;
+ mutex_unlock(&evt_pair->req.ring_io_lock);
+
+ mutex_lock(&evt_pair->evt.ring_io_lock);
+ evt_pair->evt.evt_next_id = 0;
+ mutex_unlock(&evt_pair->evt.ring_io_lock);
+}
+
diff --git a/sound/xen/xen_snd_front_evtchnl.h b/sound/xen/xen_snd_front_evtchnl.h
new file mode 100644
index 000000000..cbe51fd1e
--- /dev/null
+++ b/sound/xen/xen_snd_front_evtchnl.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+/*
+ * Xen para-virtual sound device
+ *
+ * Copyright (C) 2016-2018 EPAM Systems Inc.
+ *
+ * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
+ */
+
+#ifndef __XEN_SND_FRONT_EVTCHNL_H
+#define __XEN_SND_FRONT_EVTCHNL_H
+
+#include <xen/interface/io/sndif.h>
+
+struct xen_snd_front_info;
+
+#ifndef GRANT_INVALID_REF
+/*
+ * FIXME: usage of grant reference 0 as invalid grant reference:
+ * grant reference 0 is valid, but never exposed to a PV driver,
+ * because of the fact it is already in use/reserved by the PV console.
+ */
+#define GRANT_INVALID_REF 0
+#endif
+
+/* Timeout in ms to wait for backend to respond. */
+#define VSND_WAIT_BACK_MS 3000
+
+enum xen_snd_front_evtchnl_state {
+ EVTCHNL_STATE_DISCONNECTED,
+ EVTCHNL_STATE_CONNECTED,
+};
+
+enum xen_snd_front_evtchnl_type {
+ EVTCHNL_TYPE_REQ,
+ EVTCHNL_TYPE_EVT,
+};
+
+struct xen_snd_front_evtchnl {
+ struct xen_snd_front_info *front_info;
+ int gref;
+ int port;
+ int irq;
+ int index;
+ /* State of the event channel. */
+ enum xen_snd_front_evtchnl_state state;
+ enum xen_snd_front_evtchnl_type type;
+ /* Either response id or incoming event id. */
+ u16 evt_id;
+ /* Next request id or next expected event id. */
+ u16 evt_next_id;
+ /* Shared ring access lock. */
+ struct mutex ring_io_lock;
+ union {
+ struct {
+ struct xen_sndif_front_ring ring;
+ struct completion completion;
+ /* Serializer for backend IO: request/response. */
+ struct mutex req_io_lock;
+
+ /* Latest response status. */
+ int resp_status;
+ union {
+ struct xensnd_query_hw_param hw_param;
+ } resp;
+ } req;
+ struct {
+ struct xensnd_event_page *page;
+ /* This is needed to handle XENSND_EVT_CUR_POS event. */
+ struct snd_pcm_substream *substream;
+ } evt;
+ } u;
+};
+
+struct xen_snd_front_evtchnl_pair {
+ struct xen_snd_front_evtchnl req;
+ struct xen_snd_front_evtchnl evt;
+};
+
+int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info,
+ int num_streams);
+
+void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info);
+
+int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info);
+
+void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *evtchnl);
+
+void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair,
+ bool is_connected);
+
+void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair);
+
+#endif /* __XEN_SND_FRONT_EVTCHNL_H */