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:
Takashi Iwai 2018-05-21 22:21:50 +02:00
commit 96382b4f56
15 changed files with 2708 additions and 1 deletions

View File

@ -15494,6 +15494,13 @@ S: Supported
F: arch/x86/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
M: Darrick J. Wong <darrick.wong@oracle.com>
M: linux-xfs@vger.kernel.org

View File

@ -96,6 +96,8 @@ source "sound/x86/Kconfig"
source "sound/synth/Kconfig"
source "sound/xen/Kconfig"
endif # SND
endif # !UML

View File

@ -5,7 +5,7 @@
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_DMASOUND) += oss/dmasound/
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/
# This one must be compilable even if sound is configured out

10
sound/xen/Kconfig Normal file
View File

@ -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.

9
sound/xen/Makefile Normal file
View File

@ -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

397
sound/xen/xen_snd_front.c Normal file
View File

@ -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}}");

54
sound/xen/xen_snd_front.h Normal file
View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */