[alsa-devel] [PATCH 00/15] Line6 POD X3/X3Live suport
Hello all,
attached is the first version of the driver, based on POD HD. They share a lot of similarities, but at the same time there were some generalizations needed. I think POD HD could follow up on these patches to add hwdep support, perhaps helix too... I tried to not introduce regressions to the old HW.
Anyhow, this is probably not the final version of the patches, as it is my first submission to upstream - I'm sure there will be some issues. Especially the patch "Use device_create_file instead of snd_card_add_dev_attr" I have to revisit, it may not be necessarry in the end.
Other than that, the patches mostly pass `make checkpatches`. I've been testing the driver for a while now, including lock debugging options etc., and there don't seem to be functional problems.
There's one missing thing - the driver uses bulk USB interface of the device, but so far I wasn't able to make the usb_driver_claim_interface() work... I hope this can be added later, if someone (or I) finds time.
Thanks for your inputs, greetings,
Andrej Krutak (15): ALSA: line6: Make driver configuration more generic. ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture ALSA: line6: Distinguish device init (ctrl EP) and MIDI data transfer (int EP) ALSA: line6: Add support for POD X3 ALSA: line6: Use device_create_file instead of snd_card_add_dev_attr ALSA: line6: Allow bulk endpoints instead of interrupt endpoints ALSA: line6: Allow processing of raw incoming messages ALSA: line6: Cleanup initialization ALSA: line6: Add hwdep interface to access the POD control messages ALSA: line6: Add proper locks for hwdep open/release/read ALSA: line6: Only free buffer if it is set. ALSA: line6: Give up on the lock while URBs are released. ALSA: line6: Add support for POD X3 Live (only USB ID differs from POD X3) ALSA: line6: Give up hwdep spinlock temporarily during read operation ALSA: line6: Remove double line6_pcm_release() after failed acquire.
include/uapi/sound/asound.h | 3 +- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/capture.c | 49 +++++-- sound/usb/line6/driver.c | 303 ++++++++++++++++++++++++++++++++++------ sound/usb/line6/driver.h | 71 +++++++--- sound/usb/line6/midi.c | 2 +- sound/usb/line6/pcm.c | 83 +++++++---- sound/usb/line6/pcm.h | 19 +-- sound/usb/line6/playback.c | 37 +++-- sound/usb/line6/pod.c | 12 +- sound/usb/line6/podhd.c | 328 +++++++++++++++++++++++++++++++++++++++++--- sound/usb/line6/toneport.c | 6 +- sound/usb/line6/variax.c | 6 +- 13 files changed, 767 insertions(+), 156 deletions(-)
The main reasons are different settings for USB low/high speed and possible different channel counts for in/out; required by POD X3.
This includes: * iso_buffers (count of iso buffers depends on USB speed, 2 is not enough for high speed) * bytes_per_frame -> bytes_per_channel * max_packet_size -> max_packet_size_in/out * USB_INTERVALS_PER_SECOND -> LOW/HIGH settings (high needs 8000, instead of 1000)
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 44 ++++++++++++++++++++++++++++++-------------- sound/usb/line6/driver.c | 15 ++++++++++----- sound/usb/line6/driver.h | 42 ++++++++++++++++++++++++++---------------- sound/usb/line6/pcm.c | 43 +++++++++++++++++++++++++------------------ sound/usb/line6/pcm.h | 15 +++++++-------- sound/usb/line6/playback.c | 37 +++++++++++++++++++++++++------------ sound/usb/line6/pod.c | 3 +-- sound/usb/line6/podhd.c | 4 +--- sound/usb/line6/toneport.c | 2 +- 9 files changed, 126 insertions(+), 79 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index f518fbb..bacf03f 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -29,10 +29,10 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) int ret; struct urb *urb_in;
- index = - find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->in.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -44,13 +44,13 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; fin->offset = urb_size; - fin->length = line6pcm->max_packet_size; - urb_size += line6pcm->max_packet_size; + fin->length = line6pcm->max_packet_size_in; + urb_size += line6pcm->max_packet_size_in; }
urb_in->transfer_buffer = line6pcm->in.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; urb_in->transfer_buffer_length = urb_size; urb_in->context = line6pcm;
@@ -73,7 +73,7 @@ int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_in_urb(line6pcm); if (ret < 0) break; @@ -90,7 +90,9 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); struct snd_pcm_runtime *runtime = substream->runtime; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max; int frames = fsize / bytes_per_frame;
if (runtime == NULL) @@ -154,7 +156,7 @@ static void audio_in_callback(struct urb *urb) line6pcm->in.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) if (urb == line6pcm->in.urbs[index]) break;
@@ -173,17 +175,26 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
- if (fsize > line6pcm->max_packet_size) { + if (fsize > line6pcm->max_packet_size_in) { dev_err(line6pcm->line6->ifcdev, "driver and/or device bug: packet too large (%d > %d)\n", - fsize, line6pcm->max_packet_size); + fsize, line6pcm->max_packet_size_in); }
length += fsize;
- /* the following assumes LINE6_ISO_PACKETS == 1: */ +#if LINE6_ISO_PACKETS != 1 +# error "The following assumes LINE6_ISO_PACKETS == 1" +/* TODO: + Also, if iso_buffers != 2, the prev frame is almost random at playback side. + This needs to be redesigned. It should be "stable", but we may experience + sync problems on such high-speed configs. +*/ +#endif line6pcm->prev_fbuf = fbuf; - line6pcm->prev_fsize = fsize; + line6pcm->prev_fsize = fsize / + (line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max);
if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) && @@ -247,8 +258,13 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->in.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio in: */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8..efeb16a8 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -462,13 +462,17 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep; - unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r); - unsigned epnum = usb_pipeendpoint(pipe); - - ep = usbdev->ep_in[epnum]; + struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; if (ep) { line6->interval = ep->desc.bInterval; + if (usbdev->speed == USB_SPEED_LOW) { + line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_LOW_ISO_BUFFERS; + } else { + line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_HIGH_ISO_BUFFERS; + } + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); } else { dev_err(line6->ifcdev, @@ -558,6 +562,7 @@ int line6_probe(struct usb_interface *interface, /* query interface number */ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+ /* TODO reserves the bus bandwidth even without actual transfer */ ret = usb_set_interface(usbdev, interface_number, properties->altsetting); if (ret < 0) { diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7da643e..24cd667 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,39 +18,45 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND (1000) +#define USB_LOW_ISO_BUFFERS (2) + +/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND (8000) +#define USB_HIGH_ISO_BUFFERS (16)
/* Fallback USB interval and max packet size values */ -#define LINE6_FALLBACK_INTERVAL 10 -#define LINE6_FALLBACK_MAXPACKETSIZE 16 +#define LINE6_FALLBACK_INTERVAL (10) +#define LINE6_FALLBACK_MAXPACKETSIZE (16)
-#define LINE6_TIMEOUT 1 -#define LINE6_BUFSIZE_LISTEN 32 -#define LINE6_MESSAGE_MAXLEN 256 +#define LINE6_TIMEOUT (1) +#define LINE6_BUFSIZE_LISTEN (64) +#define LINE6_MESSAGE_MAXLEN (256)
/* Line 6 MIDI control commands */ -#define LINE6_PARAM_CHANGE 0xb0 -#define LINE6_PROGRAM_CHANGE 0xc0 -#define LINE6_SYSEX_BEGIN 0xf0 -#define LINE6_SYSEX_END 0xf7 -#define LINE6_RESET 0xff +#define LINE6_PARAM_CHANGE (0xb0) +#define LINE6_PROGRAM_CHANGE (0xc0) +#define LINE6_SYSEX_BEGIN (0xf0) +#define LINE6_SYSEX_END (0xf7) +#define LINE6_RESET (0xff)
/* MIDI channel for messages initiated by the host (and eventually echoed back by the device) */ -#define LINE6_CHANNEL_HOST 0x00 +#define LINE6_CHANNEL_HOST (0x00)
/* MIDI channel for messages initiated by the device */ -#define LINE6_CHANNEL_DEVICE 0x02 +#define LINE6_CHANNEL_DEVICE (0x02)
-#define LINE6_CHANNEL_UNKNOWN 5 /* don't know yet what this is good for */ +#define LINE6_CHANNEL_UNKNOWN (5) /* don't know yet what this is good for */
-#define LINE6_CHANNEL_MASK 0x0f +#define LINE6_CHANNEL_MASK (0x0f)
#define CHECK_STARTUP_PROGRESS(x, n) \ do { \ @@ -109,8 +115,12 @@ struct usb_line6 { /* Properties */ const struct line6_properties *properties;
- /* Interval (ms) */ + /* Interval (frames) */ int interval; + int intervals_per_second; + + /* Number of isochronous URBs used for frame transfers */ + int iso_buffers;
/* Maximum size of USB packet */ int max_packet_size; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 204cc07..210c85f 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -105,7 +105,7 @@ static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) { if (!test_and_set_bit(i, &pcms->unlink_urbs)) usb_unlink_urb(pcms->urbs[i]); @@ -125,7 +125,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
do { alive = 0; - for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) alive++; } @@ -147,15 +147,20 @@ get_stream(struct snd_line6_pcm *line6pcm, int direction) }
/* allocate a buffer if not opened yet; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, - struct line6_pcm_stream *pstr, int type) + struct line6_pcm_stream *pstr, int direction, int type) { + const int pkt_size = + (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + line6pcm->max_packet_size_out : + line6pcm->max_packet_size_in; + /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { - pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * + LINE6_ISO_PACKETS * pkt_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -163,12 +168,11 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, }
/* free a buffer if all streams are closed; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, struct line6_pcm_stream *pstr, int type) { - clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); @@ -195,6 +199,7 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, else ret = line6_submit_audio_in_all_urbs(line6pcm); } + if (ret < 0) clear_bit(type, &pstr->running); spin_unlock_irqrestore(&pstr->lock, flags); @@ -433,24 +438,26 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < iso_buffers; i++) { if (pcms->urbs[i]) { usb_kill_urb(pcms->urbs[i]); usb_free_urb(pcms->urbs[i]); } } + kfree(pcms->urbs); + pcms->urbs = NULL; }
static void line6_cleanup_pcm(struct snd_pcm *pcm) { struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
- cleanup_urbs(&line6pcm->out); - cleanup_urbs(&line6pcm->in); + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); kfree(line6pcm); }
@@ -522,12 +529,12 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6;
- /* Read and write buffers are sized identically, so choose minimum */ - line6pcm->max_packet_size = min( - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0), - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1)); + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1);
spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 508410a..58d36f9 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -20,9 +20,6 @@
#include "driver.h"
-/* number of URBs */ -#define LINE6_ISO_BUFFERS 2 - /* number of USB frames per URB The Line 6 Windows driver always transmits two frames per packet, but @@ -31,7 +28,8 @@ */ #define LINE6_ISO_PACKETS 1
-/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +/* in a "full speed" device (such as the PODxt Pro) this means 1ms, + for "high speed" it's 1/8ms */ #define LINE6_ISO_INTERVAL 1
#define LINE6_IMPULSE_DEFAULT_PERIOD 100 @@ -85,12 +83,12 @@ enum { struct line6_pcm_properties { struct snd_pcm_hardware playback_hw, capture_hw; struct snd_pcm_hw_constraint_ratdens rates; - int bytes_per_frame; + int bytes_per_channel; };
struct line6_pcm_stream { /* allocated URBs */ - struct urb *urbs[LINE6_ISO_BUFFERS]; + struct urb **urbs;
/* Temporary buffer; * Since the packet size is not known in advance, this buffer is @@ -157,11 +155,12 @@ struct snd_line6_pcm { /* Previously captured frame (for software monitoring) */ unsigned char *prev_fbuf;
- /* Size of previously captured frame (for software monitoring) */ + /* Size of previously captured frame (for software monitoring/sync) */ int prev_fsize;
/* Maximum size of USB packet */ - int max_packet_size; + int max_packet_size_in; + int max_packet_size_out;
/* PCM playback volume (left and right) */ int volume_playback[2]; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 97ed593..4833c51 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -146,18 +146,20 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) int index; int i, urb_size, urb_frames; int ret; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; const int frame_increment = line6pcm->properties->rates.rats[0].num_min; const int frame_factor = line6pcm->properties->rates.rats[0].den * - (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out;
- index = - find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->out.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -165,6 +167,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out = line6pcm->out.urbs[index]; urb_size = 0;
+ /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */ for (i = 0; i < LINE6_ISO_PACKETS; ++i) { /* compute frame size for given sampling rate */ int fsize = 0; @@ -178,9 +181,11 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->out.count += frame_increment; n = line6pcm->out.count / frame_factor; line6pcm->out.count -= n * frame_factor; - fsize = n * bytes_per_frame; + fsize = n; }
+ fsize *= bytes_per_frame; + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; @@ -195,7 +200,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_frames = urb_size / bytes_per_frame; urb_out->transfer_buffer = line6pcm->out.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; urb_out->transfer_buffer_length = urb_size; urb_out->context = line6pcm;
@@ -286,7 +291,7 @@ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) break; @@ -305,6 +310,9 @@ static void audio_out_callback(struct urb *urb) struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max;
#if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -313,11 +321,11 @@ static void audio_out_callback(struct urb *urb) line6pcm->out.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; index++) + for (index = 0; index < line6pcm->line6->iso_buffers; index++) if (urb == line6pcm->out.urbs[index]) break;
- if (index >= LINE6_ISO_BUFFERS) + if (index >= line6pcm->line6->iso_buffers) return; /* URB has been unlinked asynchronously */
for (i = 0; i < LINE6_ISO_PACKETS; i++) @@ -329,7 +337,7 @@ static void audio_out_callback(struct urb *urb) struct snd_pcm_runtime *runtime = substream->runtime;
line6pcm->out.pos_done += - length / line6pcm->properties->bytes_per_frame; + length / bytes_per_frame;
if (line6pcm->out.pos_done >= runtime->buffer_size) line6pcm->out.pos_done -= runtime->buffer_size; @@ -401,8 +409,13 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->out.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio out: */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index daf81d1..31e5864 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -83,7 +83,6 @@ struct usb_line6_pod { };
#define POD_SYSEX_CODE 3 -#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
/* *INDENT-OFF* */
@@ -167,7 +166,7 @@ static struct line6_pcm_properties pod_pcm_properties = { .rates = { .nrats = 1, .rats = &pod_ratden}, - .bytes_per_frame = POD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
static const char pod_version_header[] = { diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 63dcaef..4fc4789 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -25,8 +25,6 @@ enum { LINE6_PODHD500_1, };
-#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ - static struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, @@ -73,7 +71,7 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_frame = PODHD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* 24bit audio (stereo) */ };
/* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 6d4c50c..da76e03 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -114,7 +114,7 @@ static struct line6_pcm_properties toneport_pcm_properties = { .rates = { .nrats = 1, .rats = &toneport_ratden}, - .bytes_per_frame = 4 + .bytes_per_channel = 2 };
static const struct {
On Thu, 11 Aug 2016 21:02:13 +0200, Andrej Krutak wrote:
The main reasons are different settings for USB low/high speed and possible different channel counts for in/out; required by POD X3.
This includes:
- iso_buffers (count of iso buffers depends on USB speed, 2 is not enough for high speed)
- bytes_per_frame -> bytes_per_channel
- max_packet_size -> max_packet_size_in/out
- USB_INTERVALS_PER_SECOND -> LOW/HIGH settings (high needs 8000, instead of 1000)
The changes are slightly too many done in a shot. It'd be great if you can split to a few each logical change if possible.
--- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c
.....
@@ -173,17 +175,26 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
if (fsize > line6pcm->max_packet_size) {
if (fsize > line6pcm->max_packet_size_in) { dev_err(line6pcm->line6->ifcdev, "driver and/or device bug: packet too large (%d > %d)\n",
fsize, line6pcm->max_packet_size);
fsize, line6pcm->max_packet_size_in);
}
length += fsize;
/* the following assumes LINE6_ISO_PACKETS == 1: */
+#if LINE6_ISO_PACKETS != 1 +# error "The following assumes LINE6_ISO_PACKETS == 1" +/* TODO:
- Also, if iso_buffers != 2, the prev frame is almost random at playback side.
- This needs to be redesigned. It should be "stable", but we may experience
- sync problems on such high-speed configs.
+*/ +#endif
You can use BUILD_BUG_ON().
--- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,39 +18,45 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND (1000) +#define USB_LOW_ISO_BUFFERS (2)
+/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND (8000) +#define USB_HIGH_ISO_BUFFERS (16)
Superfluous parentheses (all other places, too).
--- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c
....
@@ -433,24 +438,26 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) {
- for (i = 0; i < iso_buffers; i++) { if (pcms->urbs[i]) {
This may cause NULL-dereference when pcms->urbs wasn't allocated.
thanks,
Takashi
E.g. POD X3 seems to require playback data to be sent to it to generate capture data. Otherwise the device stalls and doesn't send any more capture data until it's reset.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 5 +++++ sound/usb/line6/driver.h | 4 +++- sound/usb/line6/pcm.c | 37 ++++++++++++++++++++++++++++--------- sound/usb/line6/pcm.h | 4 +++- sound/usb/line6/toneport.c | 4 ++-- 5 files changed, 41 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index bacf03f..21a297e 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -231,6 +231,8 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err;
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); + runtime->hw = line6pcm->properties->capture_hw; return 0; } @@ -238,6 +240,9 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) /* close capture callback */ static int snd_line6_capture_close(struct snd_pcm_substream *substream) { + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER); return 0; }
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 24cd667..0f0c45a 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -100,8 +100,10 @@ enum { LINE6_CAP_CONTROL = 1 << 0, /* device supports PCM input/output via USB */ LINE6_CAP_PCM = 1 << 1, - /* device support hardware monitoring */ + /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device requires output data when input is read */ + LINE6_CAP_IN_NEEDS_OUT = 1 << 4, };
/* diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 210c85f..0af6d4e 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -52,7 +52,7 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
line6pcm->impulse_volume = value; if (value > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0; line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE); @@ -241,6 +241,15 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: + if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT) + ) { + err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + if (err < 0) + return err; + } err = line6_stream_start(line6pcm, s->stream, LINE6_STREAM_PCM); if (err < 0) @@ -249,6 +258,13 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: + if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT) + ) { + line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + } line6_stream_stop(line6pcm, s->stream, LINE6_STREAM_PCM); break; @@ -282,27 +298,30 @@ snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) return pstr->pos_done; }
-/* Acquire and start duplex streams: +/* Acquire and optionally start duplex streams: * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR */ -int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) { struct line6_pcm_stream *pstr; int ret = 0, dir;
+ /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ mutex_lock(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); - ret = line6_buffer_acquire(line6pcm, pstr, type); + ret = line6_buffer_acquire(line6pcm, pstr, dir, type); if (ret < 0) goto error; if (!pstr->running) line6_wait_clear_audio_urbs(line6pcm, pstr); } - for (dir = 0; dir < 2; dir++) { - ret = line6_stream_start(line6pcm, dir, type); - if (ret < 0) - goto error; + if (start) { + for (dir = 0; dir < 2; dir++) { + ret = line6_stream_start(line6pcm, dir, type); + if (ret < 0) + goto error; + } } error: mutex_unlock(&line6pcm->state_mutex); @@ -338,7 +357,7 @@ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
mutex_lock(&line6pcm->state_mutex); - ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM); + ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, LINE6_STREAM_PCM); if (ret < 0) goto error;
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 58d36f9..5f796ef8 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -72,6 +72,7 @@ enum { LINE6_STREAM_PCM, LINE6_STREAM_MONITOR, LINE6_STREAM_IMPULSE, + LINE6_STREAM_CAPTURE_HELPER, };
/* misc bit flags for PCM operation */ @@ -190,7 +191,8 @@ extern int snd_line6_hw_params(struct snd_pcm_substream *substream, extern int snd_line6_hw_free(struct snd_pcm_substream *substream); extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream); extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); -extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, + bool start); extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
#endif diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index da76e03..8e22f43 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -177,7 +177,7 @@ static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, line6pcm->volume_monitor = ucontrol->value.integer.value[0];
if (line6pcm->volume_monitor > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true); if (err < 0) { line6pcm->volume_monitor = 0; line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); @@ -246,7 +246,7 @@ static void toneport_start_pcm(unsigned long arg) struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg; struct usb_line6 *line6 = &toneport->line6;
- line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR); + line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); }
/* control definition */
POD X3 can initialize similarly to older PODs, but it doesn't have the MIDI interface. Instead, configuration is done via proprietary bulk EP messages.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 10 +++++----- sound/usb/line6/driver.h | 6 ++++-- sound/usb/line6/pod.c | 9 ++++++++- sound/usb/line6/variax.c | 6 ++++-- 4 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index efeb16a8..5fd6cad 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -482,7 +482,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control(struct usb_line6 *line6) +static int line6_init_cap_control_midi(struct usb_line6 *line6) { int ret;
@@ -572,8 +572,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL) { - ret = line6_init_cap_control(line6); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + ret = line6_init_cap_control_midi(line6); if (ret < 0) goto error; } @@ -643,7 +643,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -662,7 +662,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 0f0c45a..2551cb5 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -102,6 +102,8 @@ enum { LINE6_CAP_PCM = 1 << 1, /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device uses raw MIDI via USB (data endpoints) */ + LINE6_CAP_CONTROL_MIDI = 1 << 3, /* device requires output data when input is read */ LINE6_CAP_IN_NEEDS_OUT = 1 << 4, }; @@ -141,10 +143,10 @@ struct usb_line6 { /* Line 6 MIDI device data structure */ struct snd_line6_midi *line6midi;
- /* URB for listening to PODxt Pro control endpoint */ + /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to PODxt Pro control endpoint */ + /* Buffer for listening to POD data endpoint */ unsigned char *buffer_listen;
/* Buffer for message to be processed */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 31e5864..78642e7 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -475,6 +475,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxt", .name = "BassPODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -487,6 +488,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtLive", .name = "BassPODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -499,6 +501,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtPro", .name = "BassPODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -510,7 +513,8 @@ static const struct line6_properties pod_properties_table[] = { [LINE6_POCKETPOD] = { .id = "PocketPOD", .name = "Pocket POD", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 0, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x02, @@ -520,6 +524,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxt", .name = "PODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -532,6 +537,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtLive", .name = "PODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -544,6 +550,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtPro", .name = "PODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c index ddc23dd..0c4512d 100644 --- a/sound/usb/line6/variax.c +++ b/sound/usb/line6/variax.c @@ -259,7 +259,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_PODXTLIVE_VARIAX] = { .id = "PODxtLive", .name = "PODxt Live", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x86, .ep_ctrl_w = 0x05, @@ -269,7 +270,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_VARIAX] = { .id = "Variax", .name = "Variax Workbench", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x01,
This includes audio in/out and basic initialization via control EP (emulates what original driver does). The initialization is done similarly to original POD, firmware and serial IDs are read and exported via sysfs.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/podhd.c | 276 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 265 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig index f4585d37..8ffcf48 100644 --- a/sound/usb/line6/Kconfig +++ b/sound/usb/line6/Kconfig @@ -21,10 +21,10 @@ config SND_USB_POD re-amping)
config SND_USB_PODHD - tristate "Line 6 POD HD300/400/500 USB support" + tristate "Line 6 POD X3/HD300/400/500 USB support" select SND_USB_LINE6 help - This is a driver for POD HD300, 400 and 500 devices. + This is a driver for POD X3, HD300, 400 and 500 devices.
config SND_USB_TONEPORT tristate "TonePort GX, UX1 and UX2 USB support" diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 4fc4789..42fa376 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -2,6 +2,7 @@ * Line 6 Pod HD * * Copyright (C) 2011 Stefan Hajnoczi stefanha@gmail.com + * Copyright (C) 2015 Andrej Krutak dev@andree.sk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,11 +19,44 @@ #include "driver.h" #include "pcm.h"
+#define PODHD_STARTUP_DELAY 500 + +/* + * Stages of POD startup procedure + */ +enum { + PODHD_STARTUP_INIT = 1, + PODHD_STARTUP_SCHEDULE_WORKQUEUE, + PODHD_STARTUP_SETUP, + PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 +}; + enum { LINE6_PODHD300, LINE6_PODHD400, LINE6_PODHD500_0, LINE6_PODHD500_1, + LINE6_PODX3, +}; + +struct usb_line6_podhd { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version */ + int firmware_version; };
static struct snd_ratden podhd_ratden = { @@ -71,9 +105,196 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_channel = 3 /* 24bit audio (stereo) */ + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
+static struct line6_pcm_properties podx3_pcm_properties = { + .playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + /* 1+2: Main signal (out), 3+4: Tone 1, + * 5+6: Tone 2, 7+8: raw */ + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static void podhd_startup_start_workqueue(unsigned long data); +static void podhd_startup_workqueue(struct work_struct *work); +static int podhd_startup_finalize(struct usb_line6_podhd *pod); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_podhd *pod = usb_get_intfdata(interface); + + return sprintf(buf, "%u\n", pod->serial_number); +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_podhd *pod = usb_get_intfdata(interface); + + return sprintf(buf, "%06x\n", pod->firmware_version); +} + +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *podhd_dev_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group podhd_dev_attr_group = { + .name = "podhd", + .attrs = podhd_dev_attrs, +}; + +/* + * POD X3 startup procedure. + * + * May be compatible with other POD HD's, since it's also similar to the + * previous POD setup. In any case, it doesn't seem to be required for the + * audio nor bulk interfaces to work. + */ + +static void podhd_startup(struct usb_line6_podhd *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, + podhd_startup_start_workqueue, (unsigned long)pod); +} + +static void podhd_startup_start_workqueue(unsigned long data) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)data; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, + PODHD_STARTUP_SCHEDULE_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static int podhd_dev_start(struct usb_line6_podhd *pod) +{ + int ret; + u8 init_bytes[8]; + int i; + struct usb_device *usbdev = pod->line6.usbdev; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x11, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* NOTE: looks like some kind of ping message */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x11, 0x0, + &init_bytes, 3, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, + "receive length failed (error %d)\n", ret); + return ret; + } + + pod->firmware_version = + (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); + + for (i = 0; i <= 16; i++) { + ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); + if (ret < 0) + return ret; + } + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, + 1, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void podhd_startup_workqueue(struct work_struct *work) +{ + struct usb_line6_podhd *pod = + container_of(work, struct usb_line6_podhd, startup_work); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + + podhd_dev_start(pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + + podhd_startup_finalize(pod); +} + +static int podhd_startup_finalize(struct usb_line6_podhd *pod) +{ + struct usb_line6 *line6 = &pod->line6; + + /* ALSA audio interface: */ + return snd_card_register(line6->card); +} + +static void podhd_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + } +} + /* Try to init POD HD device. */ @@ -81,6 +302,16 @@ static int podhd_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; + struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + + line6->disconnect = podhd_disconnect; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); + if (err < 0) + return err; + }
/* initialize MIDI subsystem: */ err = line6_init_midi(line6); @@ -88,12 +319,22 @@ static int podhd_init(struct usb_line6 *line6, return err;
/* initialize PCM subsystem: */ - err = line6_init_pcm(line6, &podhd_pcm_properties); + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); if (err < 0) return err;
- /* register USB audio system: */ - return snd_card_register(line6->card); + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { + /* register USB audio system directly */ + return podhd_startup_finalize(pod); + } + + /* init device and delay registering */ + init_timer(&pod->startup_timer); + INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + podhd_startup(pod); + return 0; }
#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) @@ -101,10 +342,12 @@ static int podhd_init(struct usb_line6 *line6,
/* table of devices that work with this driver */ static const struct usb_device_id podhd_id_table[] = { + /* TODO: no need to alloc data interfaces when only audio is used */ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, {} };
@@ -114,8 +357,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD300] = { .id = "PODHD300", .name = "POD HD300", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -126,8 +368,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD400] = { .id = "PODHD400", .name = "POD HD400", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -138,8 +379,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_0] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -150,8 +390,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_1] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -159,6 +398,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3] = { + .id = "PODX3", + .name = "POD X3", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/* @@ -169,7 +419,7 @@ static int podhd_probe(struct usb_interface *interface, { return line6_probe(interface, id, "Line6-PODHD", &podhd_properties_table[id->driver_info], - podhd_init, sizeof(struct usb_line6)); + podhd_init, sizeof(struct usb_line6_podhd)); }
static struct usb_driver podhd_driver = {
The latter seems to create invalid configuration, the device pointer passed to the attr callbacks is not correct afterwards (results into segfaults when accessing the attributes).
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 60 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 16 deletions(-)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 42fa376..f72803a 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -57,6 +57,9 @@ struct usb_line6_podhd {
/* Firmware version */ int firmware_version; + + /* Flag whether the sysfs files were created */ + int devfiles_created; };
static struct snd_ratden podhd_ratden = { @@ -177,16 +180,37 @@ static ssize_t firmware_version_show(struct device *dev, static DEVICE_ATTR_RO(firmware_version); static DEVICE_ATTR_RO(serial_number);
-static struct attribute *podhd_dev_attrs[] = { - &dev_attr_firmware_version.attr, - &dev_attr_serial_number.attr, - NULL -}; +/* + * Create sysfs entries. + */ +static int podhd_create_attrs(struct device *dev) +{ + int err; + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_podhd *pod = usb_get_intfdata(interface);
-static const struct attribute_group podhd_dev_attr_group = { - .name = "podhd", - .attrs = podhd_dev_attrs, -}; + err = device_create_file(dev, &dev_attr_firmware_version); + if (err < 0) + return err; + err = device_create_file(dev, &dev_attr_serial_number); + if (err < 0) + return err; + + pod->devfiles_created = 1; + return 0; +} + +static void podhd_remove_attrs(struct device *dev) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_podhd *pod = usb_get_intfdata(interface); + + if (pod->devfiles_created) { + device_remove_file(dev, &dev_attr_firmware_version); + device_remove_file(dev, &dev_attr_serial_number); + pod->devfiles_created = 0; + } +}
/* * POD X3 startup procedure. @@ -281,6 +305,14 @@ static int podhd_startup_finalize(struct usb_line6_podhd *pod) { struct usb_line6 *line6 = &pod->line6;
+ if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* create sysfs entries: */ + int err = podhd_create_attrs(line6->ifcdev); + + if (err < 0) + return err; + } + /* ALSA audio interface: */ return snd_card_register(line6->card); } @@ -288,8 +320,11 @@ static int podhd_startup_finalize(struct usb_line6_podhd *pod) static void podhd_disconnect(struct usb_line6 *line6) { struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + struct device *dev = line6->ifcdev;
if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + podhd_remove_attrs(dev); + del_timer_sync(&pod->startup_timer); cancel_work_sync(&pod->startup_work); } @@ -306,13 +341,6 @@ static int podhd_init(struct usb_line6 *line6,
line6->disconnect = podhd_disconnect;
- if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { - /* create sysfs entries: */ - err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); - if (err < 0) - return err; - } - /* initialize MIDI subsystem: */ err = line6_init_midi(line6); if (err < 0)
On Thu, 11 Aug 2016 21:02:17 +0200, Andrej Krutak wrote:
The latter seems to create invalid configuration, the device pointer passed to the attr callbacks is not correct afterwards (results into segfaults when accessing the attributes).
As you already considered, this change is moving to a wrong direction. The explicit call of device_create_file() should be reduced as much as possible in general.
Takashi
Some PODs (e.g. POD X3) have bulk instead of interrupt endpoints for data transfer.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 63 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 5fd6cad..9b16777 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -66,10 +66,17 @@ static int line6_start_listen(struct usb_line6 *line6) { int err;
- usb_fill_int_urb(line6->urb_listen, line6->usbdev, - usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), - line6->buffer_listen, LINE6_BUFSIZE_LISTEN, - line6_data_received, line6, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + } else { + usb_fill_bulk_urb(line6->urb_listen, line6->usbdev, + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6); + } line6->urb_listen->actual_length = 0; err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); return err; @@ -90,6 +97,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0; + const struct line6_properties *properties = line6->properties;
for (i = 0; i < size; i += line6->max_packet_size) { int partial; @@ -97,15 +105,21 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int frag_size = min(line6->max_packet_size, size - i); int retval;
- retval = usb_interrupt_msg(line6->usbdev, - usb_sndintpipe(line6->usbdev, - line6->properties->ep_ctrl_w), - (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + } else { + retval = usb_bulk_msg(line6->usbdev, + usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + }
if (retval) { dev_err(line6->ifcdev, - "usb_interrupt_msg failed (%d)\n", retval); + "usb_bulk_msg failed (%d)\n", retval); break; }
@@ -140,10 +154,17 @@ static int line6_send_raw_message_async_part(struct message *msg, int done = msg->done; int bytes = min(msg->size - done, line6->max_packet_size);
- usb_fill_int_urb(urb, line6->usbdev, - usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), - (char *)msg->buffer + done, bytes, - line6_async_request_sent, msg, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + } else { + usb_fill_bulk_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg); + }
msg->done += bytes; retval = usb_submit_urb(urb, GFP_ATOMIC); @@ -462,7 +483,19 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; + const struct line6_properties *properties = line6->properties; + int pipe; + struct usb_host_endpoint *ep; + + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + pipe = + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } else { + pipe = + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } + ep = usbdev->ep_in[usb_pipeendpoint(pipe)]; + if (ep) { line6->interval = ep->desc.bInterval; if (usbdev->speed == USB_SPEED_LOW) {
Not all PODs use MIDI via USB data interface, thus allow avoiding that code and instead using direct processing.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 59 ++++++++++++++++++++++++++++-------------------- sound/usb/line6/driver.h | 8 ++++--- sound/usb/line6/midi.c | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9b16777..853a143 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -290,26 +290,31 @@ static void line6_data_received(struct urb *urb) if (urb->status == -ESHUTDOWN) return;
- done = - line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (done < urb->actual_length) { - line6_midibuf_ignore(mb, done); - dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", - done, urb->actual_length); - } + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + }
- for (;;) { - done = - line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MESSAGE_MAXLEN);
- if (done == 0) - break; + if (done == 0) + break;
- line6->message_length = done; - line6_midi_receive(line6, line6->buffer_message, done); + line6->message_length = done; + line6_midi_receive(line6, line6->buffer_message, done);
+ if (line6->process_message) + line6->process_message(line6); + } + } else { if (line6->process_message) line6->process_message(line6); } @@ -469,7 +474,9 @@ static void line6_destruct(struct snd_card *card) struct usb_device *usbdev = line6->usbdev;
/* free buffer memory first: */ - kfree(line6->buffer_message); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + kfree(line6->buffer_message); + kfree(line6->buffer_listen);
/* then free URBs: */ @@ -515,7 +522,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control_midi(struct usb_line6 *line6) +static int line6_init_cap_control(struct usb_line6 *line6) { int ret;
@@ -524,14 +531,16 @@ static int line6_init_cap_control_midi(struct usb_line6 *line6) if (!line6->buffer_listen) return -ENOMEM;
- line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); - if (!line6->buffer_message) - return -ENOMEM; - line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); if (!line6->urb_listen) return -ENOMEM;
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + if (!line6->buffer_message) + return -ENOMEM; + } + ret = line6_start_listen(line6); if (ret < 0) { dev_err(line6->ifcdev, "cannot start listening: %d\n", ret); @@ -605,8 +614,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - ret = line6_init_cap_control_midi(line6); + if (properties->capabilities & LINE6_CAP_CONTROL) { + ret = line6_init_cap_control(line6); if (ret < 0) goto error; } @@ -676,7 +685,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -695,7 +704,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 2551cb5..71ad299 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -146,15 +146,17 @@ struct usb_line6 { /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to POD data endpoint */ + /* Buffer for incoming data from POD data endpoint */ unsigned char *buffer_listen;
- /* Buffer for message to be processed */ + /* Buffer for message to be processed, generated from MIDI layer */ unsigned char *buffer_message;
- /* Length of message to be processed */ + /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* If MIDI is supported, buffer_message contains the pre-processed data; + * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *); void (*disconnect)(struct usb_line6 *line6); }; diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c index cebea9b..d0fb2f2 100644 --- a/sound/usb/line6/midi.c +++ b/sound/usb/line6/midi.c @@ -258,7 +258,7 @@ int line6_init_midi(struct usb_line6 *line6) struct snd_rawmidi *rmidi; struct snd_line6_midi *line6midi;
- if (!(line6->properties->capabilities & LINE6_CAP_CONTROL)) { + if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { /* skip MIDI initialization and report success */ return 0; }
Only determine control port properties if the devices needs it. Only initialize PCM for POD HD devices that support it. No POD HD seems to support MIDI, thus drop the initialization.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 3 +-- sound/usb/line6/podhd.c | 19 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 853a143..8a71d45 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -612,9 +612,8 @@ int line6_probe(struct usb_interface *interface, goto error; }
- line6_get_interval(line6); - if (properties->capabilities & LINE6_CAP_CONTROL) { + line6_get_interval(line6); ret = line6_init_cap_control(line6); if (ret < 0) goto error; diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index f72803a..80ed138 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -341,17 +341,14 @@ static int podhd_init(struct usb_line6 *line6,
line6->disconnect = podhd_disconnect;
- /* initialize MIDI subsystem: */ - err = line6_init_midi(line6); - if (err < 0) - return err; - - /* initialize PCM subsystem: */ - err = line6_init_pcm(line6, - (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : - &podhd_pcm_properties); - if (err < 0) - return err; + if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); + if (err < 0) + return err; + }
if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { /* register USB audio system directly */
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@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); + + line6->buffer_circular.active = 1; + + 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; + + 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); + if (!data_copy) + return -ENOMEM; + + if (copy_from_user(data_copy, data, count)) + rv = -EFAULT; + 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); + } +} + +static int line6_hwdep_init(struct usb_line6 *line6) +{ + int err; + struct snd_hwdep *hwdep; + + /* TODO: usb_driver_claim_interface(); */ + line6->process_message = line6_hwdep_push_message; + + line6->buffer_circular.active = 0; + err = snd_hwdep_new(line6->card, "config", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "config"); + hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; + hwdep->ops = hwdep_ops; + hwdep->private_data = line6; + hwdep->exclusive = true; + +end: + return err; +} + static int line6_init_cap_control(struct usb_line6 *line6) { int ret; @@ -539,6 +701,10 @@ static int line6_init_cap_control(struct usb_line6 *line6) line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); if (!line6->buffer_message) return -ENOMEM; + } else { + ret = line6_hwdep_init(line6); + if (ret < 0) + return ret; }
ret = line6_start_listen(line6); @@ -716,3 +882,4 @@ EXPORT_SYMBOL_GPL(line6_resume); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); + diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 71ad299..f4ab6cd 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -33,6 +33,8 @@ #define LINE6_TIMEOUT (1) #define LINE6_BUFSIZE_LISTEN (64) #define LINE6_MESSAGE_MAXLEN (256) +/* Must be 2^n; 4k packets are common, MAXLEN * MAXCOUNT should be bigger... */ +#define LINE6_MESSAGE_MAXCOUNT (1 << 5)
/* Line 6 MIDI control commands @@ -155,6 +157,16 @@ struct usb_line6 { /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* Circular buffer for non-MIDI control messages */ + struct { + int active; + char *data; + int *data_len; + unsigned long head, tail; + /* Actually is up'd # of items in the buffer - times */ + struct semaphore sem; + } buffer_circular; + /* If MIDI is supported, buffer_message contains the pre-processed data; * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *);
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@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
On Fri, Aug 12, 2016 at 10:44 AM, Takashi Iwai tiwai@suse.de wrote:
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@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.
I can move it to hwdep_init, but here it should be fine too, since hwdep_open() is serialized by hwdep layer.
Semaphore is used because it also uses as counter for hwdep_read, which otherwise I'd have to do manually. I could rewrite this to use waitqueues, but when the counter is added to that, I end up basically with semaphore reimplementation... Do you see this as a big issue?
line6->buffer_circular.active = 1;
Looks superfluous, done in the above?
This is here to just drop the USB packets if there's no active reader. To save some CPU time basically...
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.
See below.
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.
This code is based on Documentation/circular-buffers.txt, except for the semaphore magic.
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
On Fri, 12 Aug 2016 13:10:37 +0200, Andrej Kruták wrote:
+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.
This code is based on Documentation/circular-buffers.txt, except for the semaphore magic.
The example there is basically a semi lock-free implementation. For your purpose it's an overkill. This is no severely hot path, thus a simpler version would make life easier.
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
Takashi
On Fri, Aug 12, 2016 at 2:03 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 13:10:37 +0200, Andrej Kruták wrote:
+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.
This code is based on Documentation/circular-buffers.txt, except for the semaphore magic.
The example there is basically a semi lock-free implementation. For your purpose it's an overkill. This is no severely hot path, thus a simpler version would make life easier.
Fair enough, I'll spinlock-ize it then.
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
You can - down_interruptible() is there for this exact reason. As said, semaphore is basically wait-queue + counter, so there's no real reason not to do this IMO.
But in the end - if you have some nice example of FIFO buffer in some simple driver, I have no problem using a more already-proven/standard way :-)
On Fri, 12 Aug 2016 14:15:16 +0200, Andrej Kruták wrote:
On Fri, Aug 12, 2016 at 2:03 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 13:10:37 +0200, Andrej Kruták wrote:
+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.
This code is based on Documentation/circular-buffers.txt, except for the semaphore magic.
The example there is basically a semi lock-free implementation. For your purpose it's an overkill. This is no severely hot path, thus a simpler version would make life easier.
Fair enough, I'll spinlock-ize it then.
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
You can - down_interruptible() is there for this exact reason.
There is another blocking path: you keep semaphore down after line6_hwdep_read() until line6_hwdep_push_message(). What happens if user-space interrupts during that, and line6_hwdep_push_message() is delayed or stall by some reason?
As said, semaphore is basically wait-queue + counter, so there's no real reason not to do this IMO.
But in the end - if you have some nice example of FIFO buffer in some simple driver, I have no problem using a more already-proven/standard way :-)
Just use the normal waitqueue and schedule or wait_event() or its variant.
Takashi
On Fri, Aug 12, 2016 at 2:30 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 14:15:16 +0200,
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
You can - down_interruptible() is there for this exact reason.
There is another blocking path: you keep semaphore down after line6_hwdep_read() until line6_hwdep_push_message(). What happens if user-space interrupts during that, and line6_hwdep_push_message() is delayed or stall by some reason?
Actually, I think I don't see what's the "another path" here, could you please elaborate one more bit? I just want to make sure to not reimplement the same race using waitqueue...
What's the point then? line6_hwdep_push_message() could get not scheduled for some while. So until it calls up(), line6_hwdep_read() will block on down_interruptible(), or until signal (in which case user gets -ERESTARTSYS). After up() is called, there are data in buffer... If line6_hwdep_read() returns after interrupt, no problem - the buffer will just continue to be filled and semaphore will be up()'d while there's free buffer space. Or until the device is closed...
If I do the same via waitqueue, I will have the same problems, no? Maybe if you could post the steps where you see the race...
At the same time, looking at __down_common(), it just does the standard waitqueue stuff (TASK_INTERRUPTIBLE + schedule()) (+counter in down())... So do you have some other race in mind? I'm running in circles, so surely you must :-)
Sorry if I sound like a moron... and thanks for you time!
On Fri, 12 Aug 2016 18:43:30 +0200, Andrej Kruták wrote:
On Fri, Aug 12, 2016 at 2:30 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 14:15:16 +0200,
Also, the blocking read/write control isn't usually done by a semaphore. Then you can handle the interrupt there.
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
You can - down_interruptible() is there for this exact reason.
There is another blocking path: you keep semaphore down after line6_hwdep_read() until line6_hwdep_push_message(). What happens if user-space interrupts during that, and line6_hwdep_push_message() is delayed or stall by some reason?
Actually, I think I don't see what's the "another path" here, could you please elaborate one more bit? I just want to make sure to not reimplement the same race using waitqueue...
What's the point then? line6_hwdep_push_message() could get not scheduled for some while. So until it calls up(), line6_hwdep_read() will block on down_interruptible(), or until signal (in which case user gets -ERESTARTSYS). After up() is called, there are data in buffer...
Well, what happens if user aborts before up() is called in line6_hwdep_push_message()? Now the driver calls close, and it frees the memory. What if, at the very same time, line6_hwdep_push_message() is triggered?
If line6_hwdep_read() returns after interrupt, no problem - the buffer will just continue to be filled and semaphore will be up()'d while there's free buffer space. Or until the device is closed...
Or, what if line6_hwdep_push_message() is triggered twice or more before line6_hwdep_read() is called? It will call up() twice or more. Then at this point, you call line6_hwdep_read() concurrently from two threads... How do they protect against each other?
If I do the same via waitqueue, I will have the same problems, no? Maybe if you could post the steps where you see the race...
In your code, it's not clear that you're protecting from what. A simple lock+wait loop shows it more easily, at least.
Takashi
At the same time, looking at __down_common(), it just does the standard waitqueue stuff (TASK_INTERRUPTIBLE + schedule()) (+counter in down())... So do you have some other race in mind? I'm running in circles, so surely you must :-)
Sorry if I sound like a moron... and thanks for you time!
-- Andrej
On Fri, Aug 12, 2016 at 10:01 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 18:43:30 +0200, Andrej Kruták wrote:
On Fri, Aug 12, 2016 at 2:30 PM, Takashi Iwai tiwai@suse.de wrote:
On Fri, 12 Aug 2016 14:15:16 +0200,
> Also, the blocking read/write control isn't usually done by a > semaphore. Then you can handle the interrupt there. > >
I actually wonder why, semaphores seemed perfect for this... Do you have some hints?
Assume you want to interrupt the user-space app while it's being blocked by the semaphore. With your code, you can't.
You can - down_interruptible() is there for this exact reason.
There is another blocking path: you keep semaphore down after line6_hwdep_read() until line6_hwdep_push_message(). What happens if user-space interrupts during that, and line6_hwdep_push_message() is delayed or stall by some reason?
Actually, I think I don't see what's the "another path" here, could you please elaborate one more bit? I just want to make sure to not reimplement the same race using waitqueue...
What's the point then? line6_hwdep_push_message() could get not scheduled for some while. So until it calls up(), line6_hwdep_read() will block on down_interruptible(), or until signal (in which case user gets -ERESTARTSYS). After up() is called, there are data in buffer...
Well, what happens if user aborts before up() is called in line6_hwdep_push_message()? Now the driver calls close, and it frees the memory. What if, at the very same time, line6_hwdep_push_message() is triggered?
Right, the 'active' flag is cleary not a good lock...
If line6_hwdep_read() returns after interrupt, no problem - the buffer will just continue to be filled and semaphore will be up()'d while there's free buffer space. Or until the device is closed...
Or, what if line6_hwdep_push_message() is triggered twice or more before line6_hwdep_read() is called? It will call up() twice or more. Then at this point, you call line6_hwdep_read() concurrently from two threads... How do they protect against each other?
There's read_lock, unfortunately after "fixing" the sleep-in-atomic in line6_hwdep_read(), it's broken now...
If I do the same via waitqueue, I will have the same problems, no? Maybe if you could post the steps where you see the race...
In your code, it's not clear that you're protecting from what. A simple lock+wait loop shows it more easily, at least.
Okay, I'll try to rethink/rework it, simplify the code, and get back.
Thanks!
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 16 +++++++++++----- sound/usb/line6/driver.h | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 0a0e324..344465f 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -541,8 +541,7 @@ static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file) line6->buffer_circular.head = 0; line6->buffer_circular.tail = 0; sema_init(&line6->buffer_circular.sem, 0); - - line6->buffer_circular.active = 1; + spin_lock_init(&line6->buffer_circular.read_lock);
line6->buffer_circular.data = kmalloc(LINE6_MESSAGE_MAXLEN * LINE6_MESSAGE_MAXCOUNT, GFP_KERNEL); @@ -567,7 +566,7 @@ static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file)
/* By this time, no readers are waiting, we can safely recreate the * semaphore at next open. */ - line6->buffer_circular.active = 0; + clear_bit(0, &line6->buffer_circular.active);
kfree(line6->buffer_circular.data); kfree(line6->buffer_circular.data_len); @@ -581,18 +580,23 @@ line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, { struct usb_line6 *line6 = hwdep->private_data; unsigned long tail; + long rv = 0;
if (down_interruptible(&line6->buffer_circular.sem)) { return -ERESTARTSYS; } /* There must an item now in the buffer... */
+ spin_lock(&line6->buffer_circular.read_lock); + 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; + + rv = -EINVAL; + goto end; }
if (copy_to_user(buf, @@ -608,7 +612,9 @@ line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, smp_store_release(&line6->buffer_circular.tail, (tail + 1) & (LINE6_MESSAGE_MAXCOUNT - 1));
- return 0; +end: + spin_unlock(&line6->buffer_circular.read_lock); + return rv; }
/* Write directly (no buffering) to device by user*/ diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index f4ab6cd..05e155c 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -159,12 +159,13 @@ struct usb_line6 {
/* Circular buffer for non-MIDI control messages */ struct { - int active; + unsigned long active; char *data; int *data_len; unsigned long head, tail; /* Actually is up'd # of items in the buffer - times */ struct semaphore sem; + spinlock_t read_lock; } buffer_circular;
/* If MIDI is supported, buffer_message contains the pre-processed data;
On Thu, 11 Aug 2016 21:02:22 +0200, Andrej Krutak wrote:
Signed-off-by: Andrej Krutak dev@andree.sk
This should be merged into the patch 9.
Takashi
This makes the resource freeing not dependent on the existence of private data from the podhd module, which may be gone during the call.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 344465f..142e0e3 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -479,13 +479,14 @@ static void line6_destruct(struct snd_card *card) struct usb_device *usbdev = line6->usbdev;
/* free buffer memory first: */ - if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->buffer_message) kfree(line6->buffer_message);
kfree(line6->buffer_listen);
/* then free URBs: */ usb_free_urb(line6->urb_listen); + line6->urb_listen = NULL;
/* decrement reference counters: */ usb_put_dev(usbdev);
On Thu, 11 Aug 2016 21:02:23 +0200, Andrej Krutak wrote:
This makes the resource freeing not dependent on the existence of private data from the podhd module, which may be gone during the call.
Signed-off-by: Andrej Krutak dev@andree.sk
Should be merged to patch 9 from the beginning.
Takashi
Done, because line6_stream_stop() locks and calls line6_unlink_audio_urbs(), which in turn invokes audio_out_callback(), which tries to lock 2nd time.
Fixes:
============================================= [ INFO: possible recursive locking detected ] 4.4.15+ #15 Not tainted --------------------------------------------- mplayer/3591 is trying to acquire lock: (&(&line6pcm->out.lock)->rlock){-.-...}, at: [<bfa27655>] audio_out_callback+0x70/0x110 [snd_usb_line6]
but task is already holding lock: (&(&line6pcm->out.lock)->rlock){-.-...}, at: [<bfa26aad>] line6_stream_stop+0x24/0x5c [snd_usb_line6]
other info that might help us debug this: Possible unsafe locking scenario:
CPU0 ---- lock(&(&line6pcm->out.lock)->rlock); lock(&(&line6pcm->out.lock)->rlock);
*** DEADLOCK ***
May be due to missing lock nesting notation
3 locks held by mplayer/3591: #0: (snd_pcm_link_rwlock){.-.-..}, at: [<bf8d49a7>] snd_pcm_stream_lock+0x1e/0x40 [snd_pcm] #1: (&(&substream->self_group.lock)->rlock){-.-...}, at: [<bf8d49af>] snd_pcm_stream_lock+0x26/0x40 [snd_pcm] #2: (&(&line6pcm->out.lock)->rlock){-.-...}, at: [<bfa26aad>] line6_stream_stop+0x24/0x5c [snd_usb_line6]
stack backtrace: CPU: 0 PID: 3591 Comm: mplayer Not tainted 4.4.15+ #15 Hardware name: Generic AM33XX (Flattened Device Tree) [<c0015d85>] (unwind_backtrace) from [<c001253d>] (show_stack+0x11/0x14) [<c001253d>] (show_stack) from [<c02f1bdf>] (dump_stack+0x8b/0xac) [<c02f1bdf>] (dump_stack) from [<c0076f43>] (__lock_acquire+0xc8b/0x1780) [<c0076f43>] (__lock_acquire) from [<c007810d>] (lock_acquire+0x99/0x1c0) [<c007810d>] (lock_acquire) from [<c06171e7>] (_raw_spin_lock_irqsave+0x3f/0x4c) [<c06171e7>] (_raw_spin_lock_irqsave) from [<bfa27655>] (audio_out_callback+0x70/0x110 [snd_usb_line6]) [<bfa27655>] (audio_out_callback [snd_usb_line6]) from [<c04294db>] (__usb_hcd_giveback_urb+0x53/0xd0) [<c04294db>] (__usb_hcd_giveback_urb) from [<c046388d>] (musb_giveback+0x3d/0x98) [<c046388d>] (musb_giveback) from [<c04647f5>] (musb_urb_dequeue+0x6d/0x114) [<c04647f5>] (musb_urb_dequeue) from [<c042ac11>] (usb_hcd_unlink_urb+0x39/0x98) [<c042ac11>] (usb_hcd_unlink_urb) from [<bfa26a87>] (line6_unlink_audio_urbs+0x6a/0x6c [snd_usb_line6]) [<bfa26a87>] (line6_unlink_audio_urbs [snd_usb_line6]) from [<bfa26acb>] (line6_stream_stop+0x42/0x5c [snd_usb_line6]) [<bfa26acb>] (line6_stream_stop [snd_usb_line6]) from [<bfa26fe7>] (snd_line6_trigger+0xb6/0xf4 [snd_usb_line6]) [<bfa26fe7>] (snd_line6_trigger [snd_usb_line6]) from [<bf8d47b7>] (snd_pcm_do_stop+0x36/0x38 [snd_pcm]) [<bf8d47b7>] (snd_pcm_do_stop [snd_pcm]) from [<bf8d462f>] (snd_pcm_action_single+0x22/0x40 [snd_pcm]) [<bf8d462f>] (snd_pcm_action_single [snd_pcm]) from [<bf8d46f9>] (snd_pcm_action+0xac/0xb0 [snd_pcm]) [<bf8d46f9>] (snd_pcm_action [snd_pcm]) from [<bf8d4b61>] (snd_pcm_drop+0x38/0x64 [snd_pcm]) [<bf8d4b61>] (snd_pcm_drop [snd_pcm]) from [<bf8d6233>] (snd_pcm_common_ioctl1+0x7fe/0xbe8 [snd_pcm]) [<bf8d6233>] (snd_pcm_common_ioctl1 [snd_pcm]) from [<bf8d6779>] (snd_pcm_playback_ioctl1+0x15c/0x51c [snd_pcm]) [<bf8d6779>] (snd_pcm_playback_ioctl1 [snd_pcm]) from [<bf8d6b59>] (snd_pcm_playback_ioctl+0x20/0x28 [snd_pcm]) [<bf8d6b59>] (snd_pcm_playback_ioctl [snd_pcm]) from [<c016714b>] (do_vfs_ioctl+0x3af/0x5c8)
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/pcm.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 0af6d4e..13b3b27 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -216,7 +216,9 @@ static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction, spin_lock_irqsave(&pstr->lock, flags); clear_bit(type, &pstr->running); if (!pstr->running) { + spin_unlock_irqrestore(&pstr->lock, flags); line6_unlink_audio_urbs(line6pcm, pstr); + spin_lock_irqsave(&pstr->lock, flags); if (direction == SNDRV_PCM_STREAM_CAPTURE) { line6pcm->prev_fbuf = NULL; line6pcm->prev_fsize = 0;
On Thu, 11 Aug 2016 21:02:24 +0200, Andrej Krutak wrote:
Done, because line6_stream_stop() locks and calls line6_unlink_audio_urbs(), which in turn invokes audio_out_callback(), which tries to lock 2nd time.
Ditto, write the patch right from the beginning :)
Takashi
On Fri, Aug 12, 2016 at 10:45 AM, Takashi Iwai tiwai@suse.de wrote:
On Thu, 11 Aug 2016 21:02:24 +0200, Andrej Krutak wrote:
Done, because line6_stream_stop() locks and calls line6_unlink_audio_urbs(), which in turn invokes audio_out_callback(), which tries to lock 2nd time.
Ditto, write the patch right from the beginning :)
This actually fixes a lock-bug in the code, so I think I'll send it separately (like patch 15). Will think about it...
On Fri, 12 Aug 2016 13:14:35 +0200, Andrej Kruták wrote:
On Fri, Aug 12, 2016 at 10:45 AM, Takashi Iwai tiwai@suse.de wrote:
On Thu, 11 Aug 2016 21:02:24 +0200, Andrej Krutak wrote:
Done, because line6_stream_stop() locks and calls line6_unlink_audio_urbs(), which in turn invokes audio_out_callback(), which tries to lock 2nd time.
Ditto, write the patch right from the beginning :)
This actually fixes a lock-bug in the code, so I think I'll send it separately (like patch 15). Will think about it...
Ah, right, this is the bug in the upstream code. Then you should submit it individually, instead of burying in a patchset.
Takashi
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 80ed138..be764f8 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -37,6 +37,7 @@ enum { LINE6_PODHD500_0, LINE6_PODHD500_1, LINE6_PODX3, + LINE6_PODX3LIVE };
struct usb_line6_podhd { @@ -373,6 +374,7 @@ static const struct usb_device_id podhd_id_table[] = { { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, + { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, {} };
@@ -434,6 +436,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3LIVE] = { + .id = "PODX3LIVE", + .name = "POD X3 LIVE", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/*
Without it, the code hits the 'might_sleep while atomic' debug warning.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 142e0e3..286b6e1 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -600,6 +600,10 @@ line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, goto end; }
+ /* Release lock while copying the stuff. Since there is only one reader, + * the data is going nowhere, so this should be safe. */ + spin_unlock(&line6->buffer_circular.read_lock); + if (copy_to_user(buf, &line6->buffer_circular.data[tail * LINE6_MESSAGE_MAXLEN], line6->buffer_circular.data_len[tail]) @@ -610,6 +614,7 @@ line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, rv = line6->buffer_circular.data_len[tail]; }
+ spin_lock(&line6->buffer_circular.read_lock); smp_store_release(&line6->buffer_circular.tail, (tail + 1) & (LINE6_MESSAGE_MAXCOUNT - 1));
On Thu, 11 Aug 2016 21:02:26 +0200, Andrej Krutak wrote:
Without it, the code hits the 'might_sleep while atomic' debug warning.
Signed-off-by: Andrej Krutak dev@andree.sk
This also should have been folded into patch 9.
Takashi
If there's an error, pcm is released in line6_pcm_acquire already.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/pcm.c | 1 - 1 file changed, 1 deletion(-)
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 13b3b27..455244c 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -55,7 +55,6 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol, err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0; - line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE); return err; } } else {
On Thu, 11 Aug 2016 21:02:27 +0200, Andrej Krutak wrote:
If there's an error, pcm is released in line6_pcm_acquire already.
Signed-off-by: Andrej Krutak dev@andree.sk
This looks like a fix independent from your patchset. In that case, submit it individually so that I can merge it beforehand.
Takashi
sound/usb/line6/pcm.c | 1 - 1 file changed, 1 deletion(-)
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 13b3b27..455244c 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -55,7 +55,6 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol, err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0;
} } else {line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE); return err;
-- 1.9.1
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Thu, 11 Aug 2016 21:02:12 +0200, Andrej Krutak wrote:
Hello all,
attached is the first version of the driver, based on POD HD. They share a lot of similarities, but at the same time there were some generalizations needed. I think POD HD could follow up on these patches to add hwdep support, perhaps helix too... I tried to not introduce regressions to the old HW.
Anyhow, this is probably not the final version of the patches, as it is my first submission to upstream - I'm sure there will be some issues. Especially the patch "Use device_create_file instead of snd_card_add_dev_attr" I have to revisit, it may not be necessarry in the end.
Other than that, the patches mostly pass `make checkpatches`. I've been testing the driver for a while now, including lock debugging options etc., and there don't seem to be functional problems.
There's one missing thing - the driver uses bulk USB interface of the device, but so far I wasn't able to make the usb_driver_claim_interface() work... I hope this can be added later, if someone (or I) finds time.
Thanks for your inputs, greetings,
Thanks for the patches. Many of them look good, but we'd need revisiting some of them. But at the next time, please put maintainers to Cc, not only to sending to ML.
I'll comment on each patch.
Takashi
Hi!
On Fri, Aug 12, 2016 at 10:14 AM, Takashi Iwai tiwai@suse.de wrote:
On Thu, 11 Aug 2016 21:02:12 +0200, Andrej Krutak wrote:
Hello all,
attached is the first version of the driver, based on POD HD. They share
a lot
of similarities, but at the same time there were some generalizations
needed. I
think POD HD could follow up on these patches to add hwdep support,
perhaps
helix too... I tried to not introduce regressions to the old HW.
Anyhow, this is probably not the final version of the patches, as it is
my
first submission to upstream - I'm sure there will be some issues. Especially the patch "Use device_create_file instead of snd_card_add_dev_attr" I have to
revisit,
it may not be necessarry in the end.
Other than that, the patches mostly pass `make checkpatches`. I've been testing the driver for a while now, including lock debugging options
etc.,
and there don't seem to be functional problems.
There's one missing thing - the driver uses bulk USB interface of the device, but so far I wasn't able to make the usb_driver_claim_interface() work... I hope this can be added later, if someone (or I) finds time.
Thanks for your inputs, greetings,
Thanks for the patches. Many of them look good, but we'd need revisiting some of them. But at the next time, please put maintainers to Cc, not only to sending to ML.
I'll comment on each patch.
Thanks for the quick feedback! I'll rework the patches where obvious, and respond to the comments where I "object"...
Hi,
I reworked the patches acc. to Takashi's feedback, I think I was able to fix all the "objections". Two of the previous patches were sent in a separate batch.
About first patch below - I just added some more commit notes there. I tried to split it, but it's kind of interconnected and in the end, they are mostly sed-like operations.. If it's a no-go, I can do it, but I'd prefer not to :-)
Some commits were merged to "Add hwdep IF" commit, as suggested. Also, I dropped manual implementation of the FIFO buffer in favor of KFIFO. In addition, mutex is used for read() - as required by the kfifo lib. The code now looks a lot less scary, I'd say...
Andrej Krutak (9): ALSA: line6: Make driver configuration more generic. ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture ALSA: line6: Distinguish device init (ctrl EP) and MIDI data transfer (int EP) ALSA: line6: Add support for POD X3 ALSA: line6: Add support for POD X3 Live (only USB ID differs from POD X3) ALSA: line6: Allow bulk endpoints instead of interrupt endpoints ALSA: line6: Allow processing of raw incoming messages ALSA: line6: Cleanup initialization ALSA: line6: Add hwdep interface to access the POD control messages
include/uapi/sound/asound.h | 3 +- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/capture.c | 50 +++++--- sound/usb/line6/driver.c | 269 +++++++++++++++++++++++++++++++++------ sound/usb/line6/driver.h | 58 +++++++-- sound/usb/line6/midi.c | 2 +- sound/usb/line6/pcm.c | 84 +++++++++---- sound/usb/line6/pcm.h | 19 +-- sound/usb/line6/playback.c | 37 ++++-- sound/usb/line6/pod.c | 12 +- sound/usb/line6/podhd.c | 300 ++++++++++++++++++++++++++++++++++++++++---- sound/usb/line6/toneport.c | 6 +- sound/usb/line6/variax.c | 6 +- 13 files changed, 704 insertions(+), 146 deletions(-)
The main reasons are different settings for USB low/high speed and possible different channel counts for in/out; required by POD X3.
This consists of two related parts:
Support for high-speed USB: * USB_INTERVALS_PER_SECOND -> LOW/HIGH settings (high needs 8000, instead of 1000) * LINE6_ISO_BUFFERS -> iso_buffers (count of iso buffers depends on USB speed, 2 is not enough for high speed)
Support for assymetrical in/out configurations: * bytes_per_frame -> bytes_per_channel * max_packet_size -> max_packet_size_in/out
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 45 ++++++++++++++++++++++++++++++-------------- sound/usb/line6/driver.c | 15 ++++++++++----- sound/usb/line6/driver.h | 19 +++++++++++++++---- sound/usb/line6/pcm.c | 47 ++++++++++++++++++++++++++++------------------ sound/usb/line6/pcm.h | 15 +++++++-------- sound/usb/line6/playback.c | 37 ++++++++++++++++++++++++------------ sound/usb/line6/pod.c | 3 +-- sound/usb/line6/podhd.c | 4 +--- sound/usb/line6/toneport.c | 2 +- 9 files changed, 120 insertions(+), 67 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index f518fbb..91d1562 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -29,10 +29,10 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) int ret; struct urb *urb_in;
- index = - find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->in.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -44,13 +44,13 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; fin->offset = urb_size; - fin->length = line6pcm->max_packet_size; - urb_size += line6pcm->max_packet_size; + fin->length = line6pcm->max_packet_size_in; + urb_size += line6pcm->max_packet_size_in; }
urb_in->transfer_buffer = line6pcm->in.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; urb_in->transfer_buffer_length = urb_size; urb_in->context = line6pcm;
@@ -73,7 +73,7 @@ int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_in_urb(line6pcm); if (ret < 0) break; @@ -90,7 +90,9 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); struct snd_pcm_runtime *runtime = substream->runtime; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max; int frames = fsize / bytes_per_frame;
if (runtime == NULL) @@ -154,7 +156,7 @@ static void audio_in_callback(struct urb *urb) line6pcm->in.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) if (urb == line6pcm->in.urbs[index]) break;
@@ -173,17 +175,27 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
- if (fsize > line6pcm->max_packet_size) { + if (fsize > line6pcm->max_packet_size_in) { dev_err(line6pcm->line6->ifcdev, "driver and/or device bug: packet too large (%d > %d)\n", - fsize, line6pcm->max_packet_size); + fsize, line6pcm->max_packet_size_in); }
length += fsize;
- /* the following assumes LINE6_ISO_PACKETS == 1: */ + BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1, + "The following code assumes LINE6_ISO_PACKETS == 1"); + /* TODO: + * Also, if iso_buffers != 2, the prev frame is almost random at + * playback side. + * This needs to be redesigned. It should be "stable", but we may + * experience sync problems on such high-speed configs. + */ + line6pcm->prev_fbuf = fbuf; - line6pcm->prev_fsize = fsize; + line6pcm->prev_fsize = fsize / + (line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max);
if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) && @@ -247,8 +259,13 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->in.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio in: */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8..efeb16a8 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -462,13 +462,17 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep; - unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r); - unsigned epnum = usb_pipeendpoint(pipe); - - ep = usbdev->ep_in[epnum]; + struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; if (ep) { line6->interval = ep->desc.bInterval; + if (usbdev->speed == USB_SPEED_LOW) { + line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_LOW_ISO_BUFFERS; + } else { + line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_HIGH_ISO_BUFFERS; + } + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); } else { dev_err(line6->ifcdev, @@ -558,6 +562,7 @@ int line6_probe(struct usb_interface *interface, /* query interface number */ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+ /* TODO reserves the bus bandwidth even without actual transfer */ ret = usb_set_interface(usbdev, interface_number, properties->altsetting); if (ret < 0) { diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7da643e..2d32139 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,14 +18,20 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND 1000 +#define USB_LOW_ISO_BUFFERS 2 + +/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND 8000 +#define USB_HIGH_ISO_BUFFERS 16
/* Fallback USB interval and max packet size values */ #define LINE6_FALLBACK_INTERVAL 10 #define LINE6_FALLBACK_MAXPACKETSIZE 16
#define LINE6_TIMEOUT 1 -#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MESSAGE_MAXLEN 256
/* @@ -109,10 +115,15 @@ struct usb_line6 { /* Properties */ const struct line6_properties *properties;
- /* Interval (ms) */ + /* Interval for data USB packets */ int interval; + /* ...for isochronous transfers framing */ + int intervals_per_second; + + /* Number of isochronous URBs used for frame transfers */ + int iso_buffers;
- /* Maximum size of USB packet */ + /* Maximum size of data USB packet */ int max_packet_size;
/* Device representing the USB interface */ diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 41aa335..e1913d3 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -104,7 +104,7 @@ static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) { if (!test_and_set_bit(i, &pcms->unlink_urbs)) usb_unlink_urb(pcms->urbs[i]); @@ -124,7 +124,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
do { alive = 0; - for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) alive++; } @@ -146,15 +146,20 @@ get_stream(struct snd_line6_pcm *line6pcm, int direction) }
/* allocate a buffer if not opened yet; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, - struct line6_pcm_stream *pstr, int type) + struct line6_pcm_stream *pstr, int direction, int type) { + const int pkt_size = + (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + line6pcm->max_packet_size_out : + line6pcm->max_packet_size_in; + /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { - pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * + LINE6_ISO_PACKETS * pkt_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -162,12 +167,11 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, }
/* free a buffer if all streams are closed; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, struct line6_pcm_stream *pstr, int type) { - clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); @@ -194,6 +198,7 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, else ret = line6_submit_audio_in_all_urbs(line6pcm); } + if (ret < 0) clear_bit(type, &pstr->running); spin_unlock_irqrestore(&pstr->lock, flags); @@ -434,24 +439,30 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + /* Most likely impossible in current code... */ + if (pcms->urbs == NULL) + return; + + for (i = 0; i < iso_buffers; i++) { if (pcms->urbs[i]) { usb_kill_urb(pcms->urbs[i]); usb_free_urb(pcms->urbs[i]); } } + kfree(pcms->urbs); + pcms->urbs = NULL; }
static void line6_cleanup_pcm(struct snd_pcm *pcm) { struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
- cleanup_urbs(&line6pcm->out); - cleanup_urbs(&line6pcm->in); + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); kfree(line6pcm); }
@@ -523,12 +534,12 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6;
- /* Read and write buffers are sized identically, so choose minimum */ - line6pcm->max_packet_size = min( - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0), - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1)); + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1);
spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 508410a..58d36f9 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -20,9 +20,6 @@
#include "driver.h"
-/* number of URBs */ -#define LINE6_ISO_BUFFERS 2 - /* number of USB frames per URB The Line 6 Windows driver always transmits two frames per packet, but @@ -31,7 +28,8 @@ */ #define LINE6_ISO_PACKETS 1
-/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +/* in a "full speed" device (such as the PODxt Pro) this means 1ms, + for "high speed" it's 1/8ms */ #define LINE6_ISO_INTERVAL 1
#define LINE6_IMPULSE_DEFAULT_PERIOD 100 @@ -85,12 +83,12 @@ enum { struct line6_pcm_properties { struct snd_pcm_hardware playback_hw, capture_hw; struct snd_pcm_hw_constraint_ratdens rates; - int bytes_per_frame; + int bytes_per_channel; };
struct line6_pcm_stream { /* allocated URBs */ - struct urb *urbs[LINE6_ISO_BUFFERS]; + struct urb **urbs;
/* Temporary buffer; * Since the packet size is not known in advance, this buffer is @@ -157,11 +155,12 @@ struct snd_line6_pcm { /* Previously captured frame (for software monitoring) */ unsigned char *prev_fbuf;
- /* Size of previously captured frame (for software monitoring) */ + /* Size of previously captured frame (for software monitoring/sync) */ int prev_fsize;
/* Maximum size of USB packet */ - int max_packet_size; + int max_packet_size_in; + int max_packet_size_out;
/* PCM playback volume (left and right) */ int volume_playback[2]; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 97ed593..4833c51 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -146,18 +146,20 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) int index; int i, urb_size, urb_frames; int ret; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; const int frame_increment = line6pcm->properties->rates.rats[0].num_min; const int frame_factor = line6pcm->properties->rates.rats[0].den * - (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out;
- index = - find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit( + &line6pcm->out.active_urbs, line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -165,6 +167,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out = line6pcm->out.urbs[index]; urb_size = 0;
+ /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */ for (i = 0; i < LINE6_ISO_PACKETS; ++i) { /* compute frame size for given sampling rate */ int fsize = 0; @@ -178,9 +181,11 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->out.count += frame_increment; n = line6pcm->out.count / frame_factor; line6pcm->out.count -= n * frame_factor; - fsize = n * bytes_per_frame; + fsize = n; }
+ fsize *= bytes_per_frame; + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; @@ -195,7 +200,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_frames = urb_size / bytes_per_frame; urb_out->transfer_buffer = line6pcm->out.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; urb_out->transfer_buffer_length = urb_size; urb_out->context = line6pcm;
@@ -286,7 +291,7 @@ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) break; @@ -305,6 +310,9 @@ static void audio_out_callback(struct urb *urb) struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max;
#if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -313,11 +321,11 @@ static void audio_out_callback(struct urb *urb) line6pcm->out.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; index++) + for (index = 0; index < line6pcm->line6->iso_buffers; index++) if (urb == line6pcm->out.urbs[index]) break;
- if (index >= LINE6_ISO_BUFFERS) + if (index >= line6pcm->line6->iso_buffers) return; /* URB has been unlinked asynchronously */
for (i = 0; i < LINE6_ISO_PACKETS; i++) @@ -329,7 +337,7 @@ static void audio_out_callback(struct urb *urb) struct snd_pcm_runtime *runtime = substream->runtime;
line6pcm->out.pos_done += - length / line6pcm->properties->bytes_per_frame; + length / bytes_per_frame;
if (line6pcm->out.pos_done >= runtime->buffer_size) line6pcm->out.pos_done -= runtime->buffer_size; @@ -401,8 +409,13 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->out.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio out: */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 45dd348..36e7274 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -83,7 +83,6 @@ struct usb_line6_pod { };
#define POD_SYSEX_CODE 3 -#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
/* *INDENT-OFF* */
@@ -167,7 +166,7 @@ static struct line6_pcm_properties pod_pcm_properties = { .rates = { .nrats = 1, .rats = &pod_ratden}, - .bytes_per_frame = POD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
static const char pod_version_header[] = { diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 63dcaef..4fc4789 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -25,8 +25,6 @@ enum { LINE6_PODHD500_1, };
-#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ - static struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, @@ -73,7 +71,7 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_frame = PODHD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* 24bit audio (stereo) */ };
/* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 6d4c50c..da76e03 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -114,7 +114,7 @@ static struct line6_pcm_properties toneport_pcm_properties = { .rates = { .nrats = 1, .rats = &toneport_ratden}, - .bytes_per_frame = 4 + .bytes_per_channel = 2 };
static const struct {
On Fri, 19 Aug 2016 00:20:31 +0200, Andrej Krutak wrote:
The main reasons are different settings for USB low/high speed and possible different channel counts for in/out; required by POD X3.
This consists of two related parts:
Support for high-speed USB:
- USB_INTERVALS_PER_SECOND -> LOW/HIGH settings (high needs 8000, instead of 1000)
- LINE6_ISO_BUFFERS -> iso_buffers (count of iso buffers depends on USB speed, 2 is not enough for high speed)
Support for assymetrical in/out configurations:
- bytes_per_frame -> bytes_per_channel
- max_packet_size -> max_packet_size_in/out
It'd be better to split into a few more patches. The changes about making iso_buffers dynamic and others are basically independent.
So, as the first patch, just convert to the dynamic urbs allocation and adding line6->iso_buffers field, as a preliminary change. Then introduce the two speed modes and align iso_buffers in the next patch. And at last, convert bytes_per_frame and max_packet_size. They can be either one or two individual patches, too.
Also, just a nitpick:
- index = find_first_zero_bit(
&line6pcm->in.active_urbs, line6pcm->line6->iso_buffers);
This style (leaving the open parenthesis) isn't common in the kernel code.
thanks,
Takashi
E.g. POD X3 seems to require playback data to be sent to it to generate capture data. Otherwise the device stalls and doesn't send any more capture data until it's reset.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 5 +++++ sound/usb/line6/driver.h | 4 +++- sound/usb/line6/pcm.c | 37 ++++++++++++++++++++++++++++--------- sound/usb/line6/pcm.h | 4 +++- sound/usb/line6/toneport.c | 4 ++-- 5 files changed, 41 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 91d1562..37c9e94 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -232,6 +232,8 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err;
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); + runtime->hw = line6pcm->properties->capture_hw; return 0; } @@ -239,6 +241,9 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) /* close capture callback */ static int snd_line6_capture_close(struct snd_pcm_substream *substream) { + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER); return 0; }
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 2d32139..69658dc 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -100,8 +100,10 @@ enum { LINE6_CAP_CONTROL = 1 << 0, /* device supports PCM input/output via USB */ LINE6_CAP_PCM = 1 << 1, - /* device support hardware monitoring */ + /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device requires output data when input is read */ + LINE6_CAP_IN_NEEDS_OUT = 1 << 4, };
/* diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index e1913d3..18424a2 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -52,7 +52,7 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
line6pcm->impulse_volume = value; if (value > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0; return err; @@ -242,6 +242,15 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: + if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT) + ) { + err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + if (err < 0) + return err; + } err = line6_stream_start(line6pcm, s->stream, LINE6_STREAM_PCM); if (err < 0) @@ -250,6 +259,13 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: + if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT) + ) { + line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + } line6_stream_stop(line6pcm, s->stream, LINE6_STREAM_PCM); break; @@ -283,27 +299,30 @@ snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) return pstr->pos_done; }
-/* Acquire and start duplex streams: +/* Acquire and optionally start duplex streams: * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR */ -int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) { struct line6_pcm_stream *pstr; int ret = 0, dir;
+ /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ mutex_lock(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); - ret = line6_buffer_acquire(line6pcm, pstr, type); + ret = line6_buffer_acquire(line6pcm, pstr, dir, type); if (ret < 0) goto error; if (!pstr->running) line6_wait_clear_audio_urbs(line6pcm, pstr); } - for (dir = 0; dir < 2; dir++) { - ret = line6_stream_start(line6pcm, dir, type); - if (ret < 0) - goto error; + if (start) { + for (dir = 0; dir < 2; dir++) { + ret = line6_stream_start(line6pcm, dir, type); + if (ret < 0) + goto error; + } } error: mutex_unlock(&line6pcm->state_mutex); @@ -339,7 +358,7 @@ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
mutex_lock(&line6pcm->state_mutex); - ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM); + ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, LINE6_STREAM_PCM); if (ret < 0) goto error;
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 58d36f9..5f796ef8 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -72,6 +72,7 @@ enum { LINE6_STREAM_PCM, LINE6_STREAM_MONITOR, LINE6_STREAM_IMPULSE, + LINE6_STREAM_CAPTURE_HELPER, };
/* misc bit flags for PCM operation */ @@ -190,7 +191,8 @@ extern int snd_line6_hw_params(struct snd_pcm_substream *substream, extern int snd_line6_hw_free(struct snd_pcm_substream *substream); extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream); extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); -extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, + bool start); extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
#endif diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index da76e03..8e22f43 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -177,7 +177,7 @@ static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, line6pcm->volume_monitor = ucontrol->value.integer.value[0];
if (line6pcm->volume_monitor > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true); if (err < 0) { line6pcm->volume_monitor = 0; line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); @@ -246,7 +246,7 @@ static void toneport_start_pcm(unsigned long arg) struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg; struct usb_line6 *line6 = &toneport->line6;
- line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR); + line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); }
/* control definition */
On Fri, 19 Aug 2016 00:20:32 +0200, Andrej Krutak wrote:
E.g. POD X3 seems to require playback data to be sent to it to generate capture data. Otherwise the device stalls and doesn't send any more capture data until it's reset.
Signed-off-by: Andrej Krutak dev@andree.sk
Only minor issues below:
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 2d32139..69658dc 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -100,8 +100,10 @@ enum { LINE6_CAP_CONTROL = 1 << 0, /* device supports PCM input/output via USB */ LINE6_CAP_PCM = 1 << 1,
- /* device support hardware monitoring */
- /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2,
- /* device requires output data when input is read */
- LINE6_CAP_IN_NEEDS_OUT = 1 << 4,
Any reason to skip 3?
@@ -242,6 +242,15 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME:
if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) &&
The parentheses are superfluous around (a == b).
(line6pcm->line6->properties->capabilities &
LINE6_CAP_IN_NEEDS_OUT)
) {
Move the close parenthesis and the brace to the previous line.
@@ -250,6 +259,13 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND:
if ((s->stream == SNDRV_PCM_STREAM_CAPTURE) &&
(line6pcm->line6->properties->capabilities &
LINE6_CAP_IN_NEEDS_OUT)
) {
line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK,
LINE6_STREAM_CAPTURE_HELPER);
}
Ditto.
thanks,
Takashi
POD X3 can initialize similarly to older PODs, but it doesn't have the MIDI interface. Instead, configuration is done via proprietary bulk EP messages.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 10 +++++----- sound/usb/line6/driver.h | 6 ++++-- sound/usb/line6/pod.c | 9 ++++++++- sound/usb/line6/variax.c | 6 ++++-- 4 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index efeb16a8..5fd6cad 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -482,7 +482,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control(struct usb_line6 *line6) +static int line6_init_cap_control_midi(struct usb_line6 *line6) { int ret;
@@ -572,8 +572,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL) { - ret = line6_init_cap_control(line6); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + ret = line6_init_cap_control_midi(line6); if (ret < 0) goto error; } @@ -643,7 +643,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -662,7 +662,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 69658dc..7aeb6ad 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -102,6 +102,8 @@ enum { LINE6_CAP_PCM = 1 << 1, /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device uses raw MIDI via USB (data endpoints) */ + LINE6_CAP_CONTROL_MIDI = 1 << 3, /* device requires output data when input is read */ LINE6_CAP_IN_NEEDS_OUT = 1 << 4, }; @@ -142,10 +144,10 @@ struct usb_line6 { /* Line 6 MIDI device data structure */ struct snd_line6_midi *line6midi;
- /* URB for listening to PODxt Pro control endpoint */ + /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to PODxt Pro control endpoint */ + /* Buffer for listening to POD data endpoint */ unsigned char *buffer_listen;
/* Buffer for message to be processed */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 36e7274..17aa616 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -475,6 +475,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxt", .name = "BassPODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -487,6 +488,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtLive", .name = "BassPODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -499,6 +501,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtPro", .name = "BassPODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -510,7 +513,8 @@ static const struct line6_properties pod_properties_table[] = { [LINE6_POCKETPOD] = { .id = "PocketPOD", .name = "Pocket POD", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 0, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x02, @@ -520,6 +524,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxt", .name = "PODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -532,6 +537,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtLive", .name = "PODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -544,6 +550,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtPro", .name = "PODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c index ddc23dd..0c4512d 100644 --- a/sound/usb/line6/variax.c +++ b/sound/usb/line6/variax.c @@ -259,7 +259,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_PODXTLIVE_VARIAX] = { .id = "PODxtLive", .name = "PODxt Live", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x86, .ep_ctrl_w = 0x05, @@ -269,7 +270,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_VARIAX] = { .id = "Variax", .name = "Variax Workbench", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x01,
On Fri, 19 Aug 2016 00:20:33 +0200, Andrej Krutak wrote:
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 69658dc..7aeb6ad 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -102,6 +102,8 @@ enum { LINE6_CAP_PCM = 1 << 1, /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2,
- /* device uses raw MIDI via USB (data endpoints) */
- LINE6_CAP_CONTROL_MIDI = 1 << 3, /* device requires output data when input is read */ LINE6_CAP_IN_NEEDS_OUT = 1 << 4,
OK, now I see the reason of the disorder in the previous patch. But then align either the patch order or the bit order.
Takashi
This includes audio in/out and basic initialization via control EP (emulates what original driver does). The initialization is done similarly to original POD, firmware and serial IDs are read and exported via sysfs.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/podhd.c | 276 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 265 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig index f4585d37..8ffcf48 100644 --- a/sound/usb/line6/Kconfig +++ b/sound/usb/line6/Kconfig @@ -21,10 +21,10 @@ config SND_USB_POD re-amping)
config SND_USB_PODHD - tristate "Line 6 POD HD300/400/500 USB support" + tristate "Line 6 POD X3/HD300/400/500 USB support" select SND_USB_LINE6 help - This is a driver for POD HD300, 400 and 500 devices. + This is a driver for POD X3, HD300, 400 and 500 devices.
config SND_USB_TONEPORT tristate "TonePort GX, UX1 and UX2 USB support" diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 4fc4789..8cf0a98 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -2,6 +2,7 @@ * Line 6 Pod HD * * Copyright (C) 2011 Stefan Hajnoczi stefanha@gmail.com + * Copyright (C) 2015 Andrej Krutak dev@andree.sk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,11 +19,44 @@ #include "driver.h" #include "pcm.h"
+#define PODHD_STARTUP_DELAY 500 + +/* + * Stages of POD startup procedure + */ +enum { + PODHD_STARTUP_INIT = 1, + PODHD_STARTUP_SCHEDULE_WORKQUEUE, + PODHD_STARTUP_SETUP, + PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 +}; + enum { LINE6_PODHD300, LINE6_PODHD400, LINE6_PODHD500_0, LINE6_PODHD500_1, + LINE6_PODX3, +}; + +struct usb_line6_podhd { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version */ + int firmware_version; };
static struct snd_ratden podhd_ratden = { @@ -71,9 +105,196 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_channel = 3 /* 24bit audio (stereo) */ + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
+static struct line6_pcm_properties podx3_pcm_properties = { + .playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + /* 1+2: Main signal (out), 3+4: Tone 1, + * 5+6: Tone 2, 7+8: raw */ + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static void podhd_startup_start_workqueue(unsigned long data); +static void podhd_startup_workqueue(struct work_struct *work); +static int podhd_startup_finalize(struct usb_line6_podhd *pod); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%u\n", pod->serial_number); +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%06x\n", pod->firmware_version); +} + +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *podhd_dev_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group podhd_dev_attr_group = { + .name = "podhd", + .attrs = podhd_dev_attrs, +}; + +/* + * POD X3 startup procedure. + * + * May be compatible with other POD HD's, since it's also similar to the + * previous POD setup. In any case, it doesn't seem to be required for the + * audio nor bulk interfaces to work. + */ + +static void podhd_startup(struct usb_line6_podhd *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, + podhd_startup_start_workqueue, (unsigned long)pod); +} + +static void podhd_startup_start_workqueue(unsigned long data) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)data; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, + PODHD_STARTUP_SCHEDULE_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static int podhd_dev_start(struct usb_line6_podhd *pod) +{ + int ret; + u8 init_bytes[8]; + int i; + struct usb_device *usbdev = pod->line6.usbdev; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x11, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* NOTE: looks like some kind of ping message */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x11, 0x0, + &init_bytes, 3, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, + "receive length failed (error %d)\n", ret); + return ret; + } + + pod->firmware_version = + (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); + + for (i = 0; i <= 16; i++) { + ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); + if (ret < 0) + return ret; + } + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, + 1, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void podhd_startup_workqueue(struct work_struct *work) +{ + struct usb_line6_podhd *pod = + container_of(work, struct usb_line6_podhd, startup_work); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + + podhd_dev_start(pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + + podhd_startup_finalize(pod); +} + +static int podhd_startup_finalize(struct usb_line6_podhd *pod) +{ + struct usb_line6 *line6 = &pod->line6; + + /* ALSA audio interface: */ + return snd_card_register(line6->card); +} + +static void podhd_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + } +} + /* Try to init POD HD device. */ @@ -81,6 +302,16 @@ static int podhd_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; + struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + + line6->disconnect = podhd_disconnect; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); + if (err < 0) + return err; + }
/* initialize MIDI subsystem: */ err = line6_init_midi(line6); @@ -88,12 +319,22 @@ static int podhd_init(struct usb_line6 *line6, return err;
/* initialize PCM subsystem: */ - err = line6_init_pcm(line6, &podhd_pcm_properties); + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); if (err < 0) return err;
- /* register USB audio system: */ - return snd_card_register(line6->card); + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { + /* register USB audio system directly */ + return podhd_startup_finalize(pod); + } + + /* init device and delay registering */ + init_timer(&pod->startup_timer); + INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + podhd_startup(pod); + return 0; }
#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) @@ -101,10 +342,12 @@ static int podhd_init(struct usb_line6 *line6,
/* table of devices that work with this driver */ static const struct usb_device_id podhd_id_table[] = { + /* TODO: no need to alloc data interfaces when only audio is used */ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, {} };
@@ -114,8 +357,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD300] = { .id = "PODHD300", .name = "POD HD300", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -126,8 +368,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD400] = { .id = "PODHD400", .name = "POD HD400", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -138,8 +379,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_0] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -150,8 +390,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_1] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -159,6 +398,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3] = { + .id = "PODX3", + .name = "POD X3", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/* @@ -169,7 +419,7 @@ static int podhd_probe(struct usb_interface *interface, { return line6_probe(interface, id, "Line6-PODHD", &podhd_properties_table[id->driver_info], - podhd_init, sizeof(struct usb_line6)); + podhd_init, sizeof(struct usb_line6_podhd)); }
static struct usb_driver podhd_driver = {
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8cf0a98..8246ea5 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -37,6 +37,7 @@ enum { LINE6_PODHD500_0, LINE6_PODHD500_1, LINE6_PODX3, + LINE6_PODX3LIVE };
struct usb_line6_podhd { @@ -348,6 +349,7 @@ static const struct usb_device_id podhd_id_table[] = { { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, + { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, {} };
@@ -409,6 +411,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3LIVE] = { + .id = "PODX3LIVE", + .name = "POD X3 LIVE", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/*
Some PODs (e.g. POD X3) have bulk instead of interrupt endpoints for data transfer.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 63 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 5fd6cad..9b16777 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -66,10 +66,17 @@ static int line6_start_listen(struct usb_line6 *line6) { int err;
- usb_fill_int_urb(line6->urb_listen, line6->usbdev, - usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), - line6->buffer_listen, LINE6_BUFSIZE_LISTEN, - line6_data_received, line6, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + } else { + usb_fill_bulk_urb(line6->urb_listen, line6->usbdev, + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6); + } line6->urb_listen->actual_length = 0; err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); return err; @@ -90,6 +97,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0; + const struct line6_properties *properties = line6->properties;
for (i = 0; i < size; i += line6->max_packet_size) { int partial; @@ -97,15 +105,21 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int frag_size = min(line6->max_packet_size, size - i); int retval;
- retval = usb_interrupt_msg(line6->usbdev, - usb_sndintpipe(line6->usbdev, - line6->properties->ep_ctrl_w), - (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + } else { + retval = usb_bulk_msg(line6->usbdev, + usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + }
if (retval) { dev_err(line6->ifcdev, - "usb_interrupt_msg failed (%d)\n", retval); + "usb_bulk_msg failed (%d)\n", retval); break; }
@@ -140,10 +154,17 @@ static int line6_send_raw_message_async_part(struct message *msg, int done = msg->done; int bytes = min(msg->size - done, line6->max_packet_size);
- usb_fill_int_urb(urb, line6->usbdev, - usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), - (char *)msg->buffer + done, bytes, - line6_async_request_sent, msg, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + } else { + usb_fill_bulk_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg); + }
msg->done += bytes; retval = usb_submit_urb(urb, GFP_ATOMIC); @@ -462,7 +483,19 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; + const struct line6_properties *properties = line6->properties; + int pipe; + struct usb_host_endpoint *ep; + + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + pipe = + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } else { + pipe = + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } + ep = usbdev->ep_in[usb_pipeendpoint(pipe)]; + if (ep) { line6->interval = ep->desc.bInterval; if (usbdev->speed == USB_SPEED_LOW) {
On Fri, 19 Aug 2016 00:20:36 +0200, Andrej Krutak wrote:
Some PODs (e.g. POD X3) have bulk instead of interrupt endpoints for data transfer.
This patch should be applied before the actual usage of POD X3, so it should be patch 4.
Takashi
Signed-off-by: Andrej Krutak dev@andree.sk
sound/usb/line6/driver.c | 63 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 5fd6cad..9b16777 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -66,10 +66,17 @@ static int line6_start_listen(struct usb_line6 *line6) { int err;
- usb_fill_int_urb(line6->urb_listen, line6->usbdev,
usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r),
line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
line6_data_received, line6, line6->interval);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
usb_fill_int_urb(line6->urb_listen, line6->usbdev,
usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r),
line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
line6_data_received, line6, line6->interval);
- } else {
usb_fill_bulk_urb(line6->urb_listen, line6->usbdev,
usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r),
line6->buffer_listen, LINE6_BUFSIZE_LISTEN,
line6_data_received, line6);
- } line6->urb_listen->actual_length = 0; err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); return err;
@@ -90,6 +97,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0;
const struct line6_properties *properties = line6->properties;
for (i = 0; i < size; i += line6->max_packet_size) { int partial;
@@ -97,15 +105,21 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int frag_size = min(line6->max_packet_size, size - i); int retval;
retval = usb_interrupt_msg(line6->usbdev,
usb_sndintpipe(line6->usbdev,
line6->properties->ep_ctrl_w),
(char *)frag_buf, frag_size,
&partial, LINE6_TIMEOUT * HZ);
if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
retval = usb_interrupt_msg(line6->usbdev,
usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w),
(char *)frag_buf, frag_size,
&partial, LINE6_TIMEOUT * HZ);
} else {
retval = usb_bulk_msg(line6->usbdev,
usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w),
(char *)frag_buf, frag_size,
&partial, LINE6_TIMEOUT * HZ);
}
if (retval) { dev_err(line6->ifcdev,
"usb_interrupt_msg failed (%d)\n", retval);
}"usb_bulk_msg failed (%d)\n", retval); break;
@@ -140,10 +154,17 @@ static int line6_send_raw_message_async_part(struct message *msg, int done = msg->done; int bytes = min(msg->size - done, line6->max_packet_size);
- usb_fill_int_urb(urb, line6->usbdev,
usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w),
(char *)msg->buffer + done, bytes,
line6_async_request_sent, msg, line6->interval);
if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
usb_fill_int_urb(urb, line6->usbdev,
usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w),
(char *)msg->buffer + done, bytes,
line6_async_request_sent, msg, line6->interval);
} else {
usb_fill_bulk_urb(urb, line6->usbdev,
usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w),
(char *)msg->buffer + done, bytes,
line6_async_request_sent, msg);
}
msg->done += bytes; retval = usb_submit_urb(urb, GFP_ATOMIC);
@@ -462,7 +483,19 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev;
- struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r];
- const struct line6_properties *properties = line6->properties;
- int pipe;
- struct usb_host_endpoint *ep;
- if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
pipe =
usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r);
- } else {
pipe =
usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r);
- }
- ep = usbdev->ep_in[usb_pipeendpoint(pipe)];
- if (ep) { line6->interval = ep->desc.bInterval; if (usbdev->speed == USB_SPEED_LOW) {
-- 1.9.1
Not all PODs use MIDI via USB data interface, thus allow avoiding that code and instead using direct processing.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 59 ++++++++++++++++++++++++++++-------------------- sound/usb/line6/driver.h | 8 ++++--- sound/usb/line6/midi.c | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9b16777..853a143 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -290,26 +290,31 @@ static void line6_data_received(struct urb *urb) if (urb->status == -ESHUTDOWN) return;
- done = - line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (done < urb->actual_length) { - line6_midibuf_ignore(mb, done); - dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", - done, urb->actual_length); - } + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + }
- for (;;) { - done = - line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MESSAGE_MAXLEN);
- if (done == 0) - break; + if (done == 0) + break;
- line6->message_length = done; - line6_midi_receive(line6, line6->buffer_message, done); + line6->message_length = done; + line6_midi_receive(line6, line6->buffer_message, done);
+ if (line6->process_message) + line6->process_message(line6); + } + } else { if (line6->process_message) line6->process_message(line6); } @@ -469,7 +474,9 @@ static void line6_destruct(struct snd_card *card) struct usb_device *usbdev = line6->usbdev;
/* free buffer memory first: */ - kfree(line6->buffer_message); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + kfree(line6->buffer_message); + kfree(line6->buffer_listen);
/* then free URBs: */ @@ -515,7 +522,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control_midi(struct usb_line6 *line6) +static int line6_init_cap_control(struct usb_line6 *line6) { int ret;
@@ -524,14 +531,16 @@ static int line6_init_cap_control_midi(struct usb_line6 *line6) if (!line6->buffer_listen) return -ENOMEM;
- line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); - if (!line6->buffer_message) - return -ENOMEM; - line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); if (!line6->urb_listen) return -ENOMEM;
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + if (!line6->buffer_message) + return -ENOMEM; + } + ret = line6_start_listen(line6); if (ret < 0) { dev_err(line6->ifcdev, "cannot start listening: %d\n", ret); @@ -605,8 +614,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - ret = line6_init_cap_control_midi(line6); + if (properties->capabilities & LINE6_CAP_CONTROL) { + ret = line6_init_cap_control(line6); if (ret < 0) goto error; } @@ -676,7 +685,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -695,7 +704,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7aeb6ad..14093c5 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -147,15 +147,17 @@ struct usb_line6 { /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to POD data endpoint */ + /* Buffer for incoming data from POD data endpoint */ unsigned char *buffer_listen;
- /* Buffer for message to be processed */ + /* Buffer for message to be processed, generated from MIDI layer */ unsigned char *buffer_message;
- /* Length of message to be processed */ + /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* If MIDI is supported, buffer_message contains the pre-processed data; + * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *); void (*disconnect)(struct usb_line6 *line6); }; diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c index cebea9b..d0fb2f2 100644 --- a/sound/usb/line6/midi.c +++ b/sound/usb/line6/midi.c @@ -258,7 +258,7 @@ int line6_init_midi(struct usb_line6 *line6) struct snd_rawmidi *rmidi; struct snd_line6_midi *line6midi;
- if (!(line6->properties->capabilities & LINE6_CAP_CONTROL)) { + if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { /* skip MIDI initialization and report success */ return 0; }
On Fri, 19 Aug 2016 00:20:37 +0200, Andrej Krutak wrote:
Not all PODs use MIDI via USB data interface, thus allow avoiding that code and instead using direct processing.
Signed-off-by: Andrej Krutak dev@andree.sk
sound/usb/line6/driver.c | 59 ++++++++++++++++++++++++++++-------------------- sound/usb/line6/driver.h | 8 ++++--- sound/usb/line6/midi.c | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9b16777..853a143 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -290,26 +290,31 @@ static void line6_data_received(struct urb *urb) if (urb->status == -ESHUTDOWN) return;
- done =
line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) {
done =
line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (done < urb->actual_length) {
line6_midibuf_ignore(mb, done);
dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
done, urb->actual_length);
- }
if (done < urb->actual_length) {
line6_midibuf_ignore(mb, done);
dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n",
done, urb->actual_length);
}
- for (;;) {
done =
line6_midibuf_read(mb, line6->buffer_message,
LINE6_MESSAGE_MAXLEN);
for (;;) {
done =
line6_midibuf_read(mb, line6->buffer_message,
LINE6_MESSAGE_MAXLEN);
if (done == 0)
break;
if (done == 0)
break;
line6->message_length = done;
line6_midi_receive(line6, line6->buffer_message, done);
line6->message_length = done;
line6_midi_receive(line6, line6->buffer_message, done);
if (line6->process_message)
line6->process_message(line6);
}
} else { if (line6->process_message) line6->process_message(line6); }
Both if and else run the same code (line6->process_message) here at the end. That is, this can be outside the if block.
Also, the patch should be also before the actual usage, i.e. patch 5.
Takashi
for (;;) {
done =
line6_midibuf_read(mb, line6->buffer_message,
LINE6_MESSAGE_MAXLEN);
for (;;) {
done =
line6_midibuf_read(mb, line6->buffer_message,
LINE6_MESSAGE_MAXLEN);
if (done == 0)
break;
if (done == 0)
break;
line6->message_length = done;
line6_midi_receive(line6, line6->buffer_message, done);
line6->message_length = done;
line6_midi_receive(line6, line6->buffer_message, done);
if (line6->process_message)
line6->process_message(line6);
}
} else { if (line6->process_message) line6->process_message(line6); }
Both if and else run the same code (line6->process_message) here at the end. That is, this can be outside the if block.
Nope, the first one is done in a loop...
Also, the patch should be also before the actual usage, i.e. patch 5.
Ack.
Only determine control port properties if the devices needs it. Only initialize PCM for POD HD devices that support it. No POD HD seems to support MIDI, thus drop the initialization.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 3 +-- sound/usb/line6/podhd.c | 19 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 853a143..8a71d45 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -612,9 +612,8 @@ int line6_probe(struct usb_interface *interface, goto error; }
- line6_get_interval(line6); - if (properties->capabilities & LINE6_CAP_CONTROL) { + line6_get_interval(line6); ret = line6_init_cap_control(line6); if (ret < 0) goto error; diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8246ea5..193eb29 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -314,17 +314,14 @@ static int podhd_init(struct usb_line6 *line6, return err; }
- /* initialize MIDI subsystem: */ - err = line6_init_midi(line6); - if (err < 0) - return err; - - /* initialize PCM subsystem: */ - err = line6_init_pcm(line6, - (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : - &podhd_pcm_properties); - if (err < 0) - return err; + if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); + if (err < 0) + return err; + }
if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { /* register USB audio system directly */
On Fri, 19 Aug 2016 00:20:38 +0200, Andrej Krutak wrote:
Only determine control port properties if the devices needs it. Only initialize PCM for POD HD devices that support it. No POD HD seems to support MIDI, thus drop the initialization.
This should be split to two individual patches. They are irrelevant with each other at all.
Takashi
Signed-off-by: Andrej Krutak dev@andree.sk
sound/usb/line6/driver.c | 3 +-- sound/usb/line6/podhd.c | 19 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 853a143..8a71d45 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -612,9 +612,8 @@ int line6_probe(struct usb_interface *interface, goto error; }
- line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL) {
ret = line6_init_cap_control(line6); if (ret < 0) goto error;line6_get_interval(line6);
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8246ea5..193eb29 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -314,17 +314,14 @@ static int podhd_init(struct usb_line6 *line6, return err; }
- /* initialize MIDI subsystem: */
- err = line6_init_midi(line6);
- if (err < 0)
return err;
- /* initialize PCM subsystem: */
- err = line6_init_pcm(line6,
(id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties :
&podhd_pcm_properties);
- if (err < 0)
return err;
if (pod->line6.properties->capabilities & LINE6_CAP_PCM) {
/* initialize PCM subsystem: */
err = line6_init_pcm(line6,
(id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties :
&podhd_pcm_properties);
if (err < 0)
return err;
}
if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { /* register USB audio system directly */
-- 1.9.1
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@andree.sk --- include/uapi/sound/asound.h | 3 +- sound/usb/line6/driver.c | 151 ++++++++++++++++++++++++++++++++++++++++++-- sound/usb/line6/driver.h | 23 ++++++- 3 files changed, 170 insertions(+), 7 deletions(-)
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..ac4c702 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -17,6 +17,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/hwdep.h>
#include "capture.h" #include "driver.h" @@ -303,7 +304,7 @@ static void line6_data_received(struct urb *urb) for (;;) { done = line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + LINE6_MIDI_MESSAGE_MAXLEN);
if (done == 0) break; @@ -315,8 +316,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); @@ -473,14 +477,16 @@ static void line6_destruct(struct snd_card *card) struct usb_line6 *line6 = card->private_data; struct usb_device *usbdev = line6->usbdev;
- /* free buffer memory first: */ - if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + /* Free buffer memory first. We cannot depend on the existence of private + * data from the (podhd) module, it may be gone already during this call */ + if (line6->buffer_message) kfree(line6->buffer_message);
kfree(line6->buffer_listen);
/* then free URBs: */ usb_free_urb(line6->urb_listen); + line6->urb_listen = NULL;
/* decrement reference counters: */ usb_put_dev(usbdev); @@ -522,6 +528,138 @@ 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 layer provides atomicity here */ + + line6->messages.active = 1; + + return 0; +} + +/* Stop buffering */ +static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_line6 *line6 = hw->private_data; + + line6->messages.active = 0; + + 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; + long rv = 0; + unsigned int out_count; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + + while (kfifo_len(&line6->messages.fifo) == 0) { + mutex_unlock(&line6->messages.read_lock); + + rv = wait_event_interruptible( + line6->messages.wait_queue, + kfifo_len(&line6->messages.fifo) != 0); + if (rv < 0) + return rv; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + } + + if (kfifo_peek_len(&line6->messages.fifo) > count) { + /* Buffer too small; allow re-read of the current item... */ + rv = -EINVAL; + } else { + rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count); + if (rv == 0) + rv = out_count; + } + + mutex_unlock(&line6->messages.read_lock); + return rv; +} + +/* 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; + + if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) { + /* This is an arbitrary limit - still better than nothing... */ + return -EINVAL; + } + + data_copy = memdup_user(data, count); + if (IS_ERR(ERR_PTR)) + return -ENOMEM; + + 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) +{ + if (!line6->messages.active) + return; + + if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) { + /* No race condition here, there's only one writer */ + kfifo_in(&line6->messages.fifo, + line6->buffer_message, line6->message_length); + } /* else TODO: signal overflow */ + + wake_up_interruptible(&line6->messages.wait_queue); +} + +static int line6_hwdep_init(struct usb_line6 *line6) +{ + int err; + struct snd_hwdep *hwdep; + + /* TODO: usb_driver_claim_interface(); */ + line6->process_message = line6_hwdep_push_message; + line6->messages.active = 0; + init_waitqueue_head(&line6->messages.wait_queue); + mutex_init(&line6->messages.read_lock); + INIT_KFIFO(line6->messages.fifo); + + err = snd_hwdep_new(line6->card, "config", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "config"); + hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; + hwdep->ops = hwdep_ops; + hwdep->private_data = line6; + hwdep->exclusive = true; + +end: + return err; +} + static int line6_init_cap_control(struct usb_line6 *line6) { int ret; @@ -536,9 +674,13 @@ static int line6_init_cap_control(struct usb_line6 *line6) return -ENOMEM;
if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); if (!line6->buffer_message) return -ENOMEM; + } else { + ret = line6_hwdep_init(line6); + if (ret < 0) + return ret; }
ret = line6_start_listen(line6); @@ -716,3 +858,4 @@ EXPORT_SYMBOL_GPL(line6_resume); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); + diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 14093c5..207a1d5 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -12,8 +12,9 @@ #ifndef DRIVER_H #define DRIVER_H
-#include <linux/spinlock.h> #include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/kfifo.h> #include <sound/core.h>
#include "midi.h" @@ -32,7 +33,16 @@
#define LINE6_TIMEOUT 1 #define LINE6_BUFSIZE_LISTEN 64 -#define LINE6_MESSAGE_MAXLEN 256 +#define LINE6_MIDI_MESSAGE_MAXLEN 256 + +#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7 +/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */ +#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER) + + +#if LINE6_BUFSIZE_LISTEN > 65535 +#error "Use dynamic fifo instead" +#endif
/* Line 6 MIDI control commands @@ -156,6 +166,15 @@ struct usb_line6 { /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* Circular buffer for non-MIDI control messages */ + struct { + struct mutex read_lock; + wait_queue_head_t wait_queue; + int active:1; + STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT) + fifo; + } messages; + /* If MIDI is supported, buffer_message contains the pre-processed data; * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *);
Hello all,
attached are the yet again reworked patches. Basically the diff is only some formatting. In addition, Some of the previously big patches were split to multiple smaller ones (but I didn't chceck that after each one, the compilation works and the result doesn't segfault etc.)
Greetings!
Andrej Krutak (12): ALSA: line6: Enable different number of URBs for frame transfers ALSA: line6: Add high-speed USB support ALSA: line6: Support assymetrical in/out configurations ALSA: line6: Allow different channel numbers for in/out ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture ALSA: line6: Distinguish device init (ctrl EP) and MIDI data transfer (int EP) ALSA: line6: Allow processing of raw incoming messages ALSA: line6: Add support for POD X3 ALSA: line6: Add support for POD X3 Live (only USB ID differs from POD X3) ALSA: line6: Only determine control port properties if needed ALSA: line6: Cleanup podhd initialization ALSA: line6: Add hwdep interface to access the POD control messages
include/uapi/sound/asound.h | 3 +- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/capture.c | 50 +++++--- sound/usb/line6/driver.c | 269 +++++++++++++++++++++++++++++++++------ sound/usb/line6/driver.h | 58 +++++++-- sound/usb/line6/midi.c | 2 +- sound/usb/line6/pcm.c | 82 ++++++++---- sound/usb/line6/pcm.h | 19 +-- sound/usb/line6/playback.c | 37 ++++-- sound/usb/line6/pod.c | 12 +- sound/usb/line6/podhd.c | 300 ++++++++++++++++++++++++++++++++++++++++---- sound/usb/line6/toneport.c | 6 +- sound/usb/line6/variax.c | 6 +- 13 files changed, 702 insertions(+), 146 deletions(-)
This basically changes LINE6_ISO_BUFFERS constant to a configurable iso_buffers property.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 17 +++++++++++------ sound/usb/line6/driver.c | 1 + sound/usb/line6/driver.h | 2 ++ sound/usb/line6/pcm.c | 23 +++++++++++++++-------- sound/usb/line6/pcm.h | 2 +- sound/usb/line6/playback.c | 19 ++++++++++++------- 6 files changed, 42 insertions(+), 22 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index f518fbb..e20a6bd 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -29,10 +29,10 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) int ret; struct urb *urb_in;
- index = - find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit(&line6pcm->in.active_urbs, + line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -73,7 +73,7 @@ int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_in_urb(line6pcm); if (ret < 0) break; @@ -154,7 +154,7 @@ static void audio_in_callback(struct urb *urb) line6pcm->in.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) if (urb == line6pcm->in.urbs[index]) break;
@@ -247,8 +247,13 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->in.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio in: */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8..527c408 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -467,6 +467,7 @@ static void line6_get_interval(struct usb_line6 *line6) unsigned epnum = usb_pipeendpoint(pipe);
ep = usbdev->ep_in[epnum]; + line6->iso_buffers = LINE6_ISO_BUFFERS; if (ep) { line6->interval = ep->desc.bInterval; line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7da643e..43dd1d0 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -111,6 +111,8 @@ struct usb_line6 {
/* Interval (ms) */ int interval; + /* Number of isochronous URBs used for frame transfers */ + int iso_buffers;
/* Maximum size of USB packet */ int max_packet_size; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 41aa335..6c9b7cf 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -104,7 +104,7 @@ static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) { if (!test_and_set_bit(i, &pcms->unlink_urbs)) usb_unlink_urb(pcms->urbs[i]); @@ -124,7 +124,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
do { alive = 0; - for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) alive++; } @@ -153,8 +153,9 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, { /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { - pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * + LINE6_ISO_PACKETS * + line6pcm->max_packet_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -434,24 +435,30 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + /* Most likely impossible in current code... */ + if (pcms->urbs == NULL) + return; + + for (i = 0; i < iso_buffers; i++) { if (pcms->urbs[i]) { usb_kill_urb(pcms->urbs[i]); usb_free_urb(pcms->urbs[i]); } } + kfree(pcms->urbs); + pcms->urbs = NULL; }
static void line6_cleanup_pcm(struct snd_pcm *pcm) { struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
- cleanup_urbs(&line6pcm->out); - cleanup_urbs(&line6pcm->in); + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); kfree(line6pcm); }
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 508410a..e983880 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -90,7 +90,7 @@ struct line6_pcm_properties {
struct line6_pcm_stream { /* allocated URBs */ - struct urb *urbs[LINE6_ISO_BUFFERS]; + struct urb **urbs;
/* Temporary buffer; * Since the packet size is not known in advance, this buffer is diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 97ed593..7a52806 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -154,10 +154,10 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); struct urb *urb_out;
- index = - find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit(&line6pcm->out.active_urbs, + line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -286,7 +286,7 @@ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) break; @@ -313,11 +313,11 @@ static void audio_out_callback(struct urb *urb) line6pcm->out.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; index++) + for (index = 0; index < line6pcm->line6->iso_buffers; index++) if (urb == line6pcm->out.urbs[index]) break;
- if (index >= LINE6_ISO_BUFFERS) + if (index >= line6pcm->line6->iso_buffers) return; /* URB has been unlinked asynchronously */
for (i = 0; i < LINE6_ISO_PACKETS; i++) @@ -401,8 +401,13 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->out.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio out: */
This has two parts: * intervals_per_second setup (high speed needs 8000, instead of 1000) * iso_buffers setup (count of iso buffers depends on USB speed, 2 is not enough for high speed)
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 10 +++++++++- sound/usb/line6/driver.c | 15 ++++++++++----- sound/usb/line6/driver.h | 15 ++++++++++++--- sound/usb/line6/pcm.h | 6 ++---- sound/usb/line6/playback.c | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index e20a6bd..44e9a8c 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -181,7 +181,15 @@ static void audio_in_callback(struct urb *urb)
length += fsize;
- /* the following assumes LINE6_ISO_PACKETS == 1: */ + BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1, + "The following code assumes LINE6_ISO_PACKETS == 1"); + /* TODO: + * Also, if iso_buffers != 2, the prev frame is almost random at + * playback side. + * This needs to be redesigned. It should be "stable", but we may + * experience sync problems on such high-speed configs. + */ + line6pcm->prev_fbuf = fbuf; line6pcm->prev_fsize = fsize;
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 527c408..14032d9 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -462,14 +462,18 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep; - unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r); - unsigned epnum = usb_pipeendpoint(pipe); + struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r];
- ep = usbdev->ep_in[epnum]; - line6->iso_buffers = LINE6_ISO_BUFFERS; if (ep) { line6->interval = ep->desc.bInterval; + if (usbdev->speed == USB_SPEED_LOW) { + line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_LOW_ISO_BUFFERS; + } else { + line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_HIGH_ISO_BUFFERS; + } + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); } else { dev_err(line6->ifcdev, @@ -559,6 +563,7 @@ int line6_probe(struct usb_interface *interface, /* query interface number */ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+ /* TODO reserves the bus bandwidth even without actual transfer */ ret = usb_set_interface(usbdev, interface_number, properties->altsetting); if (ret < 0) { diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 43dd1d0..a55eb88 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,7 +18,13 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND 1000 +#define USB_LOW_ISO_BUFFERS 2 + +/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND 8000 +#define USB_HIGH_ISO_BUFFERS 16
/* Fallback USB interval and max packet size values */ #define LINE6_FALLBACK_INTERVAL 10 @@ -109,12 +115,15 @@ struct usb_line6 { /* Properties */ const struct line6_properties *properties;
- /* Interval (ms) */ + /* Interval for data USB packets */ int interval; + /* ...for isochronous transfers framing */ + int intervals_per_second; + /* Number of isochronous URBs used for frame transfers */ int iso_buffers;
- /* Maximum size of USB packet */ + /* Maximum size of data USB packet */ int max_packet_size;
/* Device representing the USB interface */ diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index e983880..38bf4df 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -20,9 +20,6 @@
#include "driver.h"
-/* number of URBs */ -#define LINE6_ISO_BUFFERS 2 - /* number of USB frames per URB The Line 6 Windows driver always transmits two frames per packet, but @@ -31,7 +28,8 @@ */ #define LINE6_ISO_PACKETS 1
-/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +/* in a "full speed" device (such as the PODxt Pro) this means 1ms, + for "high speed" it's 1/8ms */ #define LINE6_ISO_INTERVAL 1
#define LINE6_IMPULSE_DEFAULT_PERIOD 100 diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7a52806..d53aad1 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -151,7 +151,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->properties->rates.rats[0].num_min; const int frame_factor = line6pcm->properties->rates.rats[0].den * - (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out;
index = find_first_zero_bit(&line6pcm->out.active_urbs,
Splits max_packet_size to max_packet_size_in/out (e.g. for different channel counts).
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 10 +++++----- sound/usb/line6/pcm.c | 28 ++++++++++++++++------------ sound/usb/line6/pcm.h | 5 +++-- sound/usb/line6/playback.c | 2 +- 4 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 44e9a8c..73cea26 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -44,13 +44,13 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; fin->offset = urb_size; - fin->length = line6pcm->max_packet_size; - urb_size += line6pcm->max_packet_size; + fin->length = line6pcm->max_packet_size_in; + urb_size += line6pcm->max_packet_size_in; }
urb_in->transfer_buffer = line6pcm->in.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; urb_in->transfer_buffer_length = urb_size; urb_in->context = line6pcm;
@@ -173,10 +173,10 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
- if (fsize > line6pcm->max_packet_size) { + if (fsize > line6pcm->max_packet_size_in) { dev_err(line6pcm->line6->ifcdev, "driver and/or device bug: packet too large (%d > %d)\n", - fsize, line6pcm->max_packet_size); + fsize, line6pcm->max_packet_size_in); }
length += fsize; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 6c9b7cf..5fc1394 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -146,16 +146,20 @@ get_stream(struct snd_line6_pcm *line6pcm, int direction) }
/* allocate a buffer if not opened yet; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, - struct line6_pcm_stream *pstr, int type) + struct line6_pcm_stream *pstr, int direction, int type) { + const int pkt_size = + (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + line6pcm->max_packet_size_out : + line6pcm->max_packet_size_in; + /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * - LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + LINE6_ISO_PACKETS * pkt_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -163,12 +167,11 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, }
/* free a buffer if all streams are closed; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, struct line6_pcm_stream *pstr, int type) { - clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); @@ -195,6 +198,7 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, else ret = line6_submit_audio_in_all_urbs(line6pcm); } + if (ret < 0) clear_bit(type, &pstr->running); spin_unlock_irqrestore(&pstr->lock, flags); @@ -530,12 +534,12 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6;
- /* Read and write buffers are sized identically, so choose minimum */ - line6pcm->max_packet_size = min( - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0), - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1)); + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1);
spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 38bf4df..f408d15 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -155,11 +155,12 @@ struct snd_line6_pcm { /* Previously captured frame (for software monitoring) */ unsigned char *prev_fbuf;
- /* Size of previously captured frame (for software monitoring) */ + /* Size of previously captured frame (for software monitoring/sync) */ int prev_fsize;
/* Maximum size of USB packet */ - int max_packet_size; + int max_packet_size_in; + int max_packet_size_out;
/* PCM playback volume (left and right) */ int volume_playback[2]; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index d53aad1..7b2644f 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -195,7 +195,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_frames = urb_size / bytes_per_frame; urb_out->transfer_buffer = line6pcm->out.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; urb_out->transfer_buffer_length = urb_size; urb_out->context = line6pcm;
Hi Andrej,
[auto build test ERROR on sound/for-next] [also build test ERROR on v4.8-rc6 next-20160916] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] [Suggest to use git(>=2.9.0) format-patch --base=<commit> (or --base=auto for convenience) to record what (public, well-known) commit your patch series was built on] [Check https://git-scm.com/docs/git-format-patch for more information]
url: https://github.com/0day-ci/linux/commits/Andrej-Krutak/Line6-POD-X3-X3Live-s... base: https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next config: s390-allmodconfig (attached as .config) compiler: s390x-linux-gnu-gcc (Debian 6.1.1-9) 6.1.1 20160705 reproduce: wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/ma... -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree make.cross ARCH=s390
Note: the linux-review/Andrej-Krutak/Line6-POD-X3-X3Live-suport/20160916-210558 HEAD 9ed07506e73b5835987ae6d7e2ba79e6c29dc6b6 builds fine. It only hurts bisectibility.
All errors (new ones prefixed by >>):
sound/usb/line6/pcm.c: In function 'line6_pcm_acquire':
sound/usb/line6/pcm.c:297:9: error: too few arguments to function 'line6_buffer_acquire'
ret = line6_buffer_acquire(line6pcm, pstr, type); ^~~~~~~~~~~~~~~~~~~~ sound/usb/line6/pcm.c:151:12: note: declared here static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, ^~~~~~~~~~~~~~~~~~~~ sound/usb/line6/pcm.c: In function 'snd_line6_hw_params': sound/usb/line6/pcm.c:342:8: error: too few arguments to function 'line6_buffer_acquire' ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM); ^~~~~~~~~~~~~~~~~~~~ sound/usb/line6/pcm.c:151:12: note: declared here static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, ^~~~~~~~~~~~~~~~~~~~
vim +/line6_buffer_acquire +297 sound/usb/line6/pcm.c
63e20df1 Takashi Iwai 2015-01-27 291 struct line6_pcm_stream *pstr; 63e20df1 Takashi Iwai 2015-01-27 292 int ret = 0, dir; 63e20df1 Takashi Iwai 2015-01-27 293 63e20df1 Takashi Iwai 2015-01-27 294 mutex_lock(&line6pcm->state_mutex); 63e20df1 Takashi Iwai 2015-01-27 295 for (dir = 0; dir < 2; dir++) { 63e20df1 Takashi Iwai 2015-01-27 296 pstr = get_stream(line6pcm, dir); 63e20df1 Takashi Iwai 2015-01-27 @297 ret = line6_buffer_acquire(line6pcm, pstr, type); 63e20df1 Takashi Iwai 2015-01-27 298 if (ret < 0) 63e20df1 Takashi Iwai 2015-01-27 299 goto error; 63e20df1 Takashi Iwai 2015-01-27 300 if (!pstr->running)
:::::: The code at line 297 was first introduced by commit :::::: 63e20df1e5b2ef8d871ecbdb6c038d554ed1ca74 ALSA: line6: Reorganize PCM stream handling
:::::: TO: Takashi Iwai tiwai@suse.de :::::: CC: Takashi Iwai tiwai@suse.de
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Changes bytes_per_frame to bytes_per_channel.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 8 ++++++-- sound/usb/line6/driver.h | 2 +- sound/usb/line6/pcm.h | 2 +- sound/usb/line6/playback.c | 14 +++++++++++--- sound/usb/line6/pod.c | 3 +-- sound/usb/line6/podhd.c | 4 +--- sound/usb/line6/toneport.c | 2 +- 7 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 73cea26..7959aaa 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -90,7 +90,9 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); struct snd_pcm_runtime *runtime = substream->runtime; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max; int frames = fsize / bytes_per_frame;
if (runtime == NULL) @@ -191,7 +193,9 @@ static void audio_in_callback(struct urb *urb) */
line6pcm->prev_fbuf = fbuf; - line6pcm->prev_fsize = fsize; + line6pcm->prev_fsize = fsize / + (line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max);
if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) && diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index a55eb88..2d32139 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -31,7 +31,7 @@ #define LINE6_FALLBACK_MAXPACKETSIZE 16
#define LINE6_TIMEOUT 1 -#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MESSAGE_MAXLEN 256
/* diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index f408d15..58d36f9 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -83,7 +83,7 @@ enum { struct line6_pcm_properties { struct snd_pcm_hardware playback_hw, capture_hw; struct snd_pcm_hw_constraint_ratdens rates; - int bytes_per_frame; + int bytes_per_channel; };
struct line6_pcm_stream { diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7b2644f..bc5799c 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -146,7 +146,9 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) int index; int i, urb_size, urb_frames; int ret; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; const int frame_increment = line6pcm->properties->rates.rats[0].num_min; const int frame_factor = @@ -165,6 +167,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out = line6pcm->out.urbs[index]; urb_size = 0;
+ /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */ for (i = 0; i < LINE6_ISO_PACKETS; ++i) { /* compute frame size for given sampling rate */ int fsize = 0; @@ -178,9 +181,11 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->out.count += frame_increment; n = line6pcm->out.count / frame_factor; line6pcm->out.count -= n * frame_factor; - fsize = n * bytes_per_frame; + fsize = n; }
+ fsize *= bytes_per_frame; + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; @@ -305,6 +310,9 @@ static void audio_out_callback(struct urb *urb) struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max;
#if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -329,7 +337,7 @@ static void audio_out_callback(struct urb *urb) struct snd_pcm_runtime *runtime = substream->runtime;
line6pcm->out.pos_done += - length / line6pcm->properties->bytes_per_frame; + length / bytes_per_frame;
if (line6pcm->out.pos_done >= runtime->buffer_size) line6pcm->out.pos_done -= runtime->buffer_size; diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 45dd348..36e7274 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -83,7 +83,6 @@ struct usb_line6_pod { };
#define POD_SYSEX_CODE 3 -#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
/* *INDENT-OFF* */
@@ -167,7 +166,7 @@ static struct line6_pcm_properties pod_pcm_properties = { .rates = { .nrats = 1, .rats = &pod_ratden}, - .bytes_per_frame = POD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
static const char pod_version_header[] = { diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 63dcaef..4fc4789 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -25,8 +25,6 @@ enum { LINE6_PODHD500_1, };
-#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ - static struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, @@ -73,7 +71,7 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_frame = PODHD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* 24bit audio (stereo) */ };
/* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 6d4c50c..da76e03 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -114,7 +114,7 @@ static struct line6_pcm_properties toneport_pcm_properties = { .rates = { .nrats = 1, .rats = &toneport_ratden}, - .bytes_per_frame = 4 + .bytes_per_channel = 2 };
static const struct {
E.g. POD X3 seems to require playback data to be sent to it to generate capture data. Otherwise the device stalls and doesn't send any more capture data until it's reset.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 5 +++++ sound/usb/line6/driver.h | 4 +++- sound/usb/line6/pcm.c | 35 ++++++++++++++++++++++++++--------- sound/usb/line6/pcm.h | 4 +++- sound/usb/line6/toneport.c | 4 ++-- 5 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 7959aaa..bf159c4 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -232,6 +232,8 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err;
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); + runtime->hw = line6pcm->properties->capture_hw; return 0; } @@ -239,6 +241,9 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) /* close capture callback */ static int snd_line6_capture_close(struct snd_pcm_substream *substream) { + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER); return 0; }
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 2d32139..0bcab38 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -100,8 +100,10 @@ enum { LINE6_CAP_CONTROL = 1 << 0, /* device supports PCM input/output via USB */ LINE6_CAP_PCM = 1 << 1, - /* device support hardware monitoring */ + /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device requires output data when input is read */ + LINE6_CAP_IN_NEEDS_OUT = 1 << 3, };
/* diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 5fc1394..5f59993 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -52,7 +52,7 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
line6pcm->impulse_volume = value; if (value > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0; return err; @@ -242,6 +242,14 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + if (err < 0) + return err; + } err = line6_stream_start(line6pcm, s->stream, LINE6_STREAM_PCM); if (err < 0) @@ -250,6 +258,12 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + } line6_stream_stop(line6pcm, s->stream, LINE6_STREAM_PCM); break; @@ -283,27 +297,30 @@ snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) return pstr->pos_done; }
-/* Acquire and start duplex streams: +/* Acquire and optionally start duplex streams: * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR */ -int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) { struct line6_pcm_stream *pstr; int ret = 0, dir;
+ /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ mutex_lock(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); - ret = line6_buffer_acquire(line6pcm, pstr, type); + ret = line6_buffer_acquire(line6pcm, pstr, dir, type); if (ret < 0) goto error; if (!pstr->running) line6_wait_clear_audio_urbs(line6pcm, pstr); } - for (dir = 0; dir < 2; dir++) { - ret = line6_stream_start(line6pcm, dir, type); - if (ret < 0) - goto error; + if (start) { + for (dir = 0; dir < 2; dir++) { + ret = line6_stream_start(line6pcm, dir, type); + if (ret < 0) + goto error; + } } error: mutex_unlock(&line6pcm->state_mutex); @@ -339,7 +356,7 @@ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
mutex_lock(&line6pcm->state_mutex); - ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM); + ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, LINE6_STREAM_PCM); if (ret < 0) goto error;
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 58d36f9..5f796ef8 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -72,6 +72,7 @@ enum { LINE6_STREAM_PCM, LINE6_STREAM_MONITOR, LINE6_STREAM_IMPULSE, + LINE6_STREAM_CAPTURE_HELPER, };
/* misc bit flags for PCM operation */ @@ -190,7 +191,8 @@ extern int snd_line6_hw_params(struct snd_pcm_substream *substream, extern int snd_line6_hw_free(struct snd_pcm_substream *substream); extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream); extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); -extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, + bool start); extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
#endif diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index da76e03..8e22f43 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -177,7 +177,7 @@ static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, line6pcm->volume_monitor = ucontrol->value.integer.value[0];
if (line6pcm->volume_monitor > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true); if (err < 0) { line6pcm->volume_monitor = 0; line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); @@ -246,7 +246,7 @@ static void toneport_start_pcm(unsigned long arg) struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg; struct usb_line6 *line6 = &toneport->line6;
- line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR); + line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); }
/* control definition */
POD X3 can initialize similarly to older PODs, but it doesn't have the MIDI interface. Instead, configuration is done via proprietary bulk EP messages.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 72 ++++++++++++++++++++++++++++++++++-------------- sound/usb/line6/driver.h | 6 ++-- sound/usb/line6/pod.c | 9 +++++- sound/usb/line6/variax.c | 6 ++-- 4 files changed, 68 insertions(+), 25 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 14032d9..9b16777 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -66,10 +66,17 @@ static int line6_start_listen(struct usb_line6 *line6) { int err;
- usb_fill_int_urb(line6->urb_listen, line6->usbdev, - usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), - line6->buffer_listen, LINE6_BUFSIZE_LISTEN, - line6_data_received, line6, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + } else { + usb_fill_bulk_urb(line6->urb_listen, line6->usbdev, + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6); + } line6->urb_listen->actual_length = 0; err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); return err; @@ -90,6 +97,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0; + const struct line6_properties *properties = line6->properties;
for (i = 0; i < size; i += line6->max_packet_size) { int partial; @@ -97,15 +105,21 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int frag_size = min(line6->max_packet_size, size - i); int retval;
- retval = usb_interrupt_msg(line6->usbdev, - usb_sndintpipe(line6->usbdev, - line6->properties->ep_ctrl_w), - (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + } else { + retval = usb_bulk_msg(line6->usbdev, + usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + }
if (retval) { dev_err(line6->ifcdev, - "usb_interrupt_msg failed (%d)\n", retval); + "usb_bulk_msg failed (%d)\n", retval); break; }
@@ -140,10 +154,17 @@ static int line6_send_raw_message_async_part(struct message *msg, int done = msg->done; int bytes = min(msg->size - done, line6->max_packet_size);
- usb_fill_int_urb(urb, line6->usbdev, - usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), - (char *)msg->buffer + done, bytes, - line6_async_request_sent, msg, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + } else { + usb_fill_bulk_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg); + }
msg->done += bytes; retval = usb_submit_urb(urb, GFP_ATOMIC); @@ -462,7 +483,18 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; + const struct line6_properties *properties = line6->properties; + int pipe; + struct usb_host_endpoint *ep; + + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + pipe = + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } else { + pipe = + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } + ep = usbdev->ep_in[usb_pipeendpoint(pipe)];
if (ep) { line6->interval = ep->desc.bInterval; @@ -483,7 +515,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control(struct usb_line6 *line6) +static int line6_init_cap_control_midi(struct usb_line6 *line6) { int ret;
@@ -573,8 +605,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL) { - ret = line6_init_cap_control(line6); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + ret = line6_init_cap_control_midi(line6); if (ret < 0) goto error; } @@ -644,7 +676,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -663,7 +695,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 0bcab38..d48c7d2 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -104,6 +104,8 @@ enum { LINE6_CAP_HWMON = 1 << 2, /* device requires output data when input is read */ LINE6_CAP_IN_NEEDS_OUT = 1 << 3, + /* device uses raw MIDI via USB (data endpoints) */ + LINE6_CAP_CONTROL_MIDI = 1 << 4, };
/* @@ -142,10 +144,10 @@ struct usb_line6 { /* Line 6 MIDI device data structure */ struct snd_line6_midi *line6midi;
- /* URB for listening to PODxt Pro control endpoint */ + /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to PODxt Pro control endpoint */ + /* Buffer for listening to POD data endpoint */ unsigned char *buffer_listen;
/* Buffer for message to be processed */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 36e7274..17aa616 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -475,6 +475,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxt", .name = "BassPODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -487,6 +488,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtLive", .name = "BassPODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -499,6 +501,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtPro", .name = "BassPODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -510,7 +513,8 @@ static const struct line6_properties pod_properties_table[] = { [LINE6_POCKETPOD] = { .id = "PocketPOD", .name = "Pocket POD", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 0, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x02, @@ -520,6 +524,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxt", .name = "PODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -532,6 +537,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtLive", .name = "PODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -544,6 +550,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtPro", .name = "PODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c index ddc23dd..0c4512d 100644 --- a/sound/usb/line6/variax.c +++ b/sound/usb/line6/variax.c @@ -259,7 +259,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_PODXTLIVE_VARIAX] = { .id = "PODxtLive", .name = "PODxt Live", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x86, .ep_ctrl_w = 0x05, @@ -269,7 +270,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_VARIAX] = { .id = "Variax", .name = "Variax Workbench", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x01,
Not all PODs use MIDI via USB data interface, thus allow avoiding that code and instead using direct processing.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 59 ++++++++++++++++++++++++++++-------------------- sound/usb/line6/driver.h | 8 ++++--- sound/usb/line6/midi.c | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9b16777..853a143 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -290,26 +290,31 @@ static void line6_data_received(struct urb *urb) if (urb->status == -ESHUTDOWN) return;
- done = - line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (done < urb->actual_length) { - line6_midibuf_ignore(mb, done); - dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", - done, urb->actual_length); - } + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + }
- for (;;) { - done = - line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MESSAGE_MAXLEN);
- if (done == 0) - break; + if (done == 0) + break;
- line6->message_length = done; - line6_midi_receive(line6, line6->buffer_message, done); + line6->message_length = done; + line6_midi_receive(line6, line6->buffer_message, done);
+ if (line6->process_message) + line6->process_message(line6); + } + } else { if (line6->process_message) line6->process_message(line6); } @@ -469,7 +474,9 @@ static void line6_destruct(struct snd_card *card) struct usb_device *usbdev = line6->usbdev;
/* free buffer memory first: */ - kfree(line6->buffer_message); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + kfree(line6->buffer_message); + kfree(line6->buffer_listen);
/* then free URBs: */ @@ -515,7 +522,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control_midi(struct usb_line6 *line6) +static int line6_init_cap_control(struct usb_line6 *line6) { int ret;
@@ -524,14 +531,16 @@ static int line6_init_cap_control_midi(struct usb_line6 *line6) if (!line6->buffer_listen) return -ENOMEM;
- line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); - if (!line6->buffer_message) - return -ENOMEM; - line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); if (!line6->urb_listen) return -ENOMEM;
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + if (!line6->buffer_message) + return -ENOMEM; + } + ret = line6_start_listen(line6); if (ret < 0) { dev_err(line6->ifcdev, "cannot start listening: %d\n", ret); @@ -605,8 +614,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - ret = line6_init_cap_control_midi(line6); + if (properties->capabilities & LINE6_CAP_CONTROL) { + ret = line6_init_cap_control(line6); if (ret < 0) goto error; } @@ -676,7 +685,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -695,7 +704,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index d48c7d2..d7eefe1 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -147,15 +147,17 @@ struct usb_line6 { /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to POD data endpoint */ + /* Buffer for incoming data from POD data endpoint */ unsigned char *buffer_listen;
- /* Buffer for message to be processed */ + /* Buffer for message to be processed, generated from MIDI layer */ unsigned char *buffer_message;
- /* Length of message to be processed */ + /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* If MIDI is supported, buffer_message contains the pre-processed data; + * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *); void (*disconnect)(struct usb_line6 *line6); }; diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c index cebea9b..d0fb2f2 100644 --- a/sound/usb/line6/midi.c +++ b/sound/usb/line6/midi.c @@ -258,7 +258,7 @@ int line6_init_midi(struct usb_line6 *line6) struct snd_rawmidi *rmidi; struct snd_line6_midi *line6midi;
- if (!(line6->properties->capabilities & LINE6_CAP_CONTROL)) { + if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { /* skip MIDI initialization and report success */ return 0; }
This includes audio in/out and basic initialization via control EP (emulates what original driver does). The initialization is done similarly to original POD, firmware and serial IDs are read and exported via sysfs.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/podhd.c | 276 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 265 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig index f4585d37..8ffcf48 100644 --- a/sound/usb/line6/Kconfig +++ b/sound/usb/line6/Kconfig @@ -21,10 +21,10 @@ config SND_USB_POD re-amping)
config SND_USB_PODHD - tristate "Line 6 POD HD300/400/500 USB support" + tristate "Line 6 POD X3/HD300/400/500 USB support" select SND_USB_LINE6 help - This is a driver for POD HD300, 400 and 500 devices. + This is a driver for POD X3, HD300, 400 and 500 devices.
config SND_USB_TONEPORT tristate "TonePort GX, UX1 and UX2 USB support" diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 4fc4789..8cf0a98 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -2,6 +2,7 @@ * Line 6 Pod HD * * Copyright (C) 2011 Stefan Hajnoczi stefanha@gmail.com + * Copyright (C) 2015 Andrej Krutak dev@andree.sk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,11 +19,44 @@ #include "driver.h" #include "pcm.h"
+#define PODHD_STARTUP_DELAY 500 + +/* + * Stages of POD startup procedure + */ +enum { + PODHD_STARTUP_INIT = 1, + PODHD_STARTUP_SCHEDULE_WORKQUEUE, + PODHD_STARTUP_SETUP, + PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 +}; + enum { LINE6_PODHD300, LINE6_PODHD400, LINE6_PODHD500_0, LINE6_PODHD500_1, + LINE6_PODX3, +}; + +struct usb_line6_podhd { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version */ + int firmware_version; };
static struct snd_ratden podhd_ratden = { @@ -71,9 +105,196 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_channel = 3 /* 24bit audio (stereo) */ + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
+static struct line6_pcm_properties podx3_pcm_properties = { + .playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + /* 1+2: Main signal (out), 3+4: Tone 1, + * 5+6: Tone 2, 7+8: raw */ + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static void podhd_startup_start_workqueue(unsigned long data); +static void podhd_startup_workqueue(struct work_struct *work); +static int podhd_startup_finalize(struct usb_line6_podhd *pod); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%u\n", pod->serial_number); +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%06x\n", pod->firmware_version); +} + +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *podhd_dev_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group podhd_dev_attr_group = { + .name = "podhd", + .attrs = podhd_dev_attrs, +}; + +/* + * POD X3 startup procedure. + * + * May be compatible with other POD HD's, since it's also similar to the + * previous POD setup. In any case, it doesn't seem to be required for the + * audio nor bulk interfaces to work. + */ + +static void podhd_startup(struct usb_line6_podhd *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, + podhd_startup_start_workqueue, (unsigned long)pod); +} + +static void podhd_startup_start_workqueue(unsigned long data) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)data; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, + PODHD_STARTUP_SCHEDULE_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static int podhd_dev_start(struct usb_line6_podhd *pod) +{ + int ret; + u8 init_bytes[8]; + int i; + struct usb_device *usbdev = pod->line6.usbdev; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x11, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* NOTE: looks like some kind of ping message */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x11, 0x0, + &init_bytes, 3, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, + "receive length failed (error %d)\n", ret); + return ret; + } + + pod->firmware_version = + (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); + + for (i = 0; i <= 16; i++) { + ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); + if (ret < 0) + return ret; + } + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, + 1, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void podhd_startup_workqueue(struct work_struct *work) +{ + struct usb_line6_podhd *pod = + container_of(work, struct usb_line6_podhd, startup_work); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + + podhd_dev_start(pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + + podhd_startup_finalize(pod); +} + +static int podhd_startup_finalize(struct usb_line6_podhd *pod) +{ + struct usb_line6 *line6 = &pod->line6; + + /* ALSA audio interface: */ + return snd_card_register(line6->card); +} + +static void podhd_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + } +} + /* Try to init POD HD device. */ @@ -81,6 +302,16 @@ static int podhd_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; + struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + + line6->disconnect = podhd_disconnect; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); + if (err < 0) + return err; + }
/* initialize MIDI subsystem: */ err = line6_init_midi(line6); @@ -88,12 +319,22 @@ static int podhd_init(struct usb_line6 *line6, return err;
/* initialize PCM subsystem: */ - err = line6_init_pcm(line6, &podhd_pcm_properties); + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); if (err < 0) return err;
- /* register USB audio system: */ - return snd_card_register(line6->card); + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { + /* register USB audio system directly */ + return podhd_startup_finalize(pod); + } + + /* init device and delay registering */ + init_timer(&pod->startup_timer); + INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + podhd_startup(pod); + return 0; }
#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) @@ -101,10 +342,12 @@ static int podhd_init(struct usb_line6 *line6,
/* table of devices that work with this driver */ static const struct usb_device_id podhd_id_table[] = { + /* TODO: no need to alloc data interfaces when only audio is used */ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, {} };
@@ -114,8 +357,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD300] = { .id = "PODHD300", .name = "POD HD300", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -126,8 +368,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD400] = { .id = "PODHD400", .name = "POD HD400", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -138,8 +379,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_0] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -150,8 +390,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_1] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -159,6 +398,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3] = { + .id = "PODX3", + .name = "POD X3", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/* @@ -169,7 +419,7 @@ static int podhd_probe(struct usb_interface *interface, { return line6_probe(interface, id, "Line6-PODHD", &podhd_properties_table[id->driver_info], - podhd_init, sizeof(struct usb_line6)); + podhd_init, sizeof(struct usb_line6_podhd)); }
static struct usb_driver podhd_driver = {
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8cf0a98..8246ea5 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -37,6 +37,7 @@ enum { LINE6_PODHD500_0, LINE6_PODHD500_1, LINE6_PODX3, + LINE6_PODX3LIVE };
struct usb_line6_podhd { @@ -348,6 +349,7 @@ static const struct usb_device_id podhd_id_table[] = { { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, + { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, {} };
@@ -409,6 +411,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3LIVE] = { + .id = "PODX3LIVE", + .name = "POD X3 LIVE", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/*
Not all line6 devices use the control port.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 853a143..8a71d45 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -612,9 +612,8 @@ int line6_probe(struct usb_interface *interface, goto error; }
- line6_get_interval(line6); - if (properties->capabilities & LINE6_CAP_CONTROL) { + line6_get_interval(line6); ret = line6_init_cap_control(line6); if (ret < 0) goto error;
Only initialize PCM for POD HD devices that support it. No POD HD seems to support MIDI, thus drop the initialization.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8246ea5..193eb29 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -314,17 +314,14 @@ static int podhd_init(struct usb_line6 *line6, return err; }
- /* initialize MIDI subsystem: */ - err = line6_init_midi(line6); - if (err < 0) - return err; - - /* initialize PCM subsystem: */ - err = line6_init_pcm(line6, - (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : - &podhd_pcm_properties); - if (err < 0) - return err; + if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); + if (err < 0) + return err; + }
if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { /* register USB audio system directly */
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@andree.sk --- include/uapi/sound/asound.h | 3 +- sound/usb/line6/driver.c | 151 ++++++++++++++++++++++++++++++++++++++++++-- sound/usb/line6/driver.h | 23 ++++++- 3 files changed, 170 insertions(+), 7 deletions(-)
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..3536efe 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -17,6 +17,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/hwdep.h>
#include "capture.h" #include "driver.h" @@ -303,7 +304,7 @@ static void line6_data_received(struct urb *urb) for (;;) { done = line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + LINE6_MIDI_MESSAGE_MAXLEN);
if (done == 0) break; @@ -315,8 +316,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); @@ -473,14 +477,16 @@ static void line6_destruct(struct snd_card *card) struct usb_line6 *line6 = card->private_data; struct usb_device *usbdev = line6->usbdev;
- /* free buffer memory first: */ - if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + /* Free buffer memory first. We cannot depend on the existence of private + * data from the (podhd) module, it may be gone already during this call */ + if (line6->buffer_message) kfree(line6->buffer_message);
kfree(line6->buffer_listen);
/* then free URBs: */ usb_free_urb(line6->urb_listen); + line6->urb_listen = NULL;
/* decrement reference counters: */ usb_put_dev(usbdev); @@ -522,6 +528,138 @@ 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 layer provides atomicity here */ + + line6->messages.active = 1; + + return 0; +} + +/* Stop buffering */ +static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_line6 *line6 = hw->private_data; + + line6->messages.active = 0; + + 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; + long rv = 0; + unsigned int out_count; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + + while (kfifo_len(&line6->messages.fifo) == 0) { + mutex_unlock(&line6->messages.read_lock); + + rv = wait_event_interruptible( + line6->messages.wait_queue, + kfifo_len(&line6->messages.fifo) != 0); + if (rv < 0) + return rv; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + } + + if (kfifo_peek_len(&line6->messages.fifo) > count) { + /* Buffer too small; allow re-read of the current item... */ + rv = -EINVAL; + } else { + rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count); + if (rv == 0) + rv = out_count; + } + + mutex_unlock(&line6->messages.read_lock); + return rv; +} + +/* 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; + + if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) { + /* This is an arbitrary limit - still better than nothing... */ + return -EINVAL; + } + + data_copy = memdup_user(data, count); + if (IS_ERR(ERR_PTR)) + return -ENOMEM; + + 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) +{ + if (!line6->messages.active) + return; + + if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) { + /* No race condition here, there's only one writer */ + kfifo_in(&line6->messages.fifo, + line6->buffer_message, line6->message_length); + } /* else TODO: signal overflow */ + + wake_up_interruptible(&line6->messages.wait_queue); +} + +static int line6_hwdep_init(struct usb_line6 *line6) +{ + int err; + struct snd_hwdep *hwdep; + + /* TODO: usb_driver_claim_interface(); */ + line6->process_message = line6_hwdep_push_message; + line6->messages.active = 0; + init_waitqueue_head(&line6->messages.wait_queue); + mutex_init(&line6->messages.read_lock); + INIT_KFIFO(line6->messages.fifo); + + err = snd_hwdep_new(line6->card, "config", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "config"); + hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; + hwdep->ops = hwdep_ops; + hwdep->private_data = line6; + hwdep->exclusive = true; + +end: + return err; +} + static int line6_init_cap_control(struct usb_line6 *line6) { int ret; @@ -536,9 +674,13 @@ static int line6_init_cap_control(struct usb_line6 *line6) return -ENOMEM;
if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); if (!line6->buffer_message) return -ENOMEM; + } else { + ret = line6_hwdep_init(line6); + if (ret < 0) + return ret; }
ret = line6_start_listen(line6); @@ -716,3 +858,4 @@ EXPORT_SYMBOL_GPL(line6_resume); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); + diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index d7eefe1..cf9f077 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -12,8 +12,9 @@ #ifndef DRIVER_H #define DRIVER_H
-#include <linux/spinlock.h> #include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/kfifo.h> #include <sound/core.h>
#include "midi.h" @@ -32,7 +33,16 @@
#define LINE6_TIMEOUT 1 #define LINE6_BUFSIZE_LISTEN 64 -#define LINE6_MESSAGE_MAXLEN 256 +#define LINE6_MIDI_MESSAGE_MAXLEN 256 + +#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7 +/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */ +#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER) + + +#if LINE6_BUFSIZE_LISTEN > 65535 +#error "Use dynamic fifo instead" +#endif
/* Line 6 MIDI control commands @@ -156,6 +166,15 @@ struct usb_line6 { /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* Circular buffer for non-MIDI control messages */ + struct { + struct mutex read_lock; + wait_queue_head_t wait_queue; + int active:1; + STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT) + fifo; + } messages; + /* If MIDI is supported, buffer_message contains the pre-processed data; * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *);
Hello all,
attached are the yet again reworked patches. Basically the diff is only some formatting. In addition, some of the previously big patches were split to multiple smaller ones. Compared to v3, there is a small bisectability/patch sanity fix.
I have checked that after each one, the compilation works, but not that the result doesn't segfault or even work - I don't have the hardware previously supported by the driver and the most interesting changes are only useful as a whole for POD X3.
Greetings!
Andrej Krutak (12): ALSA: line6: Enable different number of URBs for frame transfers ALSA: line6: Add high-speed USB support ALSA: line6: Support assymetrical in/out configurations ALSA: line6: Allow different channel numbers for in/out ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture ALSA: line6: Distinguish device init (ctrl EP) and MIDI data transfer (int EP) ALSA: line6: Allow processing of raw incoming messages ALSA: line6: Add support for POD X3 ALSA: line6: Add support for POD X3 Live (only USB ID differs from POD X3) ALSA: line6: Only determine control port properties if needed ALSA: line6: Cleanup podhd initialization ALSA: line6: Add hwdep interface to access the POD control messages
include/uapi/sound/asound.h | 3 +- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/capture.c | 50 +++++--- sound/usb/line6/driver.c | 269 +++++++++++++++++++++++++++++++++------ sound/usb/line6/driver.h | 58 +++++++-- sound/usb/line6/midi.c | 2 +- sound/usb/line6/pcm.c | 83 ++++++++---- sound/usb/line6/pcm.h | 19 +-- sound/usb/line6/playback.c | 37 ++++-- sound/usb/line6/pod.c | 12 +- sound/usb/line6/podhd.c | 300 ++++++++++++++++++++++++++++++++++++++++---- sound/usb/line6/toneport.c | 6 +- sound/usb/line6/variax.c | 6 +- 13 files changed, 703 insertions(+), 146 deletions(-)
This basically changes LINE6_ISO_BUFFERS constant to a configurable iso_buffers property.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 17 +++++++++++------ sound/usb/line6/driver.c | 1 + sound/usb/line6/driver.h | 2 ++ sound/usb/line6/pcm.c | 23 +++++++++++++++-------- sound/usb/line6/pcm.h | 2 +- sound/usb/line6/playback.c | 19 ++++++++++++------- 6 files changed, 42 insertions(+), 22 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index f518fbb..e20a6bd 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -29,10 +29,10 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) int ret; struct urb *urb_in;
- index = - find_first_zero_bit(&line6pcm->in.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit(&line6pcm->in.active_urbs, + line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -73,7 +73,7 @@ int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_in_urb(line6pcm); if (ret < 0) break; @@ -154,7 +154,7 @@ static void audio_in_callback(struct urb *urb) line6pcm->in.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) if (urb == line6pcm->in.urbs[index]) break;
@@ -247,8 +247,13 @@ int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->in.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio in: */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 81b7da8..527c408 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -467,6 +467,7 @@ static void line6_get_interval(struct usb_line6 *line6) unsigned epnum = usb_pipeendpoint(pipe);
ep = usbdev->ep_in[epnum]; + line6->iso_buffers = LINE6_ISO_BUFFERS; if (ep) { line6->interval = ep->desc.bInterval; line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 7da643e..43dd1d0 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -111,6 +111,8 @@ struct usb_line6 {
/* Interval (ms) */ int interval; + /* Number of isochronous URBs used for frame transfers */ + int iso_buffers;
/* Maximum size of USB packet */ int max_packet_size; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 41aa335..6c9b7cf 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -104,7 +104,7 @@ static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) { if (!test_and_set_bit(i, &pcms->unlink_urbs)) usb_unlink_urb(pcms->urbs[i]); @@ -124,7 +124,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm,
do { alive = 0; - for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { if (test_bit(i, &pcms->active_urbs)) alive++; } @@ -153,8 +153,9 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, { /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { - pstr->buffer = kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * + LINE6_ISO_PACKETS * + line6pcm->max_packet_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -434,24 +435,30 @@ static struct snd_kcontrol_new line6_controls[] = { /* Cleanup the PCM device. */ -static void cleanup_urbs(struct line6_pcm_stream *pcms) +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) { int i;
- for (i = 0; i < LINE6_ISO_BUFFERS; i++) { + /* Most likely impossible in current code... */ + if (pcms->urbs == NULL) + return; + + for (i = 0; i < iso_buffers; i++) { if (pcms->urbs[i]) { usb_kill_urb(pcms->urbs[i]); usb_free_urb(pcms->urbs[i]); } } + kfree(pcms->urbs); + pcms->urbs = NULL; }
static void line6_cleanup_pcm(struct snd_pcm *pcm) { struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm);
- cleanup_urbs(&line6pcm->out); - cleanup_urbs(&line6pcm->in); + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); kfree(line6pcm); }
diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 508410a..e983880 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -90,7 +90,7 @@ struct line6_pcm_properties {
struct line6_pcm_stream { /* allocated URBs */ - struct urb *urbs[LINE6_ISO_BUFFERS]; + struct urb **urbs;
/* Temporary buffer; * Since the packet size is not known in advance, this buffer is diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 97ed593..7a52806 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -154,10 +154,10 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); struct urb *urb_out;
- index = - find_first_zero_bit(&line6pcm->out.active_urbs, LINE6_ISO_BUFFERS); + index = find_first_zero_bit(&line6pcm->out.active_urbs, + line6pcm->line6->iso_buffers);
- if (index < 0 || index >= LINE6_ISO_BUFFERS) { + if (index < 0 || index >= line6pcm->line6->iso_buffers) { dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); return -EINVAL; } @@ -286,7 +286,7 @@ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { int ret = 0, i;
- for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) break; @@ -313,11 +313,11 @@ static void audio_out_callback(struct urb *urb) line6pcm->out.last_frame = urb->start_frame;
/* find index of URB */ - for (index = 0; index < LINE6_ISO_BUFFERS; index++) + for (index = 0; index < line6pcm->line6->iso_buffers; index++) if (urb == line6pcm->out.urbs[index]) break;
- if (index >= LINE6_ISO_BUFFERS) + if (index >= line6pcm->line6->iso_buffers) return; /* URB has been unlinked asynchronously */
for (i = 0; i < LINE6_ISO_PACKETS; i++) @@ -401,8 +401,13 @@ int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) struct usb_line6 *line6 = line6pcm->line6; int i;
+ line6pcm->out.urbs = kzalloc( + sizeof(struct urb *) * line6->iso_buffers, GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + /* create audio URBs and fill in constant values: */ - for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + for (i = 0; i < line6->iso_buffers; ++i) { struct urb *urb;
/* URB for audio out: */
This has two parts: * intervals_per_second setup (high speed needs 8000, instead of 1000) * iso_buffers setup (count of iso buffers depends on USB speed, 2 is not enough for high speed)
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 10 +++++++++- sound/usb/line6/driver.c | 15 ++++++++++----- sound/usb/line6/driver.h | 15 ++++++++++++--- sound/usb/line6/pcm.h | 6 ++---- sound/usb/line6/playback.c | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index e20a6bd..44e9a8c 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -181,7 +181,15 @@ static void audio_in_callback(struct urb *urb)
length += fsize;
- /* the following assumes LINE6_ISO_PACKETS == 1: */ + BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1, + "The following code assumes LINE6_ISO_PACKETS == 1"); + /* TODO: + * Also, if iso_buffers != 2, the prev frame is almost random at + * playback side. + * This needs to be redesigned. It should be "stable", but we may + * experience sync problems on such high-speed configs. + */ + line6pcm->prev_fbuf = fbuf; line6pcm->prev_fsize = fsize;
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 527c408..14032d9 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -462,14 +462,18 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep; - unsigned pipe = usb_rcvintpipe(usbdev, line6->properties->ep_ctrl_r); - unsigned epnum = usb_pipeendpoint(pipe); + struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r];
- ep = usbdev->ep_in[epnum]; - line6->iso_buffers = LINE6_ISO_BUFFERS; if (ep) { line6->interval = ep->desc.bInterval; + if (usbdev->speed == USB_SPEED_LOW) { + line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_LOW_ISO_BUFFERS; + } else { + line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_HIGH_ISO_BUFFERS; + } + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); } else { dev_err(line6->ifcdev, @@ -559,6 +563,7 @@ int line6_probe(struct usb_interface *interface, /* query interface number */ interface_number = interface->cur_altsetting->desc.bInterfaceNumber;
+ /* TODO reserves the bus bandwidth even without actual transfer */ ret = usb_set_interface(usbdev, interface_number, properties->altsetting); if (ret < 0) { diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 43dd1d0..a55eb88 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -18,7 +18,13 @@
#include "midi.h"
-#define USB_INTERVALS_PER_SECOND 1000 +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND 1000 +#define USB_LOW_ISO_BUFFERS 2 + +/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND 8000 +#define USB_HIGH_ISO_BUFFERS 16
/* Fallback USB interval and max packet size values */ #define LINE6_FALLBACK_INTERVAL 10 @@ -109,12 +115,15 @@ struct usb_line6 { /* Properties */ const struct line6_properties *properties;
- /* Interval (ms) */ + /* Interval for data USB packets */ int interval; + /* ...for isochronous transfers framing */ + int intervals_per_second; + /* Number of isochronous URBs used for frame transfers */ int iso_buffers;
- /* Maximum size of USB packet */ + /* Maximum size of data USB packet */ int max_packet_size;
/* Device representing the USB interface */ diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index e983880..38bf4df 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -20,9 +20,6 @@
#include "driver.h"
-/* number of URBs */ -#define LINE6_ISO_BUFFERS 2 - /* number of USB frames per URB The Line 6 Windows driver always transmits two frames per packet, but @@ -31,7 +28,8 @@ */ #define LINE6_ISO_PACKETS 1
-/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +/* in a "full speed" device (such as the PODxt Pro) this means 1ms, + for "high speed" it's 1/8ms */ #define LINE6_ISO_INTERVAL 1
#define LINE6_IMPULSE_DEFAULT_PERIOD 100 diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7a52806..d53aad1 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -151,7 +151,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->properties->rates.rats[0].num_min; const int frame_factor = line6pcm->properties->rates.rats[0].den * - (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out;
index = find_first_zero_bit(&line6pcm->out.active_urbs,
Splits max_packet_size to max_packet_size_in/out (e.g. for different channel counts).
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 10 +++++----- sound/usb/line6/pcm.c | 33 +++++++++++++++++++-------------- sound/usb/line6/pcm.h | 5 +++-- sound/usb/line6/playback.c | 2 +- 4 files changed, 28 insertions(+), 22 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 44e9a8c..73cea26 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -44,13 +44,13 @@ static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) struct usb_iso_packet_descriptor *fin = &urb_in->iso_frame_desc[i]; fin->offset = urb_size; - fin->length = line6pcm->max_packet_size; - urb_size += line6pcm->max_packet_size; + fin->length = line6pcm->max_packet_size_in; + urb_size += line6pcm->max_packet_size_in; }
urb_in->transfer_buffer = line6pcm->in.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; urb_in->transfer_buffer_length = urb_size; urb_in->context = line6pcm;
@@ -173,10 +173,10 @@ static void audio_in_callback(struct urb *urb) fbuf = urb->transfer_buffer + fin->offset; fsize = fin->actual_length;
- if (fsize > line6pcm->max_packet_size) { + if (fsize > line6pcm->max_packet_size_in) { dev_err(line6pcm->line6->ifcdev, "driver and/or device bug: packet too large (%d > %d)\n", - fsize, line6pcm->max_packet_size); + fsize, line6pcm->max_packet_size_in); }
length += fsize; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 6c9b7cf..31b68fa 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -146,16 +146,20 @@ get_stream(struct snd_line6_pcm *line6pcm, int direction) }
/* allocate a buffer if not opened yet; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, - struct line6_pcm_stream *pstr, int type) + struct line6_pcm_stream *pstr, int direction, int type) { + const int pkt_size = + (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + line6pcm->max_packet_size_out : + line6pcm->max_packet_size_in; + /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = kmalloc(line6pcm->line6->iso_buffers * - LINE6_ISO_PACKETS * - line6pcm->max_packet_size, GFP_KERNEL); + LINE6_ISO_PACKETS * pkt_size, GFP_KERNEL); if (!pstr->buffer) return -ENOMEM; } @@ -163,12 +167,11 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, }
/* free a buffer if all streams are closed; - * call this in line6pcm.state_change mutex + * call this in line6pcm.state_mutex */ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, struct line6_pcm_stream *pstr, int type) { - clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); @@ -195,6 +198,7 @@ static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, else ret = line6_submit_audio_in_all_urbs(line6pcm); } + if (ret < 0) clear_bit(type, &pstr->running); spin_unlock_irqrestore(&pstr->lock, flags); @@ -290,7 +294,7 @@ int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) mutex_lock(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); - ret = line6_buffer_acquire(line6pcm, pstr, type); + ret = line6_buffer_acquire(line6pcm, pstr, dir, type); if (ret < 0) goto error; if (!pstr->running) @@ -335,7 +339,8 @@ int snd_line6_hw_params(struct snd_pcm_substream *substream, struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream);
mutex_lock(&line6pcm->state_mutex); - ret = line6_buffer_acquire(line6pcm, pstr, LINE6_STREAM_PCM); + ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, + LINE6_STREAM_PCM); if (ret < 0) goto error;
@@ -530,12 +535,12 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->volume_monitor = 255; line6pcm->line6 = line6;
- /* Read and write buffers are sized identically, so choose minimum */ - line6pcm->max_packet_size = min( - usb_maxpacket(line6->usbdev, - usb_rcvisocpipe(line6->usbdev, ep_read), 0), - usb_maxpacket(line6->usbdev, - usb_sndisocpipe(line6->usbdev, ep_write), 1)); + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1);
spin_lock_init(&line6pcm->out.lock); spin_lock_init(&line6pcm->in.lock); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 38bf4df..f408d15 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -155,11 +155,12 @@ struct snd_line6_pcm { /* Previously captured frame (for software monitoring) */ unsigned char *prev_fbuf;
- /* Size of previously captured frame (for software monitoring) */ + /* Size of previously captured frame (for software monitoring/sync) */ int prev_fsize;
/* Maximum size of USB packet */ - int max_packet_size; + int max_packet_size_in; + int max_packet_size_out;
/* PCM playback volume (left and right) */ int volume_playback[2]; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index d53aad1..7b2644f 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -195,7 +195,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_frames = urb_size / bytes_per_frame; urb_out->transfer_buffer = line6pcm->out.buffer + - index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; urb_out->transfer_buffer_length = urb_size; urb_out->context = line6pcm;
Changes bytes_per_frame to bytes_per_channel.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 8 ++++++-- sound/usb/line6/driver.h | 2 +- sound/usb/line6/pcm.h | 2 +- sound/usb/line6/playback.c | 14 +++++++++++--- sound/usb/line6/pod.c | 3 +-- sound/usb/line6/podhd.c | 4 +--- sound/usb/line6/toneport.c | 2 +- 7 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 73cea26..7959aaa 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -90,7 +90,9 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); struct snd_pcm_runtime *runtime = substream->runtime; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max; int frames = fsize / bytes_per_frame;
if (runtime == NULL) @@ -191,7 +193,9 @@ static void audio_in_callback(struct urb *urb) */
line6pcm->prev_fbuf = fbuf; - line6pcm->prev_fsize = fsize; + line6pcm->prev_fsize = fsize / + (line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max);
if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) && diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index a55eb88..2d32139 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -31,7 +31,7 @@ #define LINE6_FALLBACK_MAXPACKETSIZE 16
#define LINE6_TIMEOUT 1 -#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MESSAGE_MAXLEN 256
/* diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index f408d15..58d36f9 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -83,7 +83,7 @@ enum { struct line6_pcm_properties { struct snd_pcm_hardware playback_hw, capture_hw; struct snd_pcm_hw_constraint_ratdens rates; - int bytes_per_frame; + int bytes_per_channel; };
struct line6_pcm_stream { diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7b2644f..bc5799c 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -146,7 +146,9 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) int index; int i, urb_size, urb_frames; int ret; - const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; const int frame_increment = line6pcm->properties->rates.rats[0].num_min; const int frame_factor = @@ -165,6 +167,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out = line6pcm->out.urbs[index]; urb_size = 0;
+ /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */ for (i = 0; i < LINE6_ISO_PACKETS; ++i) { /* compute frame size for given sampling rate */ int fsize = 0; @@ -178,9 +181,11 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->out.count += frame_increment; n = line6pcm->out.count / frame_factor; line6pcm->out.count -= n * frame_factor; - fsize = n * bytes_per_frame; + fsize = n; }
+ fsize *= bytes_per_frame; + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; @@ -305,6 +310,9 @@ static void audio_out_callback(struct urb *urb) struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; struct snd_pcm_substream *substream = get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max;
#if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -329,7 +337,7 @@ static void audio_out_callback(struct urb *urb) struct snd_pcm_runtime *runtime = substream->runtime;
line6pcm->out.pos_done += - length / line6pcm->properties->bytes_per_frame; + length / bytes_per_frame;
if (line6pcm->out.pos_done >= runtime->buffer_size) line6pcm->out.pos_done -= runtime->buffer_size; diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 45dd348..36e7274 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -83,7 +83,6 @@ struct usb_line6_pod { };
#define POD_SYSEX_CODE 3 -#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */
/* *INDENT-OFF* */
@@ -167,7 +166,7 @@ static struct line6_pcm_properties pod_pcm_properties = { .rates = { .nrats = 1, .rats = &pod_ratden}, - .bytes_per_frame = POD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
static const char pod_version_header[] = { diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 63dcaef..4fc4789 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -25,8 +25,6 @@ enum { LINE6_PODHD500_1, };
-#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ - static struct snd_ratden podhd_ratden = { .num_min = 48000, .num_max = 48000, @@ -73,7 +71,7 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_frame = PODHD_BYTES_PER_FRAME + .bytes_per_channel = 3 /* 24bit audio (stereo) */ };
/* diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 6d4c50c..da76e03 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -114,7 +114,7 @@ static struct line6_pcm_properties toneport_pcm_properties = { .rates = { .nrats = 1, .rats = &toneport_ratden}, - .bytes_per_frame = 4 + .bytes_per_channel = 2 };
static const struct {
E.g. POD X3 seems to require playback data to be sent to it to generate capture data. Otherwise the device stalls and doesn't send any more capture data until it's reset.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/capture.c | 5 +++++ sound/usb/line6/driver.h | 4 +++- sound/usb/line6/pcm.c | 31 ++++++++++++++++++++++++------- sound/usb/line6/pcm.h | 4 +++- sound/usb/line6/toneport.c | 4 ++-- 5 files changed, 37 insertions(+), 11 deletions(-)
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 7959aaa..bf159c4 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -232,6 +232,8 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err;
+ line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); + runtime->hw = line6pcm->properties->capture_hw; return 0; } @@ -239,6 +241,9 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) /* close capture callback */ static int snd_line6_capture_close(struct snd_pcm_substream *substream) { + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER); return 0; }
diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 2d32139..0bcab38 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -100,8 +100,10 @@ enum { LINE6_CAP_CONTROL = 1 << 0, /* device supports PCM input/output via USB */ LINE6_CAP_PCM = 1 << 1, - /* device support hardware monitoring */ + /* device supports hardware monitoring */ LINE6_CAP_HWMON = 1 << 2, + /* device requires output data when input is read */ + LINE6_CAP_IN_NEEDS_OUT = 1 << 3, };
/* diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 31b68fa..f0f3ff5 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -52,7 +52,7 @@ static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol,
line6pcm->impulse_volume = value; if (value > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); if (err < 0) { line6pcm->impulse_volume = 0; return err; @@ -242,6 +242,14 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + if (err < 0) + return err; + } err = line6_stream_start(line6pcm, s->stream, LINE6_STREAM_PCM); if (err < 0) @@ -250,6 +258,12 @@ int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + } line6_stream_stop(line6pcm, s->stream, LINE6_STREAM_PCM); break; @@ -283,14 +297,15 @@ snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) return pstr->pos_done; }
-/* Acquire and start duplex streams: +/* Acquire and optionally start duplex streams: * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR */ -int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) { struct line6_pcm_stream *pstr; int ret = 0, dir;
+ /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ mutex_lock(&line6pcm->state_mutex); for (dir = 0; dir < 2; dir++) { pstr = get_stream(line6pcm, dir); @@ -300,10 +315,12 @@ int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type) if (!pstr->running) line6_wait_clear_audio_urbs(line6pcm, pstr); } - for (dir = 0; dir < 2; dir++) { - ret = line6_stream_start(line6pcm, dir, type); - if (ret < 0) - goto error; + if (start) { + for (dir = 0; dir < 2; dir++) { + ret = line6_stream_start(line6pcm, dir, type); + if (ret < 0) + goto error; + } } error: mutex_unlock(&line6pcm->state_mutex); diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h index 58d36f9..5f796ef8 100644 --- a/sound/usb/line6/pcm.h +++ b/sound/usb/line6/pcm.h @@ -72,6 +72,7 @@ enum { LINE6_STREAM_PCM, LINE6_STREAM_MONITOR, LINE6_STREAM_IMPULSE, + LINE6_STREAM_CAPTURE_HELPER, };
/* misc bit flags for PCM operation */ @@ -190,7 +191,8 @@ extern int snd_line6_hw_params(struct snd_pcm_substream *substream, extern int snd_line6_hw_free(struct snd_pcm_substream *substream); extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream); extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); -extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, + bool start); extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type);
#endif diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index da76e03..8e22f43 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -177,7 +177,7 @@ static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, line6pcm->volume_monitor = ucontrol->value.integer.value[0];
if (line6pcm->volume_monitor > 0) { - err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR); + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true); if (err < 0) { line6pcm->volume_monitor = 0; line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); @@ -246,7 +246,7 @@ static void toneport_start_pcm(unsigned long arg) struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg; struct usb_line6 *line6 = &toneport->line6;
- line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR); + line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); }
/* control definition */
POD X3 can initialize similarly to older PODs, but it doesn't have the MIDI interface. Instead, configuration is done via proprietary bulk EP messages.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 72 ++++++++++++++++++++++++++++++++++-------------- sound/usb/line6/driver.h | 6 ++-- sound/usb/line6/pod.c | 9 +++++- sound/usb/line6/variax.c | 6 ++-- 4 files changed, 68 insertions(+), 25 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 14032d9..9b16777 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -66,10 +66,17 @@ static int line6_start_listen(struct usb_line6 *line6) { int err;
- usb_fill_int_urb(line6->urb_listen, line6->usbdev, - usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), - line6->buffer_listen, LINE6_BUFSIZE_LISTEN, - line6_data_received, line6, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + } else { + usb_fill_bulk_urb(line6->urb_listen, line6->usbdev, + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6); + } line6->urb_listen->actual_length = 0; err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); return err; @@ -90,6 +97,7 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int size) { int i, done = 0; + const struct line6_properties *properties = line6->properties;
for (i = 0; i < size; i += line6->max_packet_size) { int partial; @@ -97,15 +105,21 @@ static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, int frag_size = min(line6->max_packet_size, size - i); int retval;
- retval = usb_interrupt_msg(line6->usbdev, - usb_sndintpipe(line6->usbdev, - line6->properties->ep_ctrl_w), - (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + } else { + retval = usb_bulk_msg(line6->usbdev, + usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + }
if (retval) { dev_err(line6->ifcdev, - "usb_interrupt_msg failed (%d)\n", retval); + "usb_bulk_msg failed (%d)\n", retval); break; }
@@ -140,10 +154,17 @@ static int line6_send_raw_message_async_part(struct message *msg, int done = msg->done; int bytes = min(msg->size - done, line6->max_packet_size);
- usb_fill_int_urb(urb, line6->usbdev, - usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), - (char *)msg->buffer + done, bytes, - line6_async_request_sent, msg, line6->interval); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + } else { + usb_fill_bulk_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg); + }
msg->done += bytes; retval = usb_submit_urb(urb, GFP_ATOMIC); @@ -462,7 +483,18 @@ static void line6_destruct(struct snd_card *card) static void line6_get_interval(struct usb_line6 *line6) { struct usb_device *usbdev = line6->usbdev; - struct usb_host_endpoint *ep = usbdev->ep_in[line6->properties->ep_ctrl_r]; + const struct line6_properties *properties = line6->properties; + int pipe; + struct usb_host_endpoint *ep; + + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + pipe = + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } else { + pipe = + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r); + } + ep = usbdev->ep_in[usb_pipeendpoint(pipe)];
if (ep) { line6->interval = ep->desc.bInterval; @@ -483,7 +515,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control(struct usb_line6 *line6) +static int line6_init_cap_control_midi(struct usb_line6 *line6) { int ret;
@@ -573,8 +605,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL) { - ret = line6_init_cap_control(line6); + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + ret = line6_init_cap_control_midi(line6); if (ret < 0) goto error; } @@ -644,7 +676,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -663,7 +695,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL) + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 0bcab38..d48c7d2 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -104,6 +104,8 @@ enum { LINE6_CAP_HWMON = 1 << 2, /* device requires output data when input is read */ LINE6_CAP_IN_NEEDS_OUT = 1 << 3, + /* device uses raw MIDI via USB (data endpoints) */ + LINE6_CAP_CONTROL_MIDI = 1 << 4, };
/* @@ -142,10 +144,10 @@ struct usb_line6 { /* Line 6 MIDI device data structure */ struct snd_line6_midi *line6midi;
- /* URB for listening to PODxt Pro control endpoint */ + /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to PODxt Pro control endpoint */ + /* Buffer for listening to POD data endpoint */ unsigned char *buffer_listen;
/* Buffer for message to be processed */ diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 36e7274..17aa616 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -475,6 +475,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxt", .name = "BassPODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -487,6 +488,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtLive", .name = "BassPODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -499,6 +501,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "BassPODxtPro", .name = "BassPODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -510,7 +513,8 @@ static const struct line6_properties pod_properties_table[] = { [LINE6_POCKETPOD] = { .id = "PocketPOD", .name = "Pocket POD", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 0, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x02, @@ -520,6 +524,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxt", .name = "PODxt", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, @@ -532,6 +537,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtLive", .name = "PODxt Live", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, @@ -544,6 +550,7 @@ static const struct line6_properties pod_properties_table[] = { .id = "PODxtPro", .name = "PODxt Pro", .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI | LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c index ddc23dd..0c4512d 100644 --- a/sound/usb/line6/variax.c +++ b/sound/usb/line6/variax.c @@ -259,7 +259,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_PODXTLIVE_VARIAX] = { .id = "PODxtLive", .name = "PODxt Live", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x86, .ep_ctrl_w = 0x05, @@ -269,7 +270,8 @@ static const struct line6_properties variax_properties_table[] = { [LINE6_VARIAX] = { .id = "Variax", .name = "Variax Workbench", - .capabilities = LINE6_CAP_CONTROL, + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, .altsetting = 1, .ep_ctrl_r = 0x82, .ep_ctrl_w = 0x01,
Not all PODs use MIDI via USB data interface, thus allow avoiding that code and instead using direct processing.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 59 ++++++++++++++++++++++++++++-------------------- sound/usb/line6/driver.h | 8 ++++--- sound/usb/line6/midi.c | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9b16777..853a143 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -290,26 +290,31 @@ static void line6_data_received(struct urb *urb) if (urb->status == -ESHUTDOWN) return;
- done = - line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length);
- if (done < urb->actual_length) { - line6_midibuf_ignore(mb, done); - dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", - done, urb->actual_length); - } + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + }
- for (;;) { - done = - line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MESSAGE_MAXLEN);
- if (done == 0) - break; + if (done == 0) + break;
- line6->message_length = done; - line6_midi_receive(line6, line6->buffer_message, done); + line6->message_length = done; + line6_midi_receive(line6, line6->buffer_message, done);
+ if (line6->process_message) + line6->process_message(line6); + } + } else { if (line6->process_message) line6->process_message(line6); } @@ -469,7 +474,9 @@ static void line6_destruct(struct snd_card *card) struct usb_device *usbdev = line6->usbdev;
/* free buffer memory first: */ - kfree(line6->buffer_message); + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + kfree(line6->buffer_message); + kfree(line6->buffer_listen);
/* then free URBs: */ @@ -515,7 +522,7 @@ static void line6_get_interval(struct usb_line6 *line6) } }
-static int line6_init_cap_control_midi(struct usb_line6 *line6) +static int line6_init_cap_control(struct usb_line6 *line6) { int ret;
@@ -524,14 +531,16 @@ static int line6_init_cap_control_midi(struct usb_line6 *line6) if (!line6->buffer_listen) return -ENOMEM;
- line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); - if (!line6->buffer_message) - return -ENOMEM; - line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); if (!line6->urb_listen) return -ENOMEM;
+ if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + if (!line6->buffer_message) + return -ENOMEM; + } + ret = line6_start_listen(line6); if (ret < 0) { dev_err(line6->ifcdev, "cannot start listening: %d\n", ret); @@ -605,8 +614,8 @@ int line6_probe(struct usb_interface *interface,
line6_get_interval(line6);
- if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - ret = line6_init_cap_control_midi(line6); + if (properties->capabilities & LINE6_CAP_CONTROL) { + ret = line6_init_cap_control(line6); if (ret < 0) goto error; } @@ -676,7 +685,7 @@ int line6_suspend(struct usb_interface *interface, pm_message_t message)
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_stop_listen(line6);
if (line6pcm != NULL) { @@ -695,7 +704,7 @@ int line6_resume(struct usb_interface *interface) { struct usb_line6 *line6 = usb_get_intfdata(interface);
- if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + if (line6->properties->capabilities & LINE6_CAP_CONTROL) line6_start_listen(line6);
snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index d48c7d2..d7eefe1 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -147,15 +147,17 @@ struct usb_line6 { /* URB for listening to POD data endpoint */ struct urb *urb_listen;
- /* Buffer for listening to POD data endpoint */ + /* Buffer for incoming data from POD data endpoint */ unsigned char *buffer_listen;
- /* Buffer for message to be processed */ + /* Buffer for message to be processed, generated from MIDI layer */ unsigned char *buffer_message;
- /* Length of message to be processed */ + /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* If MIDI is supported, buffer_message contains the pre-processed data; + * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *); void (*disconnect)(struct usb_line6 *line6); }; diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c index cebea9b..d0fb2f2 100644 --- a/sound/usb/line6/midi.c +++ b/sound/usb/line6/midi.c @@ -258,7 +258,7 @@ int line6_init_midi(struct usb_line6 *line6) struct snd_rawmidi *rmidi; struct snd_line6_midi *line6midi;
- if (!(line6->properties->capabilities & LINE6_CAP_CONTROL)) { + if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { /* skip MIDI initialization and report success */ return 0; }
This includes audio in/out and basic initialization via control EP (emulates what original driver does). The initialization is done similarly to original POD, firmware and serial IDs are read and exported via sysfs.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/Kconfig | 4 +- sound/usb/line6/podhd.c | 276 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 265 insertions(+), 15 deletions(-)
diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig index f4585d37..8ffcf48 100644 --- a/sound/usb/line6/Kconfig +++ b/sound/usb/line6/Kconfig @@ -21,10 +21,10 @@ config SND_USB_POD re-amping)
config SND_USB_PODHD - tristate "Line 6 POD HD300/400/500 USB support" + tristate "Line 6 POD X3/HD300/400/500 USB support" select SND_USB_LINE6 help - This is a driver for POD HD300, 400 and 500 devices. + This is a driver for POD X3, HD300, 400 and 500 devices.
config SND_USB_TONEPORT tristate "TonePort GX, UX1 and UX2 USB support" diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 4fc4789..8cf0a98 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -2,6 +2,7 @@ * Line 6 Pod HD * * Copyright (C) 2011 Stefan Hajnoczi stefanha@gmail.com + * Copyright (C) 2015 Andrej Krutak dev@andree.sk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,11 +19,44 @@ #include "driver.h" #include "pcm.h"
+#define PODHD_STARTUP_DELAY 500 + +/* + * Stages of POD startup procedure + */ +enum { + PODHD_STARTUP_INIT = 1, + PODHD_STARTUP_SCHEDULE_WORKQUEUE, + PODHD_STARTUP_SETUP, + PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 +}; + enum { LINE6_PODHD300, LINE6_PODHD400, LINE6_PODHD500_0, LINE6_PODHD500_1, + LINE6_PODX3, +}; + +struct usb_line6_podhd { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version */ + int firmware_version; };
static struct snd_ratden podhd_ratden = { @@ -71,9 +105,196 @@ static struct line6_pcm_properties podhd_pcm_properties = { .rates = { .nrats = 1, .rats = &podhd_ratden}, - .bytes_per_channel = 3 /* 24bit audio (stereo) */ + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ };
+static struct line6_pcm_properties podx3_pcm_properties = { + .playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + /* 1+2: Main signal (out), 3+4: Tone 1, + * 5+6: Tone 2, 7+8: raw */ + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static void podhd_startup_start_workqueue(unsigned long data); +static void podhd_startup_workqueue(struct work_struct *work); +static int podhd_startup_finalize(struct usb_line6_podhd *pod); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%u\n", pod->serial_number); +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%06x\n", pod->firmware_version); +} + +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *podhd_dev_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group podhd_dev_attr_group = { + .name = "podhd", + .attrs = podhd_dev_attrs, +}; + +/* + * POD X3 startup procedure. + * + * May be compatible with other POD HD's, since it's also similar to the + * previous POD setup. In any case, it doesn't seem to be required for the + * audio nor bulk interfaces to work. + */ + +static void podhd_startup(struct usb_line6_podhd *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, + podhd_startup_start_workqueue, (unsigned long)pod); +} + +static void podhd_startup_start_workqueue(unsigned long data) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)data; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, + PODHD_STARTUP_SCHEDULE_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static int podhd_dev_start(struct usb_line6_podhd *pod) +{ + int ret; + u8 init_bytes[8]; + int i; + struct usb_device *usbdev = pod->line6.usbdev; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x11, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* NOTE: looks like some kind of ping message */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x11, 0x0, + &init_bytes, 3, LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(pod->line6.ifcdev, + "receive length failed (error %d)\n", ret); + return ret; + } + + pod->firmware_version = + (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); + + for (i = 0; i <= 16; i++) { + ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); + if (ret < 0) + return ret; + } + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, + 1, 0, + NULL, 0, LINE6_TIMEOUT * HZ); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void podhd_startup_workqueue(struct work_struct *work) +{ + struct usb_line6_podhd *pod = + container_of(work, struct usb_line6_podhd, startup_work); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + + podhd_dev_start(pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + + podhd_startup_finalize(pod); +} + +static int podhd_startup_finalize(struct usb_line6_podhd *pod) +{ + struct usb_line6 *line6 = &pod->line6; + + /* ALSA audio interface: */ + return snd_card_register(line6->card); +} + +static void podhd_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + } +} + /* Try to init POD HD device. */ @@ -81,6 +302,16 @@ static int podhd_init(struct usb_line6 *line6, const struct usb_device_id *id) { int err; + struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + + line6->disconnect = podhd_disconnect; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); + if (err < 0) + return err; + }
/* initialize MIDI subsystem: */ err = line6_init_midi(line6); @@ -88,12 +319,22 @@ static int podhd_init(struct usb_line6 *line6, return err;
/* initialize PCM subsystem: */ - err = line6_init_pcm(line6, &podhd_pcm_properties); + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); if (err < 0) return err;
- /* register USB audio system: */ - return snd_card_register(line6->card); + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { + /* register USB audio system directly */ + return podhd_startup_finalize(pod); + } + + /* init device and delay registering */ + init_timer(&pod->startup_timer); + INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + podhd_startup(pod); + return 0; }
#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) @@ -101,10 +342,12 @@ static int podhd_init(struct usb_line6 *line6,
/* table of devices that work with this driver */ static const struct usb_device_id podhd_id_table[] = { + /* TODO: no need to alloc data interfaces when only audio is used */ { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, {} };
@@ -114,8 +357,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD300] = { .id = "PODHD300", .name = "POD HD300", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -126,8 +368,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD400] = { .id = "PODHD400", .name = "POD HD400", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 5, .ep_ctrl_r = 0x84, @@ -138,8 +379,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_0] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -150,8 +390,7 @@ static const struct line6_properties podhd_properties_table[] = { [LINE6_PODHD500_1] = { .id = "PODHD500", .name = "POD HD500", - .capabilities = LINE6_CAP_CONTROL - | LINE6_CAP_PCM + .capabilities = LINE6_CAP_PCM | LINE6_CAP_HWMON, .altsetting = 1, .ep_ctrl_r = 0x81, @@ -159,6 +398,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3] = { + .id = "PODX3", + .name = "POD X3", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/* @@ -169,7 +419,7 @@ static int podhd_probe(struct usb_interface *interface, { return line6_probe(interface, id, "Line6-PODHD", &podhd_properties_table[id->driver_info], - podhd_init, sizeof(struct usb_line6)); + podhd_init, sizeof(struct usb_line6_podhd)); }
static struct usb_driver podhd_driver = {
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8cf0a98..8246ea5 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -37,6 +37,7 @@ enum { LINE6_PODHD500_0, LINE6_PODHD500_1, LINE6_PODX3, + LINE6_PODX3LIVE };
struct usb_line6_podhd { @@ -348,6 +349,7 @@ static const struct usb_device_id podhd_id_table[] = { { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, + { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, {} };
@@ -409,6 +411,17 @@ static const struct line6_properties podhd_properties_table[] = { .ep_audio_r = 0x86, .ep_audio_w = 0x02, }, + [LINE6_PODX3LIVE] = { + .id = "PODX3LIVE", + .name = "POD X3 LIVE", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, };
/*
Not all line6 devices use the control port.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 853a143..8a71d45 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -612,9 +612,8 @@ int line6_probe(struct usb_interface *interface, goto error; }
- line6_get_interval(line6); - if (properties->capabilities & LINE6_CAP_CONTROL) { + line6_get_interval(line6); ret = line6_init_cap_control(line6); if (ret < 0) goto error;
Only initialize PCM for POD HD devices that support it. No POD HD seems to support MIDI, thus drop the initialization.
Signed-off-by: Andrej Krutak dev@andree.sk --- sound/usb/line6/podhd.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 8246ea5..193eb29 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -314,17 +314,14 @@ static int podhd_init(struct usb_line6 *line6, return err; }
- /* initialize MIDI subsystem: */ - err = line6_init_midi(line6); - if (err < 0) - return err; - - /* initialize PCM subsystem: */ - err = line6_init_pcm(line6, - (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : - &podhd_pcm_properties); - if (err < 0) - return err; + if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3) ? &podx3_pcm_properties : + &podhd_pcm_properties); + if (err < 0) + return err; + }
if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL)) { /* register USB audio system directly */
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@andree.sk --- include/uapi/sound/asound.h | 3 +- sound/usb/line6/driver.c | 151 ++++++++++++++++++++++++++++++++++++++++++-- sound/usb/line6/driver.h | 23 ++++++- 3 files changed, 170 insertions(+), 7 deletions(-)
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..3536efe 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -17,6 +17,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/hwdep.h>
#include "capture.h" #include "driver.h" @@ -303,7 +304,7 @@ static void line6_data_received(struct urb *urb) for (;;) { done = line6_midibuf_read(mb, line6->buffer_message, - LINE6_MESSAGE_MAXLEN); + LINE6_MIDI_MESSAGE_MAXLEN);
if (done == 0) break; @@ -315,8 +316,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); @@ -473,14 +477,16 @@ static void line6_destruct(struct snd_card *card) struct usb_line6 *line6 = card->private_data; struct usb_device *usbdev = line6->usbdev;
- /* free buffer memory first: */ - if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) + /* Free buffer memory first. We cannot depend on the existence of private + * data from the (podhd) module, it may be gone already during this call */ + if (line6->buffer_message) kfree(line6->buffer_message);
kfree(line6->buffer_listen);
/* then free URBs: */ usb_free_urb(line6->urb_listen); + line6->urb_listen = NULL;
/* decrement reference counters: */ usb_put_dev(usbdev); @@ -522,6 +528,138 @@ 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 layer provides atomicity here */ + + line6->messages.active = 1; + + return 0; +} + +/* Stop buffering */ +static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_line6 *line6 = hw->private_data; + + line6->messages.active = 0; + + 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; + long rv = 0; + unsigned int out_count; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + + while (kfifo_len(&line6->messages.fifo) == 0) { + mutex_unlock(&line6->messages.read_lock); + + rv = wait_event_interruptible( + line6->messages.wait_queue, + kfifo_len(&line6->messages.fifo) != 0); + if (rv < 0) + return rv; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + } + + if (kfifo_peek_len(&line6->messages.fifo) > count) { + /* Buffer too small; allow re-read of the current item... */ + rv = -EINVAL; + } else { + rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count); + if (rv == 0) + rv = out_count; + } + + mutex_unlock(&line6->messages.read_lock); + return rv; +} + +/* 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; + + if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) { + /* This is an arbitrary limit - still better than nothing... */ + return -EINVAL; + } + + data_copy = memdup_user(data, count); + if (IS_ERR(ERR_PTR)) + return -ENOMEM; + + 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) +{ + if (!line6->messages.active) + return; + + if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) { + /* No race condition here, there's only one writer */ + kfifo_in(&line6->messages.fifo, + line6->buffer_message, line6->message_length); + } /* else TODO: signal overflow */ + + wake_up_interruptible(&line6->messages.wait_queue); +} + +static int line6_hwdep_init(struct usb_line6 *line6) +{ + int err; + struct snd_hwdep *hwdep; + + /* TODO: usb_driver_claim_interface(); */ + line6->process_message = line6_hwdep_push_message; + line6->messages.active = 0; + init_waitqueue_head(&line6->messages.wait_queue); + mutex_init(&line6->messages.read_lock); + INIT_KFIFO(line6->messages.fifo); + + err = snd_hwdep_new(line6->card, "config", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "config"); + hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; + hwdep->ops = hwdep_ops; + hwdep->private_data = line6; + hwdep->exclusive = true; + +end: + return err; +} + static int line6_init_cap_control(struct usb_line6 *line6) { int ret; @@ -536,9 +674,13 @@ static int line6_init_cap_control(struct usb_line6 *line6) return -ENOMEM;
if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { - line6->buffer_message = kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); if (!line6->buffer_message) return -ENOMEM; + } else { + ret = line6_hwdep_init(line6); + if (ret < 0) + return ret; }
ret = line6_start_listen(line6); @@ -716,3 +858,4 @@ EXPORT_SYMBOL_GPL(line6_resume); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); + diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index d7eefe1..cf9f077 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -12,8 +12,9 @@ #ifndef DRIVER_H #define DRIVER_H
-#include <linux/spinlock.h> #include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/kfifo.h> #include <sound/core.h>
#include "midi.h" @@ -32,7 +33,16 @@
#define LINE6_TIMEOUT 1 #define LINE6_BUFSIZE_LISTEN 64 -#define LINE6_MESSAGE_MAXLEN 256 +#define LINE6_MIDI_MESSAGE_MAXLEN 256 + +#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7 +/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */ +#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER) + + +#if LINE6_BUFSIZE_LISTEN > 65535 +#error "Use dynamic fifo instead" +#endif
/* Line 6 MIDI control commands @@ -156,6 +166,15 @@ struct usb_line6 { /* Length of message to be processed, generated from MIDI layer */ int message_length;
+ /* Circular buffer for non-MIDI control messages */ + struct { + struct mutex read_lock; + wait_queue_head_t wait_queue; + int active:1; + STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT) + fifo; + } messages; + /* If MIDI is supported, buffer_message contains the pre-processed data; * otherwise the data is only in urb_listen (buffer_incoming). */ void (*process_message)(struct usb_line6 *);
On Sun, 18 Sep 2016 20:59:32 +0200, Andrej Krutak wrote:
@@ -156,6 +166,15 @@ struct usb_line6 { /* Length of message to be processed, generated from MIDI layer */ int message_length;
- /* Circular buffer for non-MIDI control messages */
- struct {
struct mutex read_lock;
wait_queue_head_t wait_queue;
int active:1;
This should be unsigned int. Otherwise 1 bit int is handled as -1.
Takashi
On Sun, 18 Sep 2016 20:59:20 +0200, Andrej Krutak wrote:
Hello all,
attached are the yet again reworked patches. Basically the diff is only some formatting. In addition, some of the previously big patches were split to multiple smaller ones. Compared to v3, there is a small bisectability/patch sanity fix.
I have checked that after each one, the compilation works, but not that the result doesn't segfault or even work - I don't have the hardware previously supported by the driver and the most interesting changes are only useful as a whole for POD X3.
Greetings!
Andrej Krutak (12): ALSA: line6: Enable different number of URBs for frame transfers ALSA: line6: Add high-speed USB support ALSA: line6: Support assymetrical in/out configurations ALSA: line6: Allow different channel numbers for in/out ALSA: line6: Add LINE6_CAP_IN_NEEDS_OUT, a void playback stream during capture ALSA: line6: Distinguish device init (ctrl EP) and MIDI data transfer (int EP) ALSA: line6: Allow processing of raw incoming messages ALSA: line6: Add support for POD X3 ALSA: line6: Add support for POD X3 Live (only USB ID differs from POD X3) ALSA: line6: Only determine control port properties if needed ALSA: line6: Cleanup podhd initialization ALSA: line6: Add hwdep interface to access the POD control messages
OK, now all patches look almost good, so I applied them with a few manual fixes of coding style issues checkpatch.pl copmlained, as well as a fix of bit field type in patch 12.
Thanks!
Takashi
participants (3)
-
Andrej Krutak
-
kbuild test robot
-
Takashi Iwai