us144mkii: fix moderate bugs - capture robustness, ghost pointer, MIDI cleanup

BUG-09: Buffer partial 64-byte frame remainders across URB completions
to prevent silent data loss when USB bulk transfers return incomplete
chunks. Adds remainder and combined buffers to tascam_card struct.

BUG-10: Distinguish transient USB errors (-EOVERFLOW, -ENOENT, -EPIPE)
from fatal ones in capture completion. Resubmit URB on transient errors
instead of dropping it, preventing capture stream death on transient
USB bandwidth contention or controller glitches.

BUG-11: Return playback_frames_consumed from pointer callback during
ghost playback instead of always returning 0, providing correct
semantics when the callback is queried indirectly.

BUG-12: Unanchor MIDI output URB before returning on error path in
tascam_midi_out_complete to prevent the URB from remaining stuck in
the anchor's pending list.
This commit is contained in:
Marvin 2026-04-22 20:22:26 -03:00
parent f6c59c4f9f
commit ef73de30ec
4 changed files with 51 additions and 9 deletions

View File

@ -172,6 +172,10 @@ struct tascam_card {
unsigned int feedback_urb_skip_count;
bool running_ghost_playback;
u8 capture_remainder_buf[63];
unsigned int capture_remainder_len;
u8 capture_combined_buf[CAPTURE_PACKET_SIZE + 63];
struct work_struct stop_work;
struct work_struct stop_pcm_work;
};

View File

@ -46,6 +46,7 @@ static int tascam_capture_prepare(struct snd_pcm_substream *substream)
tascam->driver_capture_pos = 0;
tascam->capture_frames_processed = 0;
tascam->last_cap_period_pos = 0;
tascam->capture_remainder_len = 0;
return 0;
}
@ -144,40 +145,64 @@ static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames)
* @urb: the completed URB
*
* Decodes audio data, updates ring buffer, and handles period elapsed.
* Handles partial 64-byte frames by buffering remainder across URB completions.
* Distinguishes transient USB errors from fatal ones for robustness.
*/
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_runtime *runtime;
int frames, part1;
int frames, part1, total_available, new_remainder;
unsigned long flags;
bool need_period_elapsed = false;
u8 *combined = tascam->capture_combined_buf;
if (urb->status || !tascam || !tascam->dev)
if (!tascam || !tascam->dev)
return;
if (urb->status) {
if (urb->status == -EOVERFLOW || urb->status == -ENOENT || urb->status == -EPIPE) {
usb_anchor_urb(urb, &tascam->capture_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
}
return;
}
goto exit;
}
if (!tascam->capture_substream || !tascam->capture_substream->runtime)
goto exit;
runtime = tascam->capture_substream->runtime;
frames = urb->actual_length / 64;
memcpy(combined, tascam->capture_remainder_buf, tascam->capture_remainder_len);
memcpy(combined + tascam->capture_remainder_len, urb->transfer_buffer, urb->actual_length);
total_available = tascam->capture_remainder_len + urb->actual_length;
frames = total_available / 64;
new_remainder = total_available % 64;
if (frames > 0) {
spin_lock_irqsave(&tascam->lock, flags);
if (!atomic_read(&tascam->capture_active)) {
spin_unlock_irqrestore(&tascam->lock, flags);
goto exit;
tascam->capture_remainder_len = new_remainder;
if (new_remainder > 0)
memcpy(tascam->capture_remainder_buf, combined + (frames * 64), new_remainder);
goto resubmit;
}
u32 *dma = (u32 *)(runtime->dma_area + frames_to_bytes(runtime, tascam->driver_capture_pos));
if (tascam->driver_capture_pos + frames <= runtime->buffer_size) {
tascam_decode_capture_chunk(urb->transfer_buffer, dma, frames);
tascam_decode_capture_chunk(combined, dma, frames);
} else {
part1 = runtime->buffer_size - tascam->driver_capture_pos;
tascam_decode_capture_chunk(urb->transfer_buffer, dma, part1);
tascam_decode_capture_chunk(urb->transfer_buffer + (part1 * 64),
tascam_decode_capture_chunk(combined, dma, part1);
tascam_decode_capture_chunk(combined + (part1 * 64),
(u32 *)runtime->dma_area, frames - part1);
}
@ -191,6 +216,11 @@ void capture_urb_complete(struct urb *urb)
spin_unlock_irqrestore(&tascam->lock, flags);
}
tascam->capture_remainder_len = new_remainder;
if (new_remainder > 0)
memcpy(tascam->capture_remainder_buf, combined + (frames * 64), new_remainder);
resubmit:
usb_anchor_urb(urb, &tascam->capture_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);

View File

@ -14,6 +14,7 @@ static void tascam_midi_out_complete(struct urb *urb)
if (urb->status || !sub || !tascam->midi_out_active) {
tascam->midi_out_active = false;
usb_unanchor_urb(urb);
spin_unlock_irqrestore(&tascam->midi_lock, flags);
return;
}

View File

@ -207,8 +207,15 @@ static snd_pcm_uframes_t tascam_playback_pointer(struct snd_pcm_substream *subst
unsigned long flags;
u64 pos;
if (!atomic_read(&tascam->playback_active))
if (!atomic_read(&tascam->playback_active)) {
if (tascam->running_ghost_playback) {
spin_lock_irqsave(&tascam->lock, flags);
pos = tascam->playback_frames_consumed;
spin_unlock_irqrestore(&tascam->lock, flags);
return (snd_pcm_uframes_t)pos;
}
return 0;
}
spin_lock_irqsave(&tascam->lock, flags);
pos = tascam->playback_frames_consumed;