Update us144mkii.c

This commit is contained in:
serifpersia 2025-07-19 23:09:05 +02:00
parent 7284fb4c42
commit d22558abb8
1 changed files with 136 additions and 309 deletions

View File

@ -2,28 +2,23 @@
// Copyright (c) 2025 serifpersia <ramiserifpersia@gmail.com> // Copyright (c) 2025 serifpersia <ramiserifpersia@gmail.com>
/* /*
* ALSA Driver for TASCAM US-144MKII Audio Interface * ALSA Driver for TASCAM US-144MKII Audio Interface
*
* This version includes a robust, state-machine-based MIDI implementation
* to fix packet dropouts and a hardware activation sequence to enable MIDI input.
*/ */
#include <linux/usb.h> #include <linux/usb.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/bitops.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/rawmidi.h> #include <sound/rawmidi.h>
#include <sound/initval.h> #include <sound/initval.h>
#include <sound/control.h> #include <sound/control.h>
#include <linux/printk.h>
MODULE_AUTHOR("serifpersia <ramiserifpersia@gmail.com>"); MODULE_AUTHOR("serifpersia <ramiserifpersia@gmail.com>");
MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
#define DRIVER_NAME "us144mkii" #define DRIVER_NAME "us144mkii"
#define DRIVER_VERSION "2.5" // Version bump for MIDI fixes #define DRIVER_VERSION "1.6"
/* --- Module Parameters --- */ /* --- Module Parameters --- */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
@ -77,9 +72,8 @@ static int dev_idx;
#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) #define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4)
#define NUM_MIDI_IN_URBS 4 #define NUM_MIDI_IN_URBS 4
#define MIDI_IN_BUF_SIZE 64 #define MIDI_IN_BUF_SIZE 64
#define MIDI_OUT_BUF_SIZE 64 // Buffer in URB, must be >= 9 #define MIDI_OUT_BUF_SIZE 64
#define NUM_MIDI_OUT_URBS 8 // Increased for better throughput #define NUM_MIDI_OUT_URBS 4
#define MIDI_OUT_PACKET_QUEUE_SIZE 32 // Intermediate queue for 9-byte packets
#define USB_CTRL_TIMEOUT_MS 1000 #define USB_CTRL_TIMEOUT_MS 1000
/* --- Audio Format Configuration --- */ /* --- Audio Format Configuration --- */
@ -94,17 +88,6 @@ static int dev_idx;
#define FRAMES_PER_DECODE_BLOCK 8 #define FRAMES_PER_DECODE_BLOCK 8
#define RAW_BYTES_PER_DECODE_BLOCK 512 #define RAW_BYTES_PER_DECODE_BLOCK 512
/* --- State machine for the MIDI output parser --- */
enum tascam_midi_out_state {
MIDI_OUT_STATE_UNKNOWN, /* Waiting for a status byte */
MIDI_OUT_STATE_1PARAM, /* Waiting for 1 data byte (e.g., for Program Change) */
MIDI_OUT_STATE_2PARAM_1, /* Waiting for the 1st of 2 data bytes (e.g., for Note On) */
MIDI_OUT_STATE_2PARAM_2, /* Waiting for the 2nd of 2 data bytes */
MIDI_OUT_STATE_SYSEX_0, /* In SysEx, waiting for 1st data byte of a 3-byte chunk */
MIDI_OUT_STATE_SYSEX_1, /* In SysEx, waiting for 2nd data byte */
MIDI_OUT_STATE_SYSEX_2, /* In SysEx, waiting for 3rd data byte */
};
/* --- Main Driver Data Structure --- */ /* --- Main Driver Data Structure --- */
struct tascam_card { struct tascam_card {
struct usb_device *dev; struct usb_device *dev;
@ -150,33 +133,12 @@ struct tascam_card {
struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS]; struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS];
atomic_t midi_out_active; atomic_t midi_out_active;
struct work_struct midi_out_work; struct work_struct midi_out_work;
unsigned long midi_out_urbs_in_flight; /* bitmask */ unsigned long midi_out_urbs_in_flight;
spinlock_t midi_out_lock; spinlock_t midi_out_lock;
u8 midi_running_status;
/* --- NEW: MIDI Output Packet Queue --- */ /* State for proprietary MIDI input protocol */
/** u8 midi_in_pending_packet[9];
* @midi_out_packet_queue: Ring buffer for formatted 9-byte MIDI packets. bool midi_in_has_pending_packet;
* The state machine places formatted packets here, and the work handler
* dequeues them to send in individual URBs. This is critical because
* the device expects one 9-byte bulk transfer per packet.
*/
u8 midi_out_packet_queue[MIDI_OUT_PACKET_QUEUE_SIZE][9];
int midi_out_queue_read_ptr;
volatile int midi_out_queue_write_ptr;
/* State machine for MIDI output stream */
struct {
enum tascam_midi_out_state state;
u8 data[2]; /* Buffer for holding partial MIDI messages */
u8 running_status; /* Currently active running status */
} midi_out_state;
/* State machine for MIDI input parser */
enum {
MIDI_IN_STATE_WAIT_PACKET_1,
MIDI_IN_STATE_WAIT_PACKET_2
} midi_in_state;
u8 midi_in_b0; /* Status byte from the first packet */
/* Shared state & Routing Matrix */ /* Shared state & Routing Matrix */
spinlock_t lock; spinlock_t lock;
@ -715,34 +677,22 @@ error:
static int tascam_playback_open(struct snd_pcm_substream *substream) static int tascam_playback_open(struct snd_pcm_substream *substream)
{ {
struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err = 0;
substream->runtime->hw = tascam_pcm_hw; substream->runtime->hw = tascam_pcm_hw;
tascam->playback_substream = substream; tascam->playback_substream = substream;
atomic_set(&tascam->playback_active, 0); atomic_set(&tascam->playback_active, 0);
if (!tascam->capture_substream) {
err = tascam_alloc_urbs(tascam);
if (err < 0)
return err;
}
return 0; return 0;
} }
static int tascam_capture_open(struct snd_pcm_substream *substream) static int tascam_capture_open(struct snd_pcm_substream *substream)
{ {
struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err = 0;
substream->runtime->hw = tascam_pcm_hw; substream->runtime->hw = tascam_pcm_hw;
tascam->capture_substream = substream; tascam->capture_substream = substream;
atomic_set(&tascam->capture_active, 0); atomic_set(&tascam->capture_active, 0);
if (!tascam->playback_substream) {
err = tascam_alloc_urbs(tascam);
if (err < 0)
return err;
}
return 0; return 0;
} }
@ -750,8 +700,7 @@ static int tascam_playback_close(struct snd_pcm_substream *substream)
{ {
struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->playback_substream = NULL; tascam->playback_substream = NULL;
if (!tascam->capture_substream)
tascam_free_urbs(tascam);
return 0; return 0;
} }
@ -759,8 +708,7 @@ static int tascam_capture_close(struct snd_pcm_substream *substream)
{ {
struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->capture_substream = NULL; tascam->capture_substream = NULL;
if (!tascam->playback_substream)
tascam_free_urbs(tascam);
return 0; return 0;
} }
@ -770,8 +718,7 @@ static int tascam_capture_close(struct snd_pcm_substream *substream)
* @rate: the target sample rate (e.g., 44100, 96000). * @rate: the target sample rate (e.g., 44100, 96000).
* *
* This function sends a sequence of vendor-specific and UAC control messages * This function sends a sequence of vendor-specific and UAC control messages
* to configure the device hardware for the specified sample rate. This sequence * to configure the device hardware for the specified sample rate.
* is also required to activate the MIDI ports.
* *
* Return: 0 on success, or a negative error code on failure. * Return: 0 on success, or a negative error code on failure.
*/ */
@ -839,20 +786,6 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
int err; int err;
unsigned int rate = params_rate(params); unsigned int rate = params_rate(params);
/* --- MODIFIED: Check if rate is already set --- */
/**
* The device is configured to a default rate at probe time to enable
* MIDI. If an audio application requests the same rate, we don't need
* to re-run the entire configuration sequence.
*/
if (rate == tascam->current_rate) {
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (err < 0)
return err;
// Rate is already configured, just return.
return 0;
}
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (err < 0) if (err < 0)
return err; return err;
@ -880,13 +813,14 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
} }
} }
/* This will only run if the new rate is different from the current one */ if (tascam->current_rate != rate) {
err = us144mkii_configure_device_for_rate(tascam, rate); err = us144mkii_configure_device_for_rate(tascam, rate);
if (err < 0) { if (err < 0) {
tascam->current_rate = 0; tascam->current_rate = 0;
return err; return err;
}
tascam->current_rate = rate;
} }
tascam->current_rate = rate;
return 0; return 0;
} }
@ -918,7 +852,7 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream)
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet; tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet;
feedback_packets = 1; /* Lowest latency */ feedback_packets = 1;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) { for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i]; struct urb *f_urb = tascam->feedback_urbs[i];
@ -1488,15 +1422,14 @@ static void capture_urb_complete(struct urb *urb)
dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret); dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret);
} }
/* --- ALSA RawMIDI Implementation --- */ /* --- ALSA RawMIDI Implementation --- */
/** /**
* tascam_midi_in_urb_complete - Completion handler for MIDI IN bulk URBs. * tascam_midi_in_urb_complete
* @urb: The completed URB. * @urb: The completed URB.
* * each 9-byte USB packet contains a variable-length MIDI message fragment.
* The device sends MIDI data in a peculiar 2-packet format. A standard 3-byte * All 0xFD bytes are padding and must be stripped.
* MIDI message (e.g., Note On) is split across two 9-byte USB packets. This
* function uses a simple state machine to reassemble these messages.
*/ */
static void tascam_midi_in_urb_complete(struct urb *urb) static void tascam_midi_in_urb_complete(struct urb *urb)
{ {
@ -1505,61 +1438,68 @@ static void tascam_midi_in_urb_complete(struct urb *urb)
if (urb->status) { if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET && urb->status != -ESHUTDOWN) if (urb->status != -ENOENT && urb->status != -ECONNRESET && urb->status != -ESHUTDOWN)
dev_info(tascam->card->dev, "MIDI IN URB cancelled: %d\n", urb->status); dev_err(tascam->card->dev, "MIDI IN URB failed: status %d\n", urb->status);
return; return;
} }
if (!tascam || !atomic_read(&tascam->midi_in_active) || !tascam->midi_in_substream) if (!tascam || !atomic_read(&tascam->midi_in_active))
goto resubmit; return;
/* The device sends MIDI data in 9-byte packets. */ if (tascam->midi_in_substream && urb->actual_length > 0) {
if (urb->actual_length == 9) { u8 *raw_buf = urb->transfer_buffer;
u8 *buf = urb->transfer_buffer; u8 stripped_buf[9]; /* Max possible size is 9 */
int i;
int stripped_len = 0;
/* Ignore pure 0xFD padding packets */ /*
if (buf[0] == 0xfd && buf[1] == 0xfd) * Strip all 0xFD padding bytes from the raw USB packet,
goto resubmit; * copying the result into a temporary buffer.
*/
for (i = 0; i < urb->actual_length; i++) {
if (raw_buf[i] != 0xfd) {
stripped_buf[stripped_len] = raw_buf[i];
stripped_len++;
}
}
if (tascam->midi_in_state == MIDI_IN_STATE_WAIT_PACKET_1) { /*
/* Packet 1 contains the first MIDI byte (status). */ * The last byte is often a terminator (0x00, 0xFF, etc.).
tascam->midi_in_b0 = buf[1]; * If the stripped message is a single byte and it's a terminator,
tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_2; * it's likely an empty keep-alive packet. We can ignore it.
} else { /* MIDI_IN_STATE_WAIT_PACKET_2 */ * A real single-byte message like Active Sensing (0xFE) will pass.
/* Packet 2 contains the next two MIDI bytes. */ */
u8 msg[3]; if (stripped_len == 1 && stripped_buf[0] < 0x80) {
msg[0] = tascam->midi_in_b0; /* This is likely an empty packet, do nothing. */
msg[1] = buf[0]; } else if (stripped_len > 0) {
msg[2] = buf[1]; /*
* The last byte might be a terminator. If so, don't send it.
/* Determine message length from status byte */ * A real MIDI data byte will never be > 0x7F.
if ((msg[0] >= 0x80 && msg[0] <= 0xbf) || (msg[0] >= 0xe0 && msg[0] <= 0xef) || (msg[0] == 0xf2)) { * A real status byte will be handled by the ALSA core.
snd_rawmidi_receive(tascam->midi_in_substream, msg, 3); * Terminators like 0xFF are ambiguous (System Reset vs terminator).
} else if ((msg[0] >= 0xc0 && msg[0] <= 0xdf) || (msg[0] == 0xf3)) { * Let's assume for now that if the last byte is not a valid
snd_rawmidi_receive(tascam->midi_in_substream, msg, 2); * data byte for the preceding status, it's a terminator.
} else { /* 1-byte messages like F1, F6, F8-FF */ * A simpler approach is to just check for 0x00 or 0xFF.
snd_rawmidi_receive(tascam->midi_in_substream, msg, 1); */
if (stripped_buf[stripped_len - 1] == 0x00 ||
stripped_buf[stripped_len - 1] == 0xff) {
stripped_len--;
} }
/* Reset state for the next message */ if (stripped_len > 0) {
tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1; snd_rawmidi_receive(tascam->midi_in_substream, stripped_buf, stripped_len);
}
} }
} else if (urb->actual_length > 0) {
dev_warn(tascam->card->dev, "Received unexpected MIDI IN data size: %d\n", urb->actual_length);
} }
resubmit: ret = usb_submit_urb(urb, GFP_ATOMIC);
if (atomic_read(&tascam->midi_in_active)) { if (ret < 0)
ret = usb_submit_urb(urb, GFP_ATOMIC); dev_err(tascam->card->dev, "Failed to resubmit MIDI IN URB: error %d\n", ret);
if (ret < 0)
dev_err_ratelimited(tascam->card->dev, "Failed to resubmit MIDI IN URB: %d\n", ret);
}
} }
static int tascam_midi_in_open(struct snd_rawmidi_substream *substream) static int tascam_midi_in_open(struct snd_rawmidi_substream *substream)
{ {
struct tascam_card *tascam = substream->rmidi->private_data; struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_in_substream = substream; tascam->midi_in_substream = substream;
tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1;
return 0; return 0;
} }
@ -1574,8 +1514,9 @@ static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, int
int i, err; int i, err;
if (up) { if (up) {
dev_info(tascam->card->dev, "MIDI IN TRIGGER: START\n");
if (atomic_xchg(&tascam->midi_in_active, 1) == 0) { if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1; tascam->midi_in_has_pending_packet = false;
for (i = 0; i < NUM_MIDI_IN_URBS; i++) { for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
err = usb_submit_urb(tascam->midi_in_urbs[i], GFP_KERNEL); err = usb_submit_urb(tascam->midi_in_urbs[i], GFP_KERNEL);
if (err < 0) if (err < 0)
@ -1583,6 +1524,7 @@ static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, int
} }
} }
} else { } else {
dev_info(tascam->card->dev, "MIDI IN TRIGGER: STOP\n");
if (atomic_xchg(&tascam->midi_in_active, 0) == 1) { if (atomic_xchg(&tascam->midi_in_active, 0) == 1) {
for (i = 0; i < NUM_MIDI_IN_URBS; i++) for (i = 0; i < NUM_MIDI_IN_URBS; i++)
usb_kill_urb(tascam->midi_in_urbs[i]); usb_kill_urb(tascam->midi_in_urbs[i]);
@ -1596,122 +1538,14 @@ static struct snd_rawmidi_ops tascam_midi_in_ops = {
.trigger = tascam_midi_in_trigger, .trigger = tascam_midi_in_trigger,
}; };
/* --- REWRITTEN MIDI OUTPUT LOGIC --- */
/**
* format_tascam_midi_packets - Formats a MIDI message into two 9-byte packets.
* @b0: The first byte of the MIDI message (status).
* @b1: The second byte of the MIDI message (data1).
* @b2: The third byte of the MIDI message (data2).
* @packet1: Destination buffer for the first 9-byte packet.
* @packet2: Destination buffer for the second 9-byte packet.
*
* This helper function creates the two 9-byte packets required by the hardware.
*/
static void format_tascam_midi_packets(u8 b0, u8 b1, u8 b2, u8 *packet1, u8 *packet2)
{
u8 cin = b0 >> 4;
/* Packet 1: Header and first MIDI byte */
memset(packet1, 0xfd, 9);
packet1[0] = (0 << 4) | cin; /* Cable 0, CIN */
packet1[1] = b0;
packet1[8] = 0x00;
/* Packet 2: Second and third MIDI bytes */
memset(packet2, 0xfd, 9);
packet2[0] = b1;
packet2[1] = b2;
packet2[8] = 0x00;
}
/**
* tascam_midi_out_transmit_byte - Process one byte and enqueue formatted packets.
* @tascam: The driver instance.
* @b: The MIDI byte from the ALSA buffer.
*
* This function implements a state machine to parse a raw MIDI byte stream.
* When a complete message is formed, it's formatted into one or two 9-byte
* hardware packets and placed in the `midi_out_packet_queue`.
*/
static void tascam_midi_out_transmit_byte(struct tascam_card *tascam, u8 b)
{
u8 packet1[9], packet2[9];
bool send_two = false;
/* Helper macro to enqueue a single 9-byte packet */
#define ENQUEUE_PACKET(p) \
do { \
int next_write_ptr = (tascam->midi_out_queue_write_ptr + 1) % MIDI_OUT_PACKET_QUEUE_SIZE; \
if (next_write_ptr == tascam->midi_out_queue_read_ptr) { \
dev_warn_ratelimited(tascam->card->dev, "MIDI out queue full, dropping packet.\n"); \
} else { \
memcpy(tascam->midi_out_packet_queue[tascam->midi_out_queue_write_ptr], p, 9); \
tascam->midi_out_queue_write_ptr = next_write_ptr; \
} \
} while (0)
if (b >= 0xf8) { /* System Real-Time messages are single-packet */
format_tascam_midi_packets(b, 0, 0, packet1, packet2);
ENQUEUE_PACKET(packet1);
return;
}
if (b >= 0x80) { /* Status byte */
tascam->midi_out_state.running_status = (b >= 0xf0) ? 0 : b;
tascam->midi_out_state.data[0] = b;
if ((b >= 0xc0 && b <= 0xdf) || b == 0xf1 || b == 0xf3) {
tascam->midi_out_state.state = MIDI_OUT_STATE_1PARAM;
} else if (b == 0xf6) { /* Tune request */
format_tascam_midi_packets(b, 0, 0, packet1, packet2);
ENQUEUE_PACKET(packet1);
tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN;
} else { /* Note On/Off, Poly Pressure, Control Change, Pitch Bend, Song Position */
tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_1;
}
} else { /* Data byte */
switch (tascam->midi_out_state.state) {
case MIDI_OUT_STATE_UNKNOWN:
if (tascam->midi_out_state.running_status) {
/* Handle running status: re-process with status byte first */
tascam_midi_out_transmit_byte(tascam, tascam->midi_out_state.running_status);
tascam_midi_out_transmit_byte(tascam, b);
}
break; /* else, orphaned data byte, ignore */
case MIDI_OUT_STATE_1PARAM:
format_tascam_midi_packets(tascam->midi_out_state.data[0], b, 0, packet1, packet2);
send_two = true;
tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN;
break;
case MIDI_OUT_STATE_2PARAM_1:
tascam->midi_out_state.data[1] = b;
tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_2;
break;
case MIDI_OUT_STATE_2PARAM_2:
format_tascam_midi_packets(tascam->midi_out_state.data[0], tascam->midi_out_state.data[1], b, packet1, packet2);
send_two = true;
/* For running status, go back to waiting for the first data byte */
tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_1;
break;
default:
/* SysEx not fully handled for brevity, but would enqueue here */
break;
}
}
if (send_two) {
ENQUEUE_PACKET(packet1);
ENQUEUE_PACKET(packet2);
}
}
/** /**
* tascam_midi_out_urb_complete - Completion handler for MIDI OUT bulk URB. * tascam_midi_out_urb_complete - Completion handler for MIDI OUT bulk URB.
* @urb: The completed URB. * @urb: The completed URB.
* *
* This function runs in interrupt context. It marks the output URB as no * This function runs in interrupt context. It marks the output URB as no
* longer in-flight. It then re-schedules the work handler to check for and * longer in-flight. It then re-schedules the work handler to check for and
* send any more data waiting in the queue. * send any more data waiting in the ALSA buffer. This is a safe, non-blocking
* way to continue the data transmission chain.
*/ */
static void tascam_midi_out_urb_complete(struct urb *urb) static void tascam_midi_out_urb_complete(struct urb *urb)
{ {
@ -1748,66 +1582,61 @@ static void tascam_midi_out_urb_complete(struct urb *urb)
} }
/** /**
* tascam_midi_out_work_handler - Deferred work for sending MIDI data. * tascam_midi_out_work_handler
* @work: The work_struct instance. * @work: The work_struct instance.
* * output protocol: take the raw MIDI
* This function runs in a kernel thread context. It has two phases: * message bytes from the application, place them at the start of a 9-byte
* 1. Pull all available bytes from the ALSA buffer and run them through the * buffer, pad the rest with 0xFD, and add a terminator byte (0x00).
* state machine, which enqueues formatted 9-byte packets. * This function pulls as many bytes as will fit into one packet from the
* 2. Dequeue packets one by one and send each in its own URB. * ALSA buffer and sends them.
*/ */
static void tascam_midi_out_work_handler(struct work_struct *work) static void tascam_midi_out_work_handler(struct work_struct *work)
{ {
struct tascam_card *tascam = container_of(work, struct tascam_card, midi_out_work); struct tascam_card *tascam = container_of(work, struct tascam_card, midi_out_work);
struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
unsigned long flags; unsigned long flags;
int urb_index; int urb_index;
u8 byte; bool more_data;
if (!tascam->midi_out_substream || !atomic_read(&tascam->midi_out_active)) if (!substream || !atomic_read(&tascam->midi_out_active))
return; return;
/* Phase 1: Pull from ALSA and enqueue packets */ start_work:
spin_lock_irqsave(&tascam->midi_out_lock, flags); spin_lock_irqsave(&tascam->midi_out_lock, flags);
while (snd_rawmidi_transmit(tascam->midi_out_substream, &byte, 1) == 1)
tascam_midi_out_transmit_byte(tascam, byte);
spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
/* Phase 2: Dequeue packets and send them in URBs */ urb_index = -1;
while (atomic_read(&tascam->midi_out_active)) { for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) {
struct urb *urb; if (!test_bit(i, &tascam->midi_out_urbs_in_flight)) {
urb_index = i;
spin_lock_irqsave(&tascam->midi_out_lock, flags); break;
/* Check if there is anything to send */
if (tascam->midi_out_queue_read_ptr == tascam->midi_out_queue_write_ptr) {
spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
return; /* Queue is empty */
} }
}
/* Find a free URB */ if (urb_index < 0) {
urb_index = -1; spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) { return; /* No free URBs, will be rescheduled */
if (!test_bit(i, &tascam->midi_out_urbs_in_flight)) { }
urb_index = i;
break;
}
}
if (urb_index < 0) { struct urb *urb = tascam->midi_out_urbs[urb_index];
spin_unlock_irqrestore(&tascam->midi_out_lock, flags); u8 *buf = urb->transfer_buffer;
return; /* No free URBs, completion will reschedule */ int bytes_to_send;
}
urb = tascam->midi_out_urbs[urb_index]; /*
* We can send up to 8 bytes of MIDI data in one 9-byte packet.
* The 9th byte is a terminator.
*/
bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
/* Dequeue one 9-byte packet and copy it to the URB */ if (bytes_to_send > 0) {
memcpy(urb->transfer_buffer, /* Pad the rest of the 9-byte packet with 0xFD */
tascam->midi_out_packet_queue[tascam->midi_out_queue_read_ptr], if (bytes_to_send < 9)
9); memset(buf + bytes_to_send, 0xfd, 9 - bytes_to_send);
urb->transfer_buffer_length = 9;
tascam->midi_out_queue_read_ptr = (tascam->midi_out_queue_read_ptr + 1) % MIDI_OUT_PACKET_QUEUE_SIZE; /* The last byte is a terminator. 0x00 is a safe choice. */
buf[8] = 0x00;
set_bit(urb_index, &tascam->midi_out_urbs_in_flight); set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
urb->transfer_buffer_length = 9;
spin_unlock_irqrestore(&tascam->midi_out_lock, flags); spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
if (usb_submit_urb(urb, GFP_KERNEL) < 0) { if (usb_submit_urb(urb, GFP_KERNEL) < 0) {
@ -1816,22 +1645,23 @@ static void tascam_midi_out_work_handler(struct work_struct *work)
clear_bit(urb_index, &tascam->midi_out_urbs_in_flight); clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
spin_unlock_irqrestore(&tascam->midi_out_lock, flags); spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
} }
/* If there's more data, try to fill another URB immediately */
more_data = (snd_rawmidi_transmit_peek(substream, &buf[0], 1) == 1);
if (more_data)
goto start_work;
} else {
spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
} }
} }
static int tascam_midi_out_open(struct snd_rawmidi_substream *substream) static int tascam_midi_out_open(struct snd_rawmidi_substream *substream)
{ {
struct tascam_card *tascam = substream->rmidi->private_data; struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_out_substream = substream; tascam->midi_out_substream = substream;
/* Initialize the MIDI output state machine. */ /* Initialize the running status state for the packet packer. */
tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN; tascam->midi_running_status = 0;
tascam->midi_out_state.running_status = 0;
/* --- NEW: Initialize queue pointers --- */
tascam->midi_out_queue_read_ptr = 0;
tascam->midi_out_queue_write_ptr = 0;
return 0; return 0;
} }
@ -1845,7 +1675,6 @@ static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream)
struct tascam_card *tascam = substream->rmidi->private_data; struct tascam_card *tascam = substream->rmidi->private_data;
int i; int i;
/* Ensure all pending work is finished and kill active URBs */
cancel_work_sync(&tascam->midi_out_work); cancel_work_sync(&tascam->midi_out_work);
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) for (i = 0; i < NUM_MIDI_OUT_URBS; i++)
usb_kill_urb(tascam->midi_out_urbs[i]); usb_kill_urb(tascam->midi_out_urbs[i]);
@ -1856,10 +1685,10 @@ static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream, int
struct tascam_card *tascam = substream->rmidi->private_data; struct tascam_card *tascam = substream->rmidi->private_data;
if (up) { if (up) {
if (atomic_xchg(&tascam->midi_out_active, 1) == 0) atomic_set(&tascam->midi_out_active, 1);
schedule_work(&tascam->midi_out_work); schedule_work(&tascam->midi_out_work);
} else { } else {
atomic_xchg(&tascam->midi_out_active, 0); atomic_set(&tascam->midi_out_active, 0);
} }
} }
@ -2063,6 +1892,7 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
spin_lock_init(&tascam->midi_out_lock); spin_lock_init(&tascam->midi_out_lock);
INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler);
tascam->midi_out_urbs_in_flight = 0; tascam->midi_out_urbs_in_flight = 0;
tascam->midi_in_has_pending_packet = false;
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname));
@ -2111,6 +1941,14 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
} }
kfree(handshake_buf); kfree(handshake_buf);
/*
* Allocate all URBs now that the device is initialized.
*/
err = tascam_alloc_urbs(tascam);
if (err < 0)
goto release_iface1_and_free_card;
err = snd_pcm_new(tascam->card, "US144MKII", 0, 1, 1, &pcm); err = snd_pcm_new(tascam->card, "US144MKII", 0, 1, 1, &pcm);
if (err < 0) if (err < 0)
goto release_iface1_and_free_card; goto release_iface1_and_free_card;
@ -2125,22 +1963,6 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
if (err < 0) if (err < 0)
goto release_iface1_and_free_card; goto release_iface1_and_free_card;
/* --- NEW: Perform initial configuration to enable MIDI --- */
/**
* The device's MIDI ports are not active until the full sample rate
* configuration sequence has been sent. We do this at probe time with
* a default rate so that MIDI can be used without first starting an
* audio stream.
*/
dev_info(&intf->dev, "Performing initial configuration for MIDI activation.\n");
err = us144mkii_configure_device_for_rate(tascam, 44100);
if (err < 0) {
dev_err(&intf->dev, "Initial device configuration failed, MIDI may not work.\n");
/* Don't fail the whole probe, as audio might still be configured later */
} else {
tascam->current_rate = 44100;
}
if (device_create_file(&intf->dev, &dev_attr_driver_version)) if (device_create_file(&intf->dev, &dev_attr_driver_version))
dev_warn(&intf->dev, "Could not create sysfs attribute for driver version\n"); dev_warn(&intf->dev, "Could not create sysfs attribute for driver version\n");
@ -2189,6 +2011,11 @@ static void tascam_disconnect(struct usb_interface *intf)
cancel_work_sync(&tascam->capture_work); cancel_work_sync(&tascam->capture_work);
cancel_work_sync(&tascam->midi_out_work); cancel_work_sync(&tascam->midi_out_work);
/*
* Free all URBs before freeing the card.
*/
tascam_free_urbs(tascam);
if (tascam->iface1) { if (tascam->iface1) {
usb_set_intfdata(tascam->iface1, NULL); usb_set_intfdata(tascam->iface1, NULL);
usb_driver_release_interface(&tascam_alsa_driver, tascam->iface1); usb_driver_release_interface(&tascam_alsa_driver, tascam->iface1);