[alsa-devel] [PATCH 09/15] ALSA: line6: Add hwdep interface to access the POD control messages
Takashi Iwai
tiwai at suse.de
Fri Aug 12 10:44:21 CEST 2016
On Thu, 11 Aug 2016 21:02:21 +0200,
Andrej Krutak wrote:
>
> We must do it this way, because e.g. POD X3 won't play any sound unless
> the host listens on the bulk EP, so we cannot export it only via libusb.
>
> The driver currently doesn't use the bulk EP messages in other way,
> in future it could e.g. sense/modify volume(s).
>
> Signed-off-by: Andrej Krutak <dev at andree.sk>
> ---
> include/uapi/sound/asound.h | 3 +-
> sound/usb/line6/driver.c | 167 ++++++++++++++++++++++++++++++++++++++++++++
> sound/usb/line6/driver.h | 12 ++++
> 3 files changed, 181 insertions(+), 1 deletion(-)
>
> diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
> index 609cadb..be353a7 100644
> --- a/include/uapi/sound/asound.h
> +++ b/include/uapi/sound/asound.h
> @@ -106,9 +106,10 @@ enum {
> SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */
> SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */
> SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
> + SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */
>
> /* Don't forget to change the following: */
> - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_TASCAM
> + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6
> };
>
> struct snd_hwdep_info {
> diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c
> index 8a71d45..0a0e324 100644
> --- a/sound/usb/line6/driver.c
> +++ b/sound/usb/line6/driver.c
> @@ -14,9 +14,11 @@
> #include <linux/export.h>
> #include <linux/slab.h>
> #include <linux/usb.h>
> +#include <linux/circ_buf.h>
>
> #include <sound/core.h>
> #include <sound/initval.h>
> +#include <sound/hwdep.h>
>
> #include "capture.h"
> #include "driver.h"
> @@ -315,8 +317,11 @@ static void line6_data_received(struct urb *urb)
> line6->process_message(line6);
> }
> } else {
> + line6->buffer_message = urb->transfer_buffer;
> + line6->message_length = urb->actual_length;
> if (line6->process_message)
> line6->process_message(line6);
> + line6->buffer_message = NULL;
> }
>
> line6_start_listen(line6);
> @@ -522,6 +527,163 @@ static void line6_get_interval(struct usb_line6 *line6)
> }
> }
>
> +
> +/* Enable buffering of incoming messages, flush the buffer */
> +static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file)
> +{
> + struct usb_line6 *line6 = hw->private_data;
> +
> + /* NOTE: hwdep already provides atomicity (exclusive == true), but for
> + * sure... */
> + if (test_and_set_bit(0, &line6->buffer_circular.active))
> + return -EBUSY;
> +
> + line6->buffer_circular.head = 0;
> + line6->buffer_circular.tail = 0;
> + sema_init(&line6->buffer_circular.sem, 0);
Why do you use semaphore, not mutex? And initializing at this
point...? It looks racy.
> + line6->buffer_circular.active = 1;
Looks superfluous, done in the above?
> + line6->buffer_circular.data =
> + kmalloc(LINE6_MESSAGE_MAXLEN * LINE6_MESSAGE_MAXCOUNT, GFP_KERNEL);
> + if (!line6->buffer_circular.data) {
> + return -ENOMEM;
> + }
> + line6->buffer_circular.data_len =
> + kmalloc(sizeof(*line6->buffer_circular.data_len) *
> + LINE6_MESSAGE_MAXCOUNT, GFP_KERNEL);
> + if (!line6->buffer_circular.data_len) {
> + kfree(line6->buffer_circular.data);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +/* Stop buffering */
> +static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file)
> +{
> + struct usb_line6 *line6 = hw->private_data;
> +
> + /* By this time, no readers are waiting, we can safely recreate the
> + * semaphore at next open. */
> + line6->buffer_circular.active = 0;
> +
> + kfree(line6->buffer_circular.data);
> + kfree(line6->buffer_circular.data_len);
> + return 0;
> +}
> +
> +/* Read from circular buffer, return to user */
> +static long
> +line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
> + loff_t *offset)
> +{
> + struct usb_line6 *line6 = hwdep->private_data;
> + unsigned long tail;
No need to use long for FIFO index.
> + if (down_interruptible(&line6->buffer_circular.sem)) {
> + return -ERESTARTSYS;
> + }
> + /* There must an item now in the buffer... */
> +
> + tail = line6->buffer_circular.tail;
> +
> + if (line6->buffer_circular.data_len[tail] > count) {
> + /* Buffer too small; allow re-read of the current item... */
> + up(&line6->buffer_circular.sem);
> + return -EINVAL;
> + }
> +
> + if (copy_to_user(buf,
> + &line6->buffer_circular.data[tail * LINE6_MESSAGE_MAXLEN],
> + line6->buffer_circular.data_len[tail])
> + ) {
> + rv = -EFAULT;
> + goto end;
> + } else {
> + rv = line6->buffer_circular.data_len[tail];
> + }
> +
> + smp_store_release(&line6->buffer_circular.tail,
> + (tail + 1) & (LINE6_MESSAGE_MAXCOUNT - 1));
> +
> + return 0;
> +}
> +
> +/* Write directly (no buffering) to device by user*/
> +static long
> +line6_hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
> + loff_t *offset)
> +{
> + struct usb_line6 *line6 = hwdep->private_data;
> + int rv;
> + char *data_copy;
> +
> + data_copy = kmalloc(count, GFP_ATOMIC);
No need to use GFP_ATOMIC in this context.
Also, you should add the sanity check of the given size. User may
pass any size of the write.
> + if (!data_copy)
> + return -ENOMEM;
> +
> + if (copy_from_user(data_copy, data, count))
> + rv = -EFAULT;
Maybe easier to use memdup_user() helper.
> + else
> + rv = line6_send_raw_message(line6, data_copy, count);
> +
> + kfree(data_copy);
> + return rv;
> +}
> +
> +static const struct snd_hwdep_ops hwdep_ops = {
> + .open = line6_hwdep_open,
> + .release = line6_hwdep_release,
> + .read = line6_hwdep_read,
> + .write = line6_hwdep_write,
> +};
> +
> +/* Insert into circular buffer */
> +static void line6_hwdep_push_message(struct usb_line6 *line6)
> +{
> + unsigned long head = line6->buffer_circular.head;
> + /* The spin_unlock() and next spin_lock() provide needed ordering. */
> + unsigned long tail = ACCESS_ONCE(line6->buffer_circular.tail);
> +
> + if (!line6->buffer_circular.active)
> + return;
> +
> + if (CIRC_SPACE(head, tail, LINE6_MESSAGE_MAXCOUNT) >= 1) {
> + unsigned char *item = &line6->buffer_circular.data[
> + head * LINE6_MESSAGE_MAXLEN];
> + memcpy(item, line6->buffer_message, line6->message_length);
> + line6->buffer_circular.data_len[head] = line6->message_length;
> +
> + smp_store_release(&line6->buffer_circular.head,
> + (head + 1) & (LINE6_MESSAGE_MAXCOUNT - 1));
> + up(&line6->buffer_circular.sem);
> + }
Hmm... this kind of a simple FIFO can be seen in anywhere in the
kernel code, and I'm sure that you can find an easier way to implement
it. The whole code looks a bit scary as it being home-brewed.
Also, the blocking read/write control isn't usually done by a
semaphore. Then you can handle the interrupt there.
Takashi
More information about the Alsa-devel
mailing list