@@ -24,14 +24,40 @@
#include <sound/pcm.h>
#include <xen/platform_pci.h>
+#include <xen/events.h>
+#include <xen/grant_table.h>
#include <xen/xen.h>
#include <xen/xenbus.h>
#include <xen/interface/io/sndif.h>
+/*
+ * 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
/* maximum number of supported streams */
#define VSND_MAX_STREAM 8
+enum xdrv_evtchnl_state {
+ EVTCHNL_STATE_DISCONNECTED,
+ EVTCHNL_STATE_CONNECTED,
+};
+
+struct xdrv_evtchnl_info {
+ struct xdrv_info *drv_info;
+ struct xen_sndif_front_ring ring;
+ int ring_ref;
+ int port;
+ int irq;
+ struct completion completion;
+ enum xdrv_evtchnl_state state;
+ /* latest response status and its corresponding id */
+ int resp_status;
+ uint16_t resp_id;
+};
+
struct cfg_stream {
int unique_id;
char *xenstore_path;
@@ -65,6 +91,8 @@ struct xdrv_info {
struct xenbus_device *xb_dev;
spinlock_t io_lock;
struct mutex mutex;
+ int num_evt_channels;
+ struct xdrv_evtchnl_info *evt_chnls;
struct sdev_card_plat_data cfg_plat_data;
};
@@ -102,6 +130,244 @@ static struct snd_pcm_hardware sdrv_pcm_hw_default = {
.fifo_size = 0,
};
+static irqreturn_t xdrv_evtchnl_interrupt(int irq, void *dev_id)
+{
+ struct xdrv_evtchnl_info *channel = dev_id;
+ struct xdrv_info *drv_info = channel->drv_info;
+ struct xensnd_resp *resp;
+ RING_IDX i, rp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&drv_info->io_lock, flags);
+ if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED))
+ goto out;
+
+again:
+ rp = channel->ring.sring->rsp_prod;
+ /* ensure we see queued responses up to rp */
+ rmb();
+
+ for (i = channel->ring.rsp_cons; i != rp; i++) {
+ resp = RING_GET_RESPONSE(&channel->ring, i);
+ if (resp->id != channel->resp_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:
+ channel->resp_status = resp->status;
+ complete(&channel->completion);
+ break;
+
+ default:
+ dev_err(&drv_info->xb_dev->dev,
+ "Operation %d is not supported\n",
+ resp->operation);
+ break;
+ }
+ }
+
+ channel->ring.rsp_cons = i;
+ if (i != channel->ring.req_prod_pvt) {
+ int more_to_do;
+
+ RING_FINAL_CHECK_FOR_RESPONSES(&channel->ring, more_to_do);
+ if (more_to_do)
+ goto again;
+ } else
+ channel->ring.sring->rsp_event = i + 1;
+
+out:
+ spin_unlock_irqrestore(&drv_info->io_lock, flags);
+ return IRQ_HANDLED;
+}
+
+static inline void xdrv_evtchnl_flush(
+ struct xdrv_evtchnl_info *channel)
+{
+ int notify;
+
+ channel->ring.req_prod_pvt++;
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&channel->ring, notify);
+ if (notify)
+ notify_remote_via_irq(channel->irq);
+}
+
+static void xdrv_evtchnl_free(struct xdrv_info *drv_info,
+ struct xdrv_evtchnl_info *channel)
+{
+ if (!channel->ring.sring)
+ return;
+
+ channel->state = EVTCHNL_STATE_DISCONNECTED;
+ channel->resp_status = -EIO;
+ complete_all(&channel->completion);
+
+ if (channel->irq)
+ unbind_from_irqhandler(channel->irq, channel);
+ channel->irq = 0;
+
+ if (channel->port)
+ xenbus_free_evtchn(drv_info->xb_dev, channel->port);
+ channel->port = 0;
+
+ if (channel->ring_ref != GRANT_INVALID_REF)
+ gnttab_end_foreign_access(channel->ring_ref, 0,
+ (unsigned long)channel->ring.sring);
+ channel->ring_ref = GRANT_INVALID_REF;
+ channel->ring.sring = NULL;
+}
+
+static void xdrv_evtchnl_free_all(struct xdrv_info *drv_info)
+{
+ int i;
+
+ if (!drv_info->evt_chnls)
+ return;
+
+ for (i = 0; i < drv_info->num_evt_channels; i++)
+ xdrv_evtchnl_free(drv_info, &drv_info->evt_chnls[i]);
+
+ devm_kfree(&drv_info->xb_dev->dev, drv_info->evt_chnls);
+ drv_info->evt_chnls = NULL;
+}
+
+static int xdrv_evtchnl_alloc(struct xdrv_info *drv_info,
+ struct xdrv_evtchnl_info *evt_channel)
+{
+ struct xenbus_device *xb_dev = drv_info->xb_dev;
+ struct xen_sndif_sring *sring;
+ grant_ref_t gref;
+ int ret;
+
+ evt_channel->drv_info = drv_info;
+ init_completion(&evt_channel->completion);
+ evt_channel->state = EVTCHNL_STATE_DISCONNECTED;
+ evt_channel->ring_ref = GRANT_INVALID_REF;
+ evt_channel->ring.sring = NULL;
+ evt_channel->port = 0;
+ evt_channel->irq = 0;
+
+ sring = (struct xen_sndif_sring *)get_zeroed_page(
+ GFP_NOIO | __GFP_HIGH);
+ if (!sring) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ SHARED_RING_INIT(sring);
+ FRONT_RING_INIT(&evt_channel->ring, sring, XEN_PAGE_SIZE);
+ ret = xenbus_grant_ring(xb_dev, sring, 1, &gref);
+ if (ret < 0)
+ goto fail;
+ evt_channel->ring_ref = gref;
+
+ ret = xenbus_alloc_evtchn(xb_dev, &evt_channel->port);
+ if (ret < 0)
+ goto fail;
+
+ ret = bind_evtchn_to_irqhandler(evt_channel->port,
+ xdrv_evtchnl_interrupt, 0, xb_dev->devicetype, evt_channel);
+ if (ret < 0)
+ goto fail;
+
+ evt_channel->irq = ret;
+ return 0;
+
+fail:
+ dev_err(&xb_dev->dev, "Failed to allocate ring: %d\n", ret);
+ return ret;
+}
+
+static int xdrv_evtchnl_create(struct xdrv_info *drv_info,
+ struct xdrv_evtchnl_info *evt_channel,
+ const char *path)
+{
+ int ret;
+
+ ret = xdrv_evtchnl_alloc(drv_info, evt_channel);
+ if (ret < 0) {
+ dev_err(&drv_info->xb_dev->dev,
+ "allocating event channel: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * write values to Xen store, so backend can find ring reference
+ * and event channel
+ */
+ ret = xenbus_printf(XBT_NIL, path, XENSND_FIELD_RING_REF, "%u",
+ evt_channel->ring_ref);
+ if (ret < 0) {
+ dev_err(&drv_info->xb_dev->dev,
+ "writing " XENSND_FIELD_RING_REF": %d\n", ret);
+ return ret;
+ }
+
+ ret = xenbus_printf(XBT_NIL, path, XENSND_FIELD_EVT_CHNL, "%u",
+ evt_channel->port);
+ if (ret < 0) {
+ dev_err(&drv_info->xb_dev->dev,
+ "writing " XENSND_FIELD_EVT_CHNL": %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int xdrv_evtchnl_create_all(struct xdrv_info *drv_info,
+ int num_streams)
+{
+ struct cfg_card *cfg_card;
+ int d, ret = 0;
+
+ drv_info->evt_chnls = devm_kcalloc(&drv_info->xb_dev->dev,
+ num_streams, sizeof(struct xdrv_evtchnl_info), GFP_KERNEL);
+ if (!drv_info->evt_chnls) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ cfg_card = &drv_info->cfg_plat_data.cfg_card;
+ /* iterate over devices and their streams and create event channels */
+ for (d = 0; d < cfg_card->num_pcm_instances; d++) {
+ struct cfg_pcm_instance *pcm_instance;
+ int s, stream_idx;
+
+ pcm_instance = &cfg_card->pcm_instances[d];
+
+ for (s = 0; s < pcm_instance->num_streams_pb; s++) {
+ stream_idx = pcm_instance->streams_pb[s].unique_id;
+ ret = xdrv_evtchnl_create(drv_info,
+ &drv_info->evt_chnls[stream_idx],
+ pcm_instance->streams_pb[s].xenstore_path);
+ if (ret < 0)
+ goto fail;
+ }
+
+ for (s = 0; s < pcm_instance->num_streams_cap; s++) {
+ stream_idx = pcm_instance->streams_cap[s].unique_id;
+ ret = xdrv_evtchnl_create(drv_info,
+ &drv_info->evt_chnls[stream_idx],
+ pcm_instance->streams_cap[s].xenstore_path);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+ if (ret < 0)
+ goto fail;
+
+ drv_info->num_evt_channels = num_streams;
+ return 0;
+
+fail:
+ xdrv_evtchnl_free_all(drv_info);
+ return ret;
+}
+
struct CFG_HW_SAMPLE_RATE {
const char *name;
unsigned int mask;
@@ -556,6 +822,7 @@ static int cfg_card(struct xdrv_info *drv_info,
static void xdrv_remove_internal(struct xdrv_info *drv_info)
{
+ xdrv_evtchnl_free_all(drv_info);
}
static int xdrv_be_on_initwait(struct xdrv_info *drv_info)
@@ -568,7 +835,7 @@ static int xdrv_be_on_initwait(struct xdrv_info *drv_info)
ret = cfg_card(drv_info, &drv_info->cfg_plat_data, &stream_idx);
if (ret < 0)
return ret;
- return 0;
+ return xdrv_evtchnl_create_all(drv_info, stream_idx);
}
static inline int xdrv_be_on_connected(struct xdrv_info *drv_info)