Merge branch 'topic/xen' into for-next
Merge Xen para-virtualized frontend driver from Oleksandr Andrushchenko. Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
commit
96382b4f56
|
@ -15494,6 +15494,13 @@ S: Supported
|
||||||
F: arch/x86/xen/*swiotlb*
|
F: arch/x86/xen/*swiotlb*
|
||||||
F: drivers/xen/*swiotlb*
|
F: drivers/xen/*swiotlb*
|
||||||
|
|
||||||
|
XEN SOUND FRONTEND DRIVER
|
||||||
|
M: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
|
||||||
|
L: xen-devel@lists.xenproject.org (moderated for non-subscribers)
|
||||||
|
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||||
|
S: Supported
|
||||||
|
F: sound/xen/*
|
||||||
|
|
||||||
XFS FILESYSTEM
|
XFS FILESYSTEM
|
||||||
M: Darrick J. Wong <darrick.wong@oracle.com>
|
M: Darrick J. Wong <darrick.wong@oracle.com>
|
||||||
M: linux-xfs@vger.kernel.org
|
M: linux-xfs@vger.kernel.org
|
||||||
|
|
|
@ -96,6 +96,8 @@ source "sound/x86/Kconfig"
|
||||||
|
|
||||||
source "sound/synth/Kconfig"
|
source "sound/synth/Kconfig"
|
||||||
|
|
||||||
|
source "sound/xen/Kconfig"
|
||||||
|
|
||||||
endif # SND
|
endif # SND
|
||||||
|
|
||||||
endif # !UML
|
endif # !UML
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
obj-$(CONFIG_SOUND) += soundcore.o
|
obj-$(CONFIG_SOUND) += soundcore.o
|
||||||
obj-$(CONFIG_DMASOUND) += oss/dmasound/
|
obj-$(CONFIG_DMASOUND) += oss/dmasound/
|
||||||
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
|
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
|
||||||
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/
|
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/
|
||||||
obj-$(CONFIG_SND_AOA) += aoa/
|
obj-$(CONFIG_SND_AOA) += aoa/
|
||||||
|
|
||||||
# This one must be compilable even if sound is configured out
|
# This one must be compilable even if sound is configured out
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# ALSA Xen drivers
|
||||||
|
|
||||||
|
config SND_XEN_FRONTEND
|
||||||
|
tristate "Xen para-virtualized sound frontend driver"
|
||||||
|
depends on XEN
|
||||||
|
select SND_PCM
|
||||||
|
select XEN_XENBUS_FRONTEND
|
||||||
|
help
|
||||||
|
Choose this option if you want to enable a para-virtualized
|
||||||
|
frontend sound driver for Xen guest OSes.
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 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_shbuf.o \
|
||||||
|
xen_snd_front_alsa.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o
|
|
@ -0,0 +1,397 @@
|
||||||
|
// 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/interface/io/sndif.h>
|
||||||
|
|
||||||
|
#include "xen_snd_front.h"
|
||||||
|
#include "xen_snd_front_alsa.h"
|
||||||
|
#include "xen_snd_front_evtchnl.h"
|
||||||
|
#include "xen_snd_front_shbuf.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_snd_front_shbuf *sh_buf,
|
||||||
|
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_snd_front_shbuf_get_dir_start(sh_buf);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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:
|
||||||
|
/* fall through */
|
||||||
|
case XenbusStateReconfigured:
|
||||||
|
/* fall through */
|
||||||
|
case XenbusStateInitialised:
|
||||||
|
/* fall through */
|
||||||
|
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:
|
||||||
|
/* fall through */
|
||||||
|
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}}");
|
|
@ -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_snd_front_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_snd_front_shbuf *sh_buf,
|
||||||
|
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 */
|
|
@ -0,0 +1,821 @@
|
||||||
|
// 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_snd_front.h"
|
||||||
|
#include "xen_snd_front_alsa.h"
|
||||||
|
#include "xen_snd_front_cfg.h"
|
||||||
|
#include "xen_snd_front_evtchnl.h"
|
||||||
|
#include "xen_snd_front_shbuf.h"
|
||||||
|
|
||||||
|
struct xen_snd_front_pcm_stream_info {
|
||||||
|
struct xen_snd_front_info *front_info;
|
||||||
|
struct xen_snd_front_evtchnl_pair *evt_pair;
|
||||||
|
struct xen_snd_front_shbuf sh_buf;
|
||||||
|
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 (1 << ALSA_SNDIF_FORMATS[i].alsa & alsa_formats)
|
||||||
|
mask |= 1 << 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 (1 << ALSA_SNDIF_FORMATS[i].sndif & sndif_formats)
|
||||||
|
mask |= 1 << 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);
|
||||||
|
xen_snd_front_shbuf_clear(&stream->sh_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stream_free(struct xen_snd_front_pcm_stream_info *stream)
|
||||||
|
{
|
||||||
|
xen_snd_front_shbuf_free(&stream->sh_buf);
|
||||||
|
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 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);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This callback may be called multiple times,
|
||||||
|
* so free the previously allocated shared buffer if any.
|
||||||
|
*/
|
||||||
|
stream_free(stream);
|
||||||
|
|
||||||
|
ret = xen_snd_front_shbuf_alloc(stream->front_info->xb_dev,
|
||||||
|
&stream->sh_buf,
|
||||||
|
params_buffer_bytes(params));
|
||||||
|
if (ret < 0) {
|
||||||
|
stream_free(stream);
|
||||||
|
dev_err(&stream->front_info->xb_dev->dev,
|
||||||
|
"Failed to allocate buffers for stream with index %d\n",
|
||||||
|
stream->index);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
sndif_format = to_sndif_format(runtime->format);
|
||||||
|
if (sndif_format < 0) {
|
||||||
|
dev_err(&stream->front_info->xb_dev->dev,
|
||||||
|
"Unsupported sample format: %d\n",
|
||||||
|
runtime->format);
|
||||||
|
return sndif_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = xen_snd_front_stream_prepare(&stream->evt_pair->req,
|
||||||
|
&stream->sh_buf,
|
||||||
|
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->sh_buf.buffer_sz))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (copy_from_user(stream->sh_buf.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->sh_buf.buffer_sz))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
memcpy(stream->sh_buf.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->sh_buf.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->sh_buf.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->sh_buf.buffer_sz))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
memcpy(dst, stream->sh_buf.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->sh_buf.buffer_sz))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
memset(stream->sh_buf.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 struct snd_pcm_ops snd_drv_alsa_playback_ops = {
|
||||||
|
.open = alsa_open,
|
||||||
|
.close = alsa_close,
|
||||||
|
.ioctl = snd_pcm_lib_ioctl,
|
||||||
|
.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 struct snd_pcm_ops snd_drv_alsa_capture_ops = {
|
||||||
|
.open = alsa_open,
|
||||||
|
.close = alsa_close,
|
||||||
|
.ioctl = snd_pcm_lib_ioctl,
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -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 */
|
|
@ -0,0 +1,517 @@
|
||||||
|
// 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);
|
||||||
|
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);
|
||||||
|
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)) {
|
||||||
|
strncpy(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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 */
|
|
@ -0,0 +1,496 @@
|
||||||
|
// 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:
|
||||||
|
/* fall through */
|
||||||
|
case XENSND_OP_CLOSE:
|
||||||
|
/* fall through */
|
||||||
|
case XENSND_OP_READ:
|
||||||
|
/* fall through */
|
||||||
|
case XENSND_OP_WRITE:
|
||||||
|
/* fall through */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -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 */
|
|
@ -0,0 +1,194 @@
|
||||||
|
// 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/kernel.h>
|
||||||
|
#include <xen/xen.h>
|
||||||
|
#include <xen/xenbus.h>
|
||||||
|
|
||||||
|
#include "xen_snd_front_shbuf.h"
|
||||||
|
|
||||||
|
grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf)
|
||||||
|
{
|
||||||
|
if (!buf->grefs)
|
||||||
|
return GRANT_INVALID_REF;
|
||||||
|
|
||||||
|
return buf->grefs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf)
|
||||||
|
{
|
||||||
|
memset(buf, 0, sizeof(*buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (buf->grefs) {
|
||||||
|
for (i = 0; i < buf->num_grefs; i++)
|
||||||
|
if (buf->grefs[i] != GRANT_INVALID_REF)
|
||||||
|
gnttab_end_foreign_access(buf->grefs[i],
|
||||||
|
0, 0UL);
|
||||||
|
kfree(buf->grefs);
|
||||||
|
}
|
||||||
|
kfree(buf->directory);
|
||||||
|
free_pages_exact(buf->buffer, buf->buffer_sz);
|
||||||
|
xen_snd_front_shbuf_clear(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* number of grant references a page can hold with respect to the
|
||||||
|
* xensnd_page_directory header
|
||||||
|
*/
|
||||||
|
#define XENSND_NUM_GREFS_PER_PAGE ((XEN_PAGE_SIZE - \
|
||||||
|
offsetof(struct xensnd_page_directory, gref)) / \
|
||||||
|
sizeof(grant_ref_t))
|
||||||
|
|
||||||
|
static void fill_page_dir(struct xen_snd_front_shbuf *buf,
|
||||||
|
int num_pages_dir)
|
||||||
|
{
|
||||||
|
struct xensnd_page_directory *page_dir;
|
||||||
|
unsigned char *ptr;
|
||||||
|
int i, cur_gref, grefs_left, to_copy;
|
||||||
|
|
||||||
|
ptr = buf->directory;
|
||||||
|
grefs_left = buf->num_grefs - num_pages_dir;
|
||||||
|
/*
|
||||||
|
* skip grant references at the beginning, they are for pages granted
|
||||||
|
* for the page directory itself
|
||||||
|
*/
|
||||||
|
cur_gref = num_pages_dir;
|
||||||
|
for (i = 0; i < num_pages_dir; i++) {
|
||||||
|
page_dir = (struct xensnd_page_directory *)ptr;
|
||||||
|
if (grefs_left <= XENSND_NUM_GREFS_PER_PAGE) {
|
||||||
|
to_copy = grefs_left;
|
||||||
|
page_dir->gref_dir_next_page = GRANT_INVALID_REF;
|
||||||
|
} else {
|
||||||
|
to_copy = XENSND_NUM_GREFS_PER_PAGE;
|
||||||
|
page_dir->gref_dir_next_page = buf->grefs[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&page_dir->gref, &buf->grefs[cur_gref],
|
||||||
|
to_copy * sizeof(grant_ref_t));
|
||||||
|
|
||||||
|
ptr += XEN_PAGE_SIZE;
|
||||||
|
grefs_left -= to_copy;
|
||||||
|
cur_gref += to_copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int grant_references(struct xenbus_device *xb_dev,
|
||||||
|
struct xen_snd_front_shbuf *buf,
|
||||||
|
int num_pages_dir, int num_pages_buffer,
|
||||||
|
int num_grefs)
|
||||||
|
{
|
||||||
|
grant_ref_t priv_gref_head;
|
||||||
|
unsigned long frame;
|
||||||
|
int ret, i, j, cur_ref;
|
||||||
|
int otherend_id;
|
||||||
|
|
||||||
|
ret = gnttab_alloc_grant_references(num_grefs, &priv_gref_head);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
buf->num_grefs = num_grefs;
|
||||||
|
otherend_id = xb_dev->otherend_id;
|
||||||
|
j = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < num_pages_dir; i++) {
|
||||||
|
cur_ref = gnttab_claim_grant_reference(&priv_gref_head);
|
||||||
|
if (cur_ref < 0) {
|
||||||
|
ret = cur_ref;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = xen_page_to_gfn(virt_to_page(buf->directory +
|
||||||
|
XEN_PAGE_SIZE * i));
|
||||||
|
gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0);
|
||||||
|
buf->grefs[j++] = cur_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_pages_buffer; i++) {
|
||||||
|
cur_ref = gnttab_claim_grant_reference(&priv_gref_head);
|
||||||
|
if (cur_ref < 0) {
|
||||||
|
ret = cur_ref;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = xen_page_to_gfn(virt_to_page(buf->buffer +
|
||||||
|
XEN_PAGE_SIZE * i));
|
||||||
|
gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0);
|
||||||
|
buf->grefs[j++] = cur_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
gnttab_free_grant_references(priv_gref_head);
|
||||||
|
fill_page_dir(buf, num_pages_dir);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
gnttab_free_grant_references(priv_gref_head);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int alloc_int_buffers(struct xen_snd_front_shbuf *buf,
|
||||||
|
int num_pages_dir, int num_pages_buffer,
|
||||||
|
int num_grefs)
|
||||||
|
{
|
||||||
|
buf->grefs = kcalloc(num_grefs, sizeof(*buf->grefs), GFP_KERNEL);
|
||||||
|
if (!buf->grefs)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
buf->directory = kcalloc(num_pages_dir, XEN_PAGE_SIZE, GFP_KERNEL);
|
||||||
|
if (!buf->directory)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
buf->buffer_sz = num_pages_buffer * XEN_PAGE_SIZE;
|
||||||
|
buf->buffer = alloc_pages_exact(buf->buffer_sz, GFP_KERNEL);
|
||||||
|
if (!buf->buffer)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
kfree(buf->grefs);
|
||||||
|
buf->grefs = NULL;
|
||||||
|
kfree(buf->directory);
|
||||||
|
buf->directory = NULL;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev,
|
||||||
|
struct xen_snd_front_shbuf *buf,
|
||||||
|
unsigned int buffer_sz)
|
||||||
|
{
|
||||||
|
int num_pages_buffer, num_pages_dir, num_grefs;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
xen_snd_front_shbuf_clear(buf);
|
||||||
|
|
||||||
|
num_pages_buffer = DIV_ROUND_UP(buffer_sz, XEN_PAGE_SIZE);
|
||||||
|
/* number of pages the page directory consumes itself */
|
||||||
|
num_pages_dir = DIV_ROUND_UP(num_pages_buffer,
|
||||||
|
XENSND_NUM_GREFS_PER_PAGE);
|
||||||
|
num_grefs = num_pages_buffer + num_pages_dir;
|
||||||
|
|
||||||
|
ret = alloc_int_buffers(buf, num_pages_dir,
|
||||||
|
num_pages_buffer, num_grefs);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = grant_references(xb_dev, buf, num_pages_dir, num_pages_buffer,
|
||||||
|
num_grefs);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
fill_page_dir(buf, num_pages_dir);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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_SHBUF_H
|
||||||
|
#define __XEN_SND_FRONT_SHBUF_H
|
||||||
|
|
||||||
|
#include <xen/grant_table.h>
|
||||||
|
|
||||||
|
#include "xen_snd_front_evtchnl.h"
|
||||||
|
|
||||||
|
struct xen_snd_front_shbuf {
|
||||||
|
int num_grefs;
|
||||||
|
grant_ref_t *grefs;
|
||||||
|
u8 *directory;
|
||||||
|
u8 *buffer;
|
||||||
|
size_t buffer_sz;
|
||||||
|
};
|
||||||
|
|
||||||
|
grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf);
|
||||||
|
|
||||||
|
int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev,
|
||||||
|
struct xen_snd_front_shbuf *buf,
|
||||||
|
unsigned int buffer_sz);
|
||||||
|
|
||||||
|
void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf);
|
||||||
|
|
||||||
|
void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf);
|
||||||
|
|
||||||
|
#endif /* __XEN_SND_FRONT_SHBUF_H */
|
Loading…
Reference in New Issue