1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
3
4 #include "us144mkii.h"
5
6 /*
7 * tascam_midi_in_work_handler() - Deferred work for processing MIDI input.
8 * @work: The work_struct instance.
9 *
10 * This function runs in a thread context. It safely reads raw USB data from
11 * the kfifo, processes it by stripping protocol-specific padding bytes, and
12 * passes the clean MIDI data to the ALSA rawmidi subsystem.
13 */
tascam_midi_in_work_handler(struct work_struct * work)14 static void tascam_midi_in_work_handler(struct work_struct *work)
15 {
16 struct tascam_card *tascam =
17 container_of(work, struct tascam_card, midi_in_work);
18 u8 buf[9];
19 u8 clean_buf[8];
20 unsigned int count, clean_count;
21
22 if (!tascam->midi_in_substream)
23 return;
24
25 while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf),
26 &tascam->midi_in_lock) == sizeof(buf)) {
27 clean_count = 0;
28 for (count = 0; count < 8; ++count) {
29 if (buf[count] != 0xfd)
30 clean_buf[clean_count++] = buf[count];
31 }
32
33 if (clean_count > 0)
34 snd_rawmidi_receive(tascam->midi_in_substream,
35 clean_buf, clean_count);
36 }
37 }
38
tascam_midi_in_urb_complete(struct urb * urb)39 void tascam_midi_in_urb_complete(struct urb *urb)
40 {
41 struct tascam_card *tascam = urb->context;
42 int ret;
43
44 if (!tascam)
45 goto out;
46
47 if (urb->status) {
48 if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
49 urb->status != -ESHUTDOWN && urb->status != -EPROTO) {
50 dev_err_ratelimited(tascam->card->dev,
51 "MIDI IN URB failed: status %d\n",
52 urb->status);
53 }
54 goto out;
55 }
56
57 if (atomic_read(&tascam->midi_in_active) &&
58 urb->actual_length > 0) {
59 kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer,
60 urb->actual_length, &tascam->midi_in_lock);
61 schedule_work(&tascam->midi_in_work);
62 }
63
64 usb_get_urb(urb);
65 usb_anchor_urb(urb, &tascam->midi_in_anchor);
66 ret = usb_submit_urb(urb, GFP_ATOMIC);
67 if (ret < 0) {
68 dev_err(tascam->card->dev,
69 "Failed to resubmit MIDI IN URB: error %d\n", ret);
70 usb_unanchor_urb(urb);
71 goto out;
72 }
73
74 out:
75 usb_put_urb(urb);
76 }
77
78 /*
79 * tascam_midi_in_open() - Opens the MIDI input substream.
80 * @substream: The ALSA rawmidi substream to open.
81 *
82 * This function stores a reference to the MIDI input substream in the
83 * driver's private data.
84 *
85 * Return: 0 on success.
86 */
tascam_midi_in_open(struct snd_rawmidi_substream * substream)87 static int tascam_midi_in_open(struct snd_rawmidi_substream *substream)
88 {
89 struct tascam_card *tascam = substream->rmidi->private_data;
90
91 tascam->midi_in_substream = substream;
92 return 0;
93 }
94
95 /*
96 * tascam_midi_in_close() - Closes the MIDI input substream.
97 * @substream: The ALSA rawmidi substream to close.
98 *
99 * Return: 0 on success.
100 */
tascam_midi_in_close(struct snd_rawmidi_substream * substream)101 static int tascam_midi_in_close(struct snd_rawmidi_substream *substream)
102 {
103 return 0;
104 }
105
106 /*
107 * tascam_midi_in_trigger() - Triggers MIDI input stream activity.
108 * @substream: The ALSA rawmidi substream.
109 * @up: Boolean indicating whether to start (1) or stop (0) the stream.
110 *
111 * This function starts or stops the MIDI input URBs based on the 'up'
112 * parameter. When starting, it resets the kfifo and submits all MIDI input
113 * URBs. When stopping, it kills all anchored MIDI input URBs and cancels the
114 * associated workqueue.
115 */
tascam_midi_in_trigger(struct snd_rawmidi_substream * substream,int up)116 static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream,
117 int up)
118 {
119 struct tascam_card *tascam = substream->rmidi->private_data;
120 int i, err;
121
122 if (up) {
123 if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
124 scoped_guard(spinlock_irqsave, &tascam->midi_in_lock)
125 {
126 kfifo_reset(&tascam->midi_in_fifo);
127 }
128
129 for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
130 usb_get_urb(tascam->midi_in_urbs[i]);
131 usb_anchor_urb(tascam->midi_in_urbs[i],
132 &tascam->midi_in_anchor);
133 err = usb_submit_urb(tascam->midi_in_urbs[i],
134 GFP_KERNEL);
135 if (err < 0) {
136 dev_err(tascam->card->dev,
137 "Failed to submit MIDI IN URB %d: %d\n",
138 i, err);
139 usb_unanchor_urb(
140 tascam->midi_in_urbs[i]);
141 usb_put_urb(tascam->midi_in_urbs[i]);
142 }
143 }
144 }
145 } else {
146 if (atomic_xchg(&tascam->midi_in_active, 0) == 1) {
147 usb_kill_anchored_urbs(&tascam->midi_in_anchor);
148 cancel_work_sync(&tascam->midi_in_work);
149 }
150 }
151 }
152
153 /*
154 * tascam_midi_in_ops - ALSA rawmidi operations for MIDI input.
155 *
156 * This structure defines the callback functions for MIDI input stream
157 * operations, including open, close, and trigger.
158 */
159 static const struct snd_rawmidi_ops tascam_midi_in_ops = {
160 .open = tascam_midi_in_open,
161 .close = tascam_midi_in_close,
162 .trigger = tascam_midi_in_trigger,
163 };
164
tascam_midi_out_urb_complete(struct urb * urb)165 void tascam_midi_out_urb_complete(struct urb *urb)
166 {
167 struct tascam_card *tascam = urb->context;
168 int i, urb_index = -1;
169
170 if (urb->status) {
171 if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
172 urb->status != -ESHUTDOWN) {
173 dev_err_ratelimited(tascam->card->dev,
174 "MIDI OUT URB failed: %d\n",
175 urb->status);
176 }
177 goto out;
178 }
179
180 if (!tascam)
181 goto out;
182
183 for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
184 if (tascam->midi_out_urbs[i] == urb) {
185 urb_index = i;
186 break;
187 }
188 }
189
190 if (urb_index < 0) {
191 dev_err_ratelimited(tascam->card->dev,
192 "Unknown MIDI OUT URB completed!\n");
193 goto out;
194 }
195
196 scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
197 {
198 clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
199 }
200
201 if (atomic_read(&tascam->midi_out_active))
202 schedule_work(&tascam->midi_out_work);
203
204 out:
205 usb_put_urb(urb);
206 }
207
208 /*
209 * tascam_midi_out_work_handler() - Deferred work for sending MIDI data
210 * @work: The work_struct instance.
211 *
212 * This function handles the proprietary output protocol: take the raw MIDI
213 * message bytes from the application, place them at the start of a 9-byte
214 * buffer, pad the rest with 0xFD, and add a terminator byte (0x00).
215 * This function pulls as many bytes as will fit into one packet from the
216 * ALSA buffer and sends them.
217 */
tascam_midi_out_work_handler(struct work_struct * work)218 static void tascam_midi_out_work_handler(struct work_struct *work)
219 {
220 struct tascam_card *tascam =
221 container_of(work, struct tascam_card, midi_out_work);
222 struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
223 int i;
224
225 if (!substream || !atomic_read(&tascam->midi_out_active))
226 return;
227
228 while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) == 1) {
229 int urb_index;
230 struct urb *urb;
231 u8 *buf;
232 int bytes_to_send;
233
234 scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) {
235 urb_index = -1;
236 for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
237 if (!test_bit(
238 i,
239 &tascam->midi_out_urbs_in_flight)) {
240 urb_index = i;
241 break;
242 }
243 }
244
245 if (urb_index < 0)
246 return; /* No free URBs, will be rescheduled by
247 * completion handler
248 */
249
250 urb = tascam->midi_out_urbs[urb_index];
251 buf = urb->transfer_buffer;
252 bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
253
254 if (bytes_to_send <= 0)
255 break; /* No more data */
256
257 if (bytes_to_send < 9)
258 memset(buf + bytes_to_send, 0xfd,
259 9 - bytes_to_send);
260 buf[8] = 0xe0;
261
262 set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
263 urb->transfer_buffer_length = 9;
264 }
265
266 usb_get_urb(urb);
267 usb_anchor_urb(urb, &tascam->midi_out_anchor);
268 if (usb_submit_urb(urb, GFP_KERNEL) < 0) {
269 dev_err_ratelimited(
270 tascam->card->dev,
271 "Failed to submit MIDI OUT URB %d\n",
272 urb_index);
273 scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
274 {
275 clear_bit(urb_index,
276 &tascam->midi_out_urbs_in_flight);
277 }
278 usb_unanchor_urb(urb);
279 usb_put_urb(urb);
280 break; /* Stop on error */
281 }
282 }
283 }
284
285 /*
286 * tascam_midi_out_open() - Opens the MIDI output substream.
287 * @substream: The ALSA rawmidi substream to open.
288 *
289 * This function stores a reference to the MIDI output substream in the
290 * driver's private data and initializes the MIDI running status.
291 *
292 * Return: 0 on success.
293 */
tascam_midi_out_open(struct snd_rawmidi_substream * substream)294 static int tascam_midi_out_open(struct snd_rawmidi_substream *substream)
295 {
296 struct tascam_card *tascam = substream->rmidi->private_data;
297
298 tascam->midi_out_substream = substream;
299 /* Initialize the running status state for the packet packer. */
300 tascam->midi_running_status = 0;
301 return 0;
302 }
303
304 /*
305 * tascam_midi_out_close() - Closes the MIDI output substream.
306 * @substream: The ALSA rawmidi substream to close.
307 *
308 * Return: 0 on success.
309 */
tascam_midi_out_close(struct snd_rawmidi_substream * substream)310 static int tascam_midi_out_close(struct snd_rawmidi_substream *substream)
311 {
312 return 0;
313 }
314
315 /*
316 * tascam_midi_out_drain() - Drains the MIDI output stream.
317 * @substream: The ALSA rawmidi substream.
318 *
319 * This function cancels any pending MIDI output work and kills all
320 * anchored MIDI output URBs, ensuring all data is sent or discarded.
321 */
tascam_midi_out_drain(struct snd_rawmidi_substream * substream)322 static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream)
323 {
324 struct tascam_card *tascam = substream->rmidi->private_data;
325 bool in_flight = true;
326
327 while (in_flight) {
328 in_flight = false;
329 for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) {
330 if (test_bit(i, &tascam->midi_out_urbs_in_flight)) {
331 in_flight = true;
332 break;
333 }
334 }
335 if (in_flight)
336 schedule_timeout_uninterruptible(1);
337 }
338
339 cancel_work_sync(&tascam->midi_out_work);
340 usb_kill_anchored_urbs(&tascam->midi_out_anchor);
341 }
342
343 /*
344 * tascam_midi_out_trigger() - Triggers MIDI output stream activity.
345 * @substream: The ALSA rawmidi substream.
346 * @up: Boolean indicating whether to start (1) or stop (0) the stream.
347 *
348 * This function starts or stops the MIDI output workqueue based on the
349 * 'up' parameter.
350 */
tascam_midi_out_trigger(struct snd_rawmidi_substream * substream,int up)351 static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream,
352 int up)
353 {
354 struct tascam_card *tascam = substream->rmidi->private_data;
355
356 if (up) {
357 atomic_set(&tascam->midi_out_active, 1);
358 schedule_work(&tascam->midi_out_work);
359 } else {
360 atomic_set(&tascam->midi_out_active, 0);
361 }
362 }
363
364 /*
365 * tascam_midi_out_ops - ALSA rawmidi operations for MIDI output.
366 *
367 * This structure defines the callback functions for MIDI output stream
368 * operations, including open, close, trigger, and drain.
369 */
370 static const struct snd_rawmidi_ops tascam_midi_out_ops = {
371 .open = tascam_midi_out_open,
372 .close = tascam_midi_out_close,
373 .trigger = tascam_midi_out_trigger,
374 .drain = tascam_midi_out_drain,
375 };
376
tascam_create_midi(struct tascam_card * tascam)377 int tascam_create_midi(struct tascam_card *tascam)
378 {
379 int err;
380
381 err = snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1,
382 &tascam->rmidi);
383 if (err < 0)
384 return err;
385
386 strscpy(tascam->rmidi->name, "US144MKII MIDI",
387 sizeof(tascam->rmidi->name));
388 tascam->rmidi->private_data = tascam;
389
390 snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
391 &tascam_midi_in_ops);
392 snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
393 &tascam_midi_out_ops);
394
395 tascam->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
396 SNDRV_RAWMIDI_INFO_OUTPUT |
397 SNDRV_RAWMIDI_INFO_DUPLEX;
398
399 INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler);
400 INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler);
401
402 return 0;
403 }
404