Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/sound/virtio/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 10 kB image not shown  

Quelle  virtio_card.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0+
/*
 * virtio-snd: Virtio sound device
 * Copyright (C) 2021 OpenSynergy GmbH
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/virtio_config.h>
#include <sound/initval.h>
#include <uapi/linux/virtio_ids.h>

#include "virtio_card.h"

u32 virtsnd_msg_timeout_ms = MSEC_PER_SEC;
module_param_named(msg_timeout_ms, virtsnd_msg_timeout_ms, uint, 0644);
MODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds");

static void virtsnd_remove(struct virtio_device *vdev);

/**
 * virtsnd_event_send() - Add an event to the event queue.
 * @vqueue: Underlying event virtqueue.
 * @event: Event.
 * @notify: Indicates whether or not to send a notification to the device.
 * @gfp: Kernel flags for memory allocation.
 *
 * Context: Any context.
 */

static void virtsnd_event_send(struct virtqueue *vqueue,
          struct virtio_snd_event *event, bool notify,
          gfp_t gfp)
{
 struct scatterlist sg;
 struct scatterlist *psgs[1] = { &sg };

 /* reset event content */
 memset(event, 0, sizeof(*event));

 sg_init_one(&sg, event, sizeof(*event));

 if (virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp) || !notify)
  return;

 if (virtqueue_kick_prepare(vqueue))
  virtqueue_notify(vqueue);
}

/**
 * virtsnd_event_dispatch() - Dispatch an event from the device side.
 * @snd: VirtIO sound device.
 * @event: VirtIO sound event.
 *
 * Context: Any context.
 */

static void virtsnd_event_dispatch(struct virtio_snd *snd,
       struct virtio_snd_event *event)
{
 switch (le32_to_cpu(event->hdr.code)) {
 case VIRTIO_SND_EVT_JACK_CONNECTED:
 case VIRTIO_SND_EVT_JACK_DISCONNECTED:
  virtsnd_jack_event(snd, event);
  break;
 case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
 case VIRTIO_SND_EVT_PCM_XRUN:
  virtsnd_pcm_event(snd, event);
  break;
 case VIRTIO_SND_EVT_CTL_NOTIFY:
  virtsnd_kctl_event(snd, event);
  break;
 }
}

/**
 * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue.
 * @vqueue: Underlying event virtqueue.
 *
 * This callback function is called upon a vring interrupt request from the
 * device.
 *
 * Context: Interrupt context.
 */

static void virtsnd_event_notify_cb(struct virtqueue *vqueue)
{
 struct virtio_snd *snd = vqueue->vdev->priv;
 struct virtio_snd_queue *queue = virtsnd_event_queue(snd);
 struct virtio_snd_event *event;
 u32 length;
 unsigned long flags;

 spin_lock_irqsave(&queue->lock, flags);
 do {
  virtqueue_disable_cb(vqueue);
  while ((event = virtqueue_get_buf(vqueue, &length))) {
   virtsnd_event_dispatch(snd, event);
   virtsnd_event_send(vqueue, event, true, GFP_ATOMIC);
  }
 } while (!virtqueue_enable_cb(vqueue));
 spin_unlock_irqrestore(&queue->lock, flags);
}

/**
 * virtsnd_find_vqs() - Enumerate and initialize all virtqueues.
 * @snd: VirtIO sound device.
 *
 * After calling this function, the event queue is disabled.
 *
 * Context: Any context.
 * Return: 0 on success, -errno on failure.
 */

static int virtsnd_find_vqs(struct virtio_snd *snd)
{
 struct virtio_device *vdev = snd->vdev;
 struct virtqueue_info vqs_info[VIRTIO_SND_VQ_MAX] = {
  [VIRTIO_SND_VQ_CONTROL] = { "virtsnd-ctl",
         virtsnd_ctl_notify_cb },
  [VIRTIO_SND_VQ_EVENT] = { "virtsnd-event",
       virtsnd_event_notify_cb },
  [VIRTIO_SND_VQ_TX] = { "virtsnd-tx",
           virtsnd_pcm_tx_notify_cb },
  [VIRTIO_SND_VQ_RX] = { "virtsnd-rx",
           virtsnd_pcm_rx_notify_cb },
 };
 struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };
 unsigned int i;
 unsigned int n;
 int rc;

 rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, vqs_info, NULL);
 if (rc) {
  dev_err(&vdev->dev, "failed to initialize virtqueues\n");
  return rc;
 }

 for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)
  snd->queues[i].vqueue = vqs[i];

 /* Allocate events and populate the event queue */
 virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]);

 n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);

 snd->event_msgs = kmalloc_array(n, sizeof(*snd->event_msgs),
     GFP_KERNEL);
 if (!snd->event_msgs)
  return -ENOMEM;

 for (i = 0; i < n; ++i)
  virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],
       &snd->event_msgs[i], false, GFP_KERNEL);

 return 0;
}

/**
 * virtsnd_enable_event_vq() - Enable the event virtqueue.
 * @snd: VirtIO sound device.
 *
 * Context: Any context.
 */

static void virtsnd_enable_event_vq(struct virtio_snd *snd)
{
 struct virtio_snd_queue *queue = virtsnd_event_queue(snd);

 if (!virtqueue_enable_cb(queue->vqueue))
  virtsnd_event_notify_cb(queue->vqueue);
}

/**
 * virtsnd_disable_event_vq() - Disable the event virtqueue.
 * @snd: VirtIO sound device.
 *
 * Context: Any context.
 */

static void virtsnd_disable_event_vq(struct virtio_snd *snd)
{
 struct virtio_snd_queue *queue = virtsnd_event_queue(snd);
 struct virtio_snd_event *event;
 u32 length;
 unsigned long flags;

 if (queue->vqueue) {
  spin_lock_irqsave(&queue->lock, flags);
  virtqueue_disable_cb(queue->vqueue);
  while ((event = virtqueue_get_buf(queue->vqueue, &length)))
   virtsnd_event_dispatch(snd, event);
  spin_unlock_irqrestore(&queue->lock, flags);
 }
}

/**
 * virtsnd_build_devs() - Read configuration and build ALSA devices.
 * @snd: VirtIO sound device.
 *
 * Context: Any context that permits to sleep.
 * Return: 0 on success, -errno on failure.
 */

static int virtsnd_build_devs(struct virtio_snd *snd)
{
 struct virtio_device *vdev = snd->vdev;
 struct device *dev = &vdev->dev;
 int rc;

 rc = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
     THIS_MODULE, 0, &snd->card);
 if (rc < 0)
  return rc;

 snd->card->private_data = snd;

 strscpy(snd->card->driver, VIRTIO_SND_CARD_DRIVER,
  sizeof(snd->card->driver));
 strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME,
  sizeof(snd->card->shortname));
 if (dev->parent->bus)
  snprintf(snd->card->longname, sizeof(snd->card->longname),
    VIRTIO_SND_CARD_NAME " at %s/%s/%s",
    dev->parent->bus->name, dev_name(dev->parent),
    dev_name(dev));
 else
  snprintf(snd->card->longname, sizeof(snd->card->longname),
    VIRTIO_SND_CARD_NAME " at %s/%s",
    dev_name(dev->parent), dev_name(dev));

 rc = virtsnd_jack_parse_cfg(snd);
 if (rc)
  return rc;

 rc = virtsnd_pcm_parse_cfg(snd);
 if (rc)
  return rc;

 rc = virtsnd_chmap_parse_cfg(snd);
 if (rc)
  return rc;

 if (virtio_has_feature(vdev, VIRTIO_SND_F_CTLS)) {
  rc = virtsnd_kctl_parse_cfg(snd);
  if (rc)
   return rc;
 }

 if (snd->njacks) {
  rc = virtsnd_jack_build_devs(snd);
  if (rc)
   return rc;
 }

 if (snd->nsubstreams) {
  rc = virtsnd_pcm_build_devs(snd);
  if (rc)
   return rc;
 }

 if (snd->nchmaps) {
  rc = virtsnd_chmap_build_devs(snd);
  if (rc)
   return rc;
 }

 if (snd->nkctls) {
  rc = virtsnd_kctl_build_devs(snd);
  if (rc)
   return rc;
 }

 return snd_card_register(snd->card);
}

/**
 * virtsnd_validate() - Validate if the device can be started.
 * @vdev: VirtIO parent device.
 *
 * Context: Any context.
 * Return: 0 on success, -EINVAL on failure.
 */

static int virtsnd_validate(struct virtio_device *vdev)
{
 if (!vdev->config->get) {
  dev_err(&vdev->dev, "configuration access disabled\n");
  return -EINVAL;
 }

 if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
  dev_err(&vdev->dev,
   "device does not comply with spec version 1.x\n");
  return -EINVAL;
 }

 if (!virtsnd_msg_timeout_ms) {
  dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");
  return -EINVAL;
 }

 if (virtsnd_pcm_validate(vdev))
  return -EINVAL;

 return 0;
}

/**
 * virtsnd_probe() - Create and initialize the device.
 * @vdev: VirtIO parent device.
 *
 * Context: Any context that permits to sleep.
 * Return: 0 on success, -errno on failure.
 */

static int virtsnd_probe(struct virtio_device *vdev)
{
 struct virtio_snd *snd;
 unsigned int i;
 int rc;

 snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL);
 if (!snd)
  return -ENOMEM;

 snd->vdev = vdev;
 INIT_LIST_HEAD(&snd->ctl_msgs);
 INIT_LIST_HEAD(&snd->pcm_list);

 vdev->priv = snd;

 for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)
  spin_lock_init(&snd->queues[i].lock);

 rc = virtsnd_find_vqs(snd);
 if (rc)
  goto on_exit;

 virtio_device_ready(vdev);

 rc = virtsnd_build_devs(snd);
 if (rc)
  goto on_exit;

 virtsnd_enable_event_vq(snd);

on_exit:
 if (rc)
  virtsnd_remove(vdev);

 return rc;
}

/**
 * virtsnd_remove() - Remove VirtIO and ALSA devices.
 * @vdev: VirtIO parent device.
 *
 * Context: Any context that permits to sleep.
 */

static void virtsnd_remove(struct virtio_device *vdev)
{
 struct virtio_snd *snd = vdev->priv;
 unsigned int i;

 virtsnd_disable_event_vq(snd);
 virtsnd_ctl_msg_cancel_all(snd);

 if (snd->card)
  snd_card_free(snd->card);

 vdev->config->del_vqs(vdev);
 virtio_reset_device(vdev);

 for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) {
  struct virtio_pcm_substream *vss = &snd->substreams[i];

  cancel_work_sync(&vss->elapsed_period);
  virtsnd_pcm_msg_free(vss);
 }

 kfree(snd->event_msgs);
}

#ifdef CONFIG_PM_SLEEP
/**
 * virtsnd_freeze() - Suspend device.
 * @vdev: VirtIO parent device.
 *
 * Context: Any context.
 * Return: 0 on success, -errno on failure.
 */

static int virtsnd_freeze(struct virtio_device *vdev)
{
 struct virtio_snd *snd = vdev->priv;
 unsigned int i;

 virtsnd_disable_event_vq(snd);
 virtsnd_ctl_msg_cancel_all(snd);

 vdev->config->del_vqs(vdev);
 virtio_reset_device(vdev);

 for (i = 0; i < snd->nsubstreams; ++i)
  cancel_work_sync(&snd->substreams[i].elapsed_period);

 kfree(snd->event_msgs);
 snd->event_msgs = NULL;

 return 0;
}

/**
 * virtsnd_restore() - Resume device.
 * @vdev: VirtIO parent device.
 *
 * Context: Any context.
 * Return: 0 on success, -errno on failure.
 */

static int virtsnd_restore(struct virtio_device *vdev)
{
 struct virtio_snd *snd = vdev->priv;
 int rc;

 rc = virtsnd_find_vqs(snd);
 if (rc)
  return rc;

 virtio_device_ready(vdev);

 virtsnd_enable_event_vq(snd);

 return 0;
}
#endif /* CONFIG_PM_SLEEP */

static const struct virtio_device_id id_table[] = {
 { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },
 { 0 },
};

static unsigned int features[] = {
 VIRTIO_SND_F_CTLS
};

static struct virtio_driver virtsnd_driver = {
 .driver.name = KBUILD_MODNAME,
 .id_table = id_table,
 .feature_table = features,
 .feature_table_size = ARRAY_SIZE(features),
 .validate = virtsnd_validate,
 .probe = virtsnd_probe,
 .remove = virtsnd_remove,
#ifdef CONFIG_PM_SLEEP
 .freeze = virtsnd_freeze,
 .restore = virtsnd_restore,
#endif
};

module_virtio_driver(virtsnd_driver);

MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio sound card driver");
MODULE_LICENSE("GPL");

Messung V0.5
C=92 H=93 G=92

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.