FILE
us144mkii_midi.c
File preview with syntax highlighting.
linux-6.19.14/sound/usb/usx2y/us144mkii_midi.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.