xref: /linux/sound/usb/usx2y/us144mkii_midi.c (revision 55a42f78ffd386e01a5404419f8c5ded7db70a21)
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  */
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 
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  */
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  */
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  */
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 
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  */
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  */
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  */
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  */
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  */
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 
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