File Index
Files and directories
Sign in
FILE

us144mkii_midi.c

File preview with syntax highlighting.

linux-6.19.14/sound/usb/usx2y/us144mkii_midi.c C
  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 */
 14static 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
 39void 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
 74out:
 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 */
 87static 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 */
101static 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 */
116static 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 */
159static 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
165void 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
204out:
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 */
218static 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 */
294static 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 */
310static 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 */
322static 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 */
351static 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 */
370static 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
377int 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}
~/ Read-only file preview.