forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
12 KiB
490 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0 OR MIT |
|
|
|
/* |
|
* Xen para-virtual sound device |
|
* |
|
* Copyright (C) 2016-2018 EPAM Systems Inc. |
|
* |
|
* Author: Oleksandr Andrushchenko <[email protected]> |
|
*/ |
|
|
|
#include <xen/events.h> |
|
#include <xen/grant_table.h> |
|
#include <xen/xen.h> |
|
#include <xen/xenbus.h> |
|
|
|
#include "xen_snd_front.h" |
|
#include "xen_snd_front_alsa.h" |
|
#include "xen_snd_front_cfg.h" |
|
#include "xen_snd_front_evtchnl.h" |
|
|
|
static irqreturn_t evtchnl_interrupt_req(int irq, void *dev_id) |
|
{ |
|
struct xen_snd_front_evtchnl *channel = dev_id; |
|
struct xen_snd_front_info *front_info = channel->front_info; |
|
struct xensnd_resp *resp; |
|
RING_IDX i, rp; |
|
|
|
if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) |
|
return IRQ_HANDLED; |
|
|
|
mutex_lock(&channel->ring_io_lock); |
|
|
|
again: |
|
rp = channel->u.req.ring.sring->rsp_prod; |
|
/* Ensure we see queued responses up to rp. */ |
|
rmb(); |
|
|
|
/* |
|
* Assume that the backend is trusted to always write sane values |
|
* to the ring counters, so no overflow checks on frontend side |
|
* are required. |
|
*/ |
|
for (i = channel->u.req.ring.rsp_cons; i != rp; i++) { |
|
resp = RING_GET_RESPONSE(&channel->u.req.ring, i); |
|
if (resp->id != channel->evt_id) |
|
continue; |
|
switch (resp->operation) { |
|
case XENSND_OP_OPEN: |
|
case XENSND_OP_CLOSE: |
|
case XENSND_OP_READ: |
|
case XENSND_OP_WRITE: |
|
case XENSND_OP_TRIGGER: |
|
channel->u.req.resp_status = resp->status; |
|
complete(&channel->u.req.completion); |
|
break; |
|
case XENSND_OP_HW_PARAM_QUERY: |
|
channel->u.req.resp_status = resp->status; |
|
channel->u.req.resp.hw_param = |
|
resp->resp.hw_param; |
|
complete(&channel->u.req.completion); |
|
break; |
|
|
|
default: |
|
dev_err(&front_info->xb_dev->dev, |
|
"Operation %d is not supported\n", |
|
resp->operation); |
|
break; |
|
} |
|
} |
|
|
|
channel->u.req.ring.rsp_cons = i; |
|
if (i != channel->u.req.ring.req_prod_pvt) { |
|
int more_to_do; |
|
|
|
RING_FINAL_CHECK_FOR_RESPONSES(&channel->u.req.ring, |
|
more_to_do); |
|
if (more_to_do) |
|
goto again; |
|
} else { |
|
channel->u.req.ring.sring->rsp_event = i + 1; |
|
} |
|
|
|
mutex_unlock(&channel->ring_io_lock); |
|
return IRQ_HANDLED; |
|
} |
|
|
|
static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id) |
|
{ |
|
struct xen_snd_front_evtchnl *channel = dev_id; |
|
struct xensnd_event_page *page = channel->u.evt.page; |
|
u32 cons, prod; |
|
|
|
if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) |
|
return IRQ_HANDLED; |
|
|
|
mutex_lock(&channel->ring_io_lock); |
|
|
|
prod = page->in_prod; |
|
/* Ensure we see ring contents up to prod. */ |
|
virt_rmb(); |
|
if (prod == page->in_cons) |
|
goto out; |
|
|
|
/* |
|
* Assume that the backend is trusted to always write sane values |
|
* to the ring counters, so no overflow checks on frontend side |
|
* are required. |
|
*/ |
|
for (cons = page->in_cons; cons != prod; cons++) { |
|
struct xensnd_evt *event; |
|
|
|
event = &XENSND_IN_RING_REF(page, cons); |
|
if (unlikely(event->id != channel->evt_id++)) |
|
continue; |
|
|
|
switch (event->type) { |
|
case XENSND_EVT_CUR_POS: |
|
xen_snd_front_alsa_handle_cur_pos(channel, |
|
event->op.cur_pos.position); |
|
break; |
|
} |
|
} |
|
|
|
page->in_cons = cons; |
|
/* Ensure ring contents. */ |
|
virt_wmb(); |
|
|
|
out: |
|
mutex_unlock(&channel->ring_io_lock); |
|
return IRQ_HANDLED; |
|
} |
|
|
|
void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *channel) |
|
{ |
|
int notify; |
|
|
|
channel->u.req.ring.req_prod_pvt++; |
|
RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&channel->u.req.ring, notify); |
|
if (notify) |
|
notify_remote_via_irq(channel->irq); |
|
} |
|
|
|
static void evtchnl_free(struct xen_snd_front_info *front_info, |
|
struct xen_snd_front_evtchnl *channel) |
|
{ |
|
unsigned long page = 0; |
|
|
|
if (channel->type == EVTCHNL_TYPE_REQ) |
|
page = (unsigned long)channel->u.req.ring.sring; |
|
else if (channel->type == EVTCHNL_TYPE_EVT) |
|
page = (unsigned long)channel->u.evt.page; |
|
|
|
if (!page) |
|
return; |
|
|
|
channel->state = EVTCHNL_STATE_DISCONNECTED; |
|
if (channel->type == EVTCHNL_TYPE_REQ) { |
|
/* Release all who still waits for response if any. */ |
|
channel->u.req.resp_status = -EIO; |
|
complete_all(&channel->u.req.completion); |
|
} |
|
|
|
if (channel->irq) |
|
unbind_from_irqhandler(channel->irq, channel); |
|
|
|
if (channel->port) |
|
xenbus_free_evtchn(front_info->xb_dev, channel->port); |
|
|
|
/* End access and free the page. */ |
|
if (channel->gref != GRANT_INVALID_REF) |
|
gnttab_end_foreign_access(channel->gref, 0, page); |
|
else |
|
free_page(page); |
|
|
|
memset(channel, 0, sizeof(*channel)); |
|
} |
|
|
|
void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info) |
|
{ |
|
int i; |
|
|
|
if (!front_info->evt_pairs) |
|
return; |
|
|
|
for (i = 0; i < front_info->num_evt_pairs; i++) { |
|
evtchnl_free(front_info, &front_info->evt_pairs[i].req); |
|
evtchnl_free(front_info, &front_info->evt_pairs[i].evt); |
|
} |
|
|
|
kfree(front_info->evt_pairs); |
|
front_info->evt_pairs = NULL; |
|
} |
|
|
|
static int evtchnl_alloc(struct xen_snd_front_info *front_info, int index, |
|
struct xen_snd_front_evtchnl *channel, |
|
enum xen_snd_front_evtchnl_type type) |
|
{ |
|
struct xenbus_device *xb_dev = front_info->xb_dev; |
|
unsigned long page; |
|
grant_ref_t gref; |
|
irq_handler_t handler; |
|
char *handler_name = NULL; |
|
int ret; |
|
|
|
memset(channel, 0, sizeof(*channel)); |
|
channel->type = type; |
|
channel->index = index; |
|
channel->front_info = front_info; |
|
channel->state = EVTCHNL_STATE_DISCONNECTED; |
|
channel->gref = GRANT_INVALID_REF; |
|
page = get_zeroed_page(GFP_KERNEL); |
|
if (!page) { |
|
ret = -ENOMEM; |
|
goto fail; |
|
} |
|
|
|
handler_name = kasprintf(GFP_KERNEL, "%s-%s", XENSND_DRIVER_NAME, |
|
type == EVTCHNL_TYPE_REQ ? |
|
XENSND_FIELD_RING_REF : |
|
XENSND_FIELD_EVT_RING_REF); |
|
if (!handler_name) { |
|
ret = -ENOMEM; |
|
goto fail; |
|
} |
|
|
|
mutex_init(&channel->ring_io_lock); |
|
|
|
if (type == EVTCHNL_TYPE_REQ) { |
|
struct xen_sndif_sring *sring = (struct xen_sndif_sring *)page; |
|
|
|
init_completion(&channel->u.req.completion); |
|
mutex_init(&channel->u.req.req_io_lock); |
|
SHARED_RING_INIT(sring); |
|
FRONT_RING_INIT(&channel->u.req.ring, sring, XEN_PAGE_SIZE); |
|
|
|
ret = xenbus_grant_ring(xb_dev, sring, 1, &gref); |
|
if (ret < 0) { |
|
channel->u.req.ring.sring = NULL; |
|
goto fail; |
|
} |
|
|
|
handler = evtchnl_interrupt_req; |
|
} else { |
|
ret = gnttab_grant_foreign_access(xb_dev->otherend_id, |
|
virt_to_gfn((void *)page), 0); |
|
if (ret < 0) |
|
goto fail; |
|
|
|
channel->u.evt.page = (struct xensnd_event_page *)page; |
|
gref = ret; |
|
handler = evtchnl_interrupt_evt; |
|
} |
|
|
|
channel->gref = gref; |
|
|
|
ret = xenbus_alloc_evtchn(xb_dev, &channel->port); |
|
if (ret < 0) |
|
goto fail; |
|
|
|
ret = bind_evtchn_to_irq(channel->port); |
|
if (ret < 0) { |
|
dev_err(&xb_dev->dev, |
|
"Failed to bind IRQ for domid %d port %d: %d\n", |
|
front_info->xb_dev->otherend_id, channel->port, ret); |
|
goto fail; |
|
} |
|
|
|
channel->irq = ret; |
|
|
|
ret = request_threaded_irq(channel->irq, NULL, handler, |
|
IRQF_ONESHOT, handler_name, channel); |
|
if (ret < 0) { |
|
dev_err(&xb_dev->dev, "Failed to request IRQ %d: %d\n", |
|
channel->irq, ret); |
|
goto fail; |
|
} |
|
|
|
kfree(handler_name); |
|
return 0; |
|
|
|
fail: |
|
if (page) |
|
free_page(page); |
|
kfree(handler_name); |
|
dev_err(&xb_dev->dev, "Failed to allocate ring: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, |
|
int num_streams) |
|
{ |
|
struct xen_front_cfg_card *cfg = &front_info->cfg; |
|
struct device *dev = &front_info->xb_dev->dev; |
|
int d, ret = 0; |
|
|
|
front_info->evt_pairs = |
|
kcalloc(num_streams, |
|
sizeof(struct xen_snd_front_evtchnl_pair), |
|
GFP_KERNEL); |
|
if (!front_info->evt_pairs) |
|
return -ENOMEM; |
|
|
|
/* Iterate over devices and their streams and create event channels. */ |
|
for (d = 0; d < cfg->num_pcm_instances; d++) { |
|
struct xen_front_cfg_pcm_instance *pcm_instance; |
|
int s, index; |
|
|
|
pcm_instance = &cfg->pcm_instances[d]; |
|
|
|
for (s = 0; s < pcm_instance->num_streams_pb; s++) { |
|
index = pcm_instance->streams_pb[s].index; |
|
|
|
ret = evtchnl_alloc(front_info, index, |
|
&front_info->evt_pairs[index].req, |
|
EVTCHNL_TYPE_REQ); |
|
if (ret < 0) { |
|
dev_err(dev, "Error allocating control channel\n"); |
|
goto fail; |
|
} |
|
|
|
ret = evtchnl_alloc(front_info, index, |
|
&front_info->evt_pairs[index].evt, |
|
EVTCHNL_TYPE_EVT); |
|
if (ret < 0) { |
|
dev_err(dev, "Error allocating in-event channel\n"); |
|
goto fail; |
|
} |
|
} |
|
|
|
for (s = 0; s < pcm_instance->num_streams_cap; s++) { |
|
index = pcm_instance->streams_cap[s].index; |
|
|
|
ret = evtchnl_alloc(front_info, index, |
|
&front_info->evt_pairs[index].req, |
|
EVTCHNL_TYPE_REQ); |
|
if (ret < 0) { |
|
dev_err(dev, "Error allocating control channel\n"); |
|
goto fail; |
|
} |
|
|
|
ret = evtchnl_alloc(front_info, index, |
|
&front_info->evt_pairs[index].evt, |
|
EVTCHNL_TYPE_EVT); |
|
if (ret < 0) { |
|
dev_err(dev, "Error allocating in-event channel\n"); |
|
goto fail; |
|
} |
|
} |
|
} |
|
|
|
front_info->num_evt_pairs = num_streams; |
|
return 0; |
|
|
|
fail: |
|
xen_snd_front_evtchnl_free_all(front_info); |
|
return ret; |
|
} |
|
|
|
static int evtchnl_publish(struct xenbus_transaction xbt, |
|
struct xen_snd_front_evtchnl *channel, |
|
const char *path, const char *node_ring, |
|
const char *node_chnl) |
|
{ |
|
struct xenbus_device *xb_dev = channel->front_info->xb_dev; |
|
int ret; |
|
|
|
/* Write control channel ring reference. */ |
|
ret = xenbus_printf(xbt, path, node_ring, "%u", channel->gref); |
|
if (ret < 0) { |
|
dev_err(&xb_dev->dev, "Error writing ring-ref: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
/* Write event channel ring reference. */ |
|
ret = xenbus_printf(xbt, path, node_chnl, "%u", channel->port); |
|
if (ret < 0) { |
|
dev_err(&xb_dev->dev, "Error writing event channel: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info) |
|
{ |
|
struct xen_front_cfg_card *cfg = &front_info->cfg; |
|
struct xenbus_transaction xbt; |
|
int ret, d; |
|
|
|
again: |
|
ret = xenbus_transaction_start(&xbt); |
|
if (ret < 0) { |
|
xenbus_dev_fatal(front_info->xb_dev, ret, |
|
"starting transaction"); |
|
return ret; |
|
} |
|
|
|
for (d = 0; d < cfg->num_pcm_instances; d++) { |
|
struct xen_front_cfg_pcm_instance *pcm_instance; |
|
int s, index; |
|
|
|
pcm_instance = &cfg->pcm_instances[d]; |
|
|
|
for (s = 0; s < pcm_instance->num_streams_pb; s++) { |
|
index = pcm_instance->streams_pb[s].index; |
|
|
|
ret = evtchnl_publish(xbt, |
|
&front_info->evt_pairs[index].req, |
|
pcm_instance->streams_pb[s].xenstore_path, |
|
XENSND_FIELD_RING_REF, |
|
XENSND_FIELD_EVT_CHNL); |
|
if (ret < 0) |
|
goto fail; |
|
|
|
ret = evtchnl_publish(xbt, |
|
&front_info->evt_pairs[index].evt, |
|
pcm_instance->streams_pb[s].xenstore_path, |
|
XENSND_FIELD_EVT_RING_REF, |
|
XENSND_FIELD_EVT_EVT_CHNL); |
|
if (ret < 0) |
|
goto fail; |
|
} |
|
|
|
for (s = 0; s < pcm_instance->num_streams_cap; s++) { |
|
index = pcm_instance->streams_cap[s].index; |
|
|
|
ret = evtchnl_publish(xbt, |
|
&front_info->evt_pairs[index].req, |
|
pcm_instance->streams_cap[s].xenstore_path, |
|
XENSND_FIELD_RING_REF, |
|
XENSND_FIELD_EVT_CHNL); |
|
if (ret < 0) |
|
goto fail; |
|
|
|
ret = evtchnl_publish(xbt, |
|
&front_info->evt_pairs[index].evt, |
|
pcm_instance->streams_cap[s].xenstore_path, |
|
XENSND_FIELD_EVT_RING_REF, |
|
XENSND_FIELD_EVT_EVT_CHNL); |
|
if (ret < 0) |
|
goto fail; |
|
} |
|
} |
|
ret = xenbus_transaction_end(xbt, 0); |
|
if (ret < 0) { |
|
if (ret == -EAGAIN) |
|
goto again; |
|
|
|
xenbus_dev_fatal(front_info->xb_dev, ret, |
|
"completing transaction"); |
|
goto fail_to_end; |
|
} |
|
return 0; |
|
fail: |
|
xenbus_transaction_end(xbt, 1); |
|
fail_to_end: |
|
xenbus_dev_fatal(front_info->xb_dev, ret, "writing XenStore"); |
|
return ret; |
|
} |
|
|
|
void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair, |
|
bool is_connected) |
|
{ |
|
enum xen_snd_front_evtchnl_state state; |
|
|
|
if (is_connected) |
|
state = EVTCHNL_STATE_CONNECTED; |
|
else |
|
state = EVTCHNL_STATE_DISCONNECTED; |
|
|
|
mutex_lock(&evt_pair->req.ring_io_lock); |
|
evt_pair->req.state = state; |
|
mutex_unlock(&evt_pair->req.ring_io_lock); |
|
|
|
mutex_lock(&evt_pair->evt.ring_io_lock); |
|
evt_pair->evt.state = state; |
|
mutex_unlock(&evt_pair->evt.ring_io_lock); |
|
} |
|
|
|
void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair) |
|
{ |
|
mutex_lock(&evt_pair->req.ring_io_lock); |
|
evt_pair->req.evt_next_id = 0; |
|
mutex_unlock(&evt_pair->req.ring_io_lock); |
|
|
|
mutex_lock(&evt_pair->evt.ring_io_lock); |
|
evt_pair->evt.evt_next_id = 0; |
|
mutex_unlock(&evt_pair->evt.ring_io_lock); |
|
} |
|
|
|
|