1 | /*
|
---|
2 | * midi usb driver
|
---|
3 | * usb_midi.c
|
---|
4 | *
|
---|
5 | * Copyright 2006-2011 Haiku Inc. All rights reserved.
|
---|
6 | * Distributed under the terms of the MIT Licence.
|
---|
7 | *
|
---|
8 | * Authors:
|
---|
9 | * Jérôme Duval
|
---|
10 | * Pete Goodeve, pete.goodeve@computer.org
|
---|
11 | *
|
---|
12 | * Some portions of this code were originally derived from
|
---|
13 | * USB Joystick driver for BeOS R5
|
---|
14 | * Copyright 2000 (C) ITO, Takayuki
|
---|
15 | * All rights reserved
|
---|
16 | *
|
---|
17 | */
|
---|
18 |
|
---|
19 |
|
---|
20 | /* #define DEBUG 1 */ /* Define this to enable DPRINTF_DEBUG statements */
|
---|
21 | /* (Other categories of printout set in usb_midi.h) */
|
---|
22 |
|
---|
23 | #include "usb_midi.h"
|
---|
24 |
|
---|
25 | #include <stdio.h>
|
---|
26 | #include <stdlib.h>
|
---|
27 | #include <string.h>
|
---|
28 | #include <unistd.h>
|
---|
29 |
|
---|
30 |
|
---|
31 | const char* midi_base_name = "midi/usb/";
|
---|
32 |
|
---|
33 |
|
---|
34 | usbmidi_port_info*
|
---|
35 | create_usbmidi_port(usbmidi_device_info* devinfo,
|
---|
36 | int cable, bool has_in, bool has_out)
|
---|
37 | {
|
---|
38 | usbmidi_port_info* port = NULL;
|
---|
39 | assert(usb != NULL && devinfo != NULL);
|
---|
40 |
|
---|
41 | port = malloc(sizeof(usbmidi_port_info));
|
---|
42 | if (port == NULL)
|
---|
43 | return NULL;
|
---|
44 |
|
---|
45 | sprintf(port->name, "%s-%d", devinfo->name, cable);
|
---|
46 | port->device = devinfo;
|
---|
47 | port->cable = cable;
|
---|
48 | port->next = NULL;
|
---|
49 | port->open = 0;
|
---|
50 | port->open_fd = NULL;
|
---|
51 | port->has_in = has_in;
|
---|
52 | port->has_out = has_out;
|
---|
53 | port->rbuf = create_ring_buffer(1024);
|
---|
54 |
|
---|
55 | devinfo->ports[cable] = port;
|
---|
56 |
|
---|
57 | DPRINTF_INFO((MY_ID "Created port %p cable %d: %s\n",
|
---|
58 | port, cable, port->name));
|
---|
59 |
|
---|
60 | return port;
|
---|
61 | }
|
---|
62 |
|
---|
63 | void
|
---|
64 | remove_port(usbmidi_port_info* port)
|
---|
65 | {
|
---|
66 | assert(port != NULL);
|
---|
67 | if (port->rbuf != NULL) {
|
---|
68 | delete_ring_buffer(port->rbuf);
|
---|
69 | port->rbuf = NULL;
|
---|
70 | }
|
---|
71 | DPRINTF_INFO((MY_ID "remove_port %p done\n", port));
|
---|
72 |
|
---|
73 | free(port);
|
---|
74 | }
|
---|
75 |
|
---|
76 |
|
---|
77 | usbmidi_device_info*
|
---|
78 | create_device(const usb_device* dev, uint16 ifno)
|
---|
79 | {
|
---|
80 | usbmidi_device_info* midiDevice = NULL;
|
---|
81 | int number;
|
---|
82 | area_id area;
|
---|
83 | sem_id sem;
|
---|
84 | char area_name[32];
|
---|
85 |
|
---|
86 | assert(usb != NULL && dev != NULL);
|
---|
87 |
|
---|
88 | number = find_free_device_number();
|
---|
89 |
|
---|
90 | midiDevice = malloc(sizeof(usbmidi_device_info));
|
---|
91 | if (midiDevice == NULL)
|
---|
92 | return NULL;
|
---|
93 |
|
---|
94 | midiDevice->sem_lock = sem = create_sem(1, DRIVER_NAME "_lock");
|
---|
95 | if (sem < 0) {
|
---|
96 | DPRINTF_ERR((MY_ID "create_sem() failed %d\n", (int)sem));
|
---|
97 | free(midiDevice);
|
---|
98 | return NULL;
|
---|
99 | }
|
---|
100 |
|
---|
101 | sprintf(area_name, DRIVER_NAME "_buffer%d", number);
|
---|
102 | midiDevice->buffer_area = area = create_area(area_name,
|
---|
103 | (void**)&midiDevice->buffer, B_ANY_KERNEL_ADDRESS,
|
---|
104 | B_PAGE_SIZE, B_CONTIGUOUS, B_READ_AREA | B_WRITE_AREA);
|
---|
105 | if (area < 0) {
|
---|
106 | DPRINTF_ERR((MY_ID "create_area() failed %d\n", (int)area));
|
---|
107 | delete_sem(midiDevice->sem_lock);
|
---|
108 | free(midiDevice);
|
---|
109 | return NULL;
|
---|
110 | }
|
---|
111 | /* use half of reserved area for each of in and out buffers: */
|
---|
112 | midiDevice->out_buffer
|
---|
113 | = (usb_midi_event_packet*)((uint8*)midiDevice->buffer + B_PAGE_SIZE/2);
|
---|
114 | midiDevice->sem_send = sem = create_sem(1, DRIVER_NAME "_send");
|
---|
115 | if (sem < 0) {
|
---|
116 | DPRINTF_ERR((MY_ID "create_sem() failed %d\n", (int)sem));
|
---|
117 | delete_sem(midiDevice->sem_lock);
|
---|
118 | delete_area(area);
|
---|
119 | free(midiDevice);
|
---|
120 | return NULL;
|
---|
121 | }
|
---|
122 | {
|
---|
123 | int32 bc;
|
---|
124 | get_sem_count(sem, &bc);
|
---|
125 | DPRINTF_DEBUG((MY_ID "Allocated %ld write buffers\n", bc));
|
---|
126 | }
|
---|
127 |
|
---|
128 |
|
---|
129 | sprintf(midiDevice->name, "%s%d", midi_base_name, number);
|
---|
130 | midiDevice->dev = dev;
|
---|
131 | midiDevice->devnum = number;
|
---|
132 | midiDevice->ifno = ifno;
|
---|
133 | midiDevice->active = true;
|
---|
134 | midiDevice->flags = 0;
|
---|
135 | memset(midiDevice->ports, 0, sizeof(midiDevice->ports));
|
---|
136 | midiDevice->buffer_size = B_PAGE_SIZE/2;
|
---|
137 | DPRINTF_INFO((MY_ID "Created device %p\n", midiDevice));
|
---|
138 |
|
---|
139 | return midiDevice;
|
---|
140 | }
|
---|
141 |
|
---|
142 |
|
---|
143 | void
|
---|
144 | remove_device(usbmidi_device_info* midiDevice)
|
---|
145 | {
|
---|
146 | assert(midiDevice != NULL);
|
---|
147 | DPRINTF_INFO((MY_ID "remove_device %p\n", midiDevice));
|
---|
148 |
|
---|
149 | delete_area(midiDevice->buffer_area);
|
---|
150 | delete_sem(midiDevice->sem_lock);
|
---|
151 | delete_sem(midiDevice->sem_send);
|
---|
152 | free(midiDevice);
|
---|
153 | }
|
---|
154 |
|
---|
155 |
|
---|
156 | /* driver cookie (per open -- but only one open per port allowed!) */
|
---|
157 |
|
---|
158 | typedef struct driver_cookie
|
---|
159 | {
|
---|
160 | struct driver_cookie* next;
|
---|
161 | usbmidi_device_info* device; /* a bit redundant, but convenient */
|
---|
162 | usbmidi_port_info* port;
|
---|
163 | sem_id sem_cb;
|
---|
164 | } driver_cookie;
|
---|
165 |
|
---|
166 |
|
---|
167 | /* NB global variables are valid only while driver is loaded */
|
---|
168 |
|
---|
169 | _EXPORT int32 api_version = B_CUR_DRIVER_API_VERSION;
|
---|
170 |
|
---|
171 | const char* usb_midi_driver_name = "usb_midi";
|
---|
172 |
|
---|
173 | const int CINbytes[] = { /* See USB-MIDI Spec */
|
---|
174 | 0, /* 0x0 -- undefined Misc -- Reserved */
|
---|
175 | 0, /* 0x1 -- undefined Cable -- Reserved */
|
---|
176 | 2, /* 0x2 -- 2-byte system common */
|
---|
177 | 3, /* 0x3 -- 3-byte system common */
|
---|
178 | 3, /* 0x4 -- SysEx start/continue */
|
---|
179 | 1, /* 0x5 -- SysEx single-byte-end, or 1-byte Common */
|
---|
180 | 2, /* 0x6 -- SysEx two-byte-end */
|
---|
181 | 3, /* 0x7 -- SysEx three-byte-end */
|
---|
182 | 3, /* 0x8 -- Note Off */
|
---|
183 | 3, /* 0x9 -- Note On */
|
---|
184 | 3, /* 0xA -- Poly KeyPress*/
|
---|
185 | 3, /* 0xB -- Control Change */
|
---|
186 | 2, /* 0xC -- Program Change */
|
---|
187 | 2, /* 0xD -- Channel Pressure */
|
---|
188 | 3, /* 0xE -- Pitch Bend */
|
---|
189 | 1, /* 0xF -- Single Byte */
|
---|
190 | };
|
---|
191 |
|
---|
192 | usb_module_info* usb;
|
---|
193 |
|
---|
194 |
|
---|
195 | static void
|
---|
196 | interpret_midi_buffer(usbmidi_device_info* midiDevice)
|
---|
197 | {
|
---|
198 | usb_midi_event_packet* packet = midiDevice->buffer;
|
---|
199 | size_t bytes_left = midiDevice->actual_length;
|
---|
200 | while (bytes_left) { /* buffer may have several packets */
|
---|
201 | int pktlen = CINbytes[packet->cin];
|
---|
202 | usbmidi_port_info* port = midiDevice->ports[packet->cn];
|
---|
203 | /* port matching 'cable' */
|
---|
204 | DPRINTF_DEBUG((MY_ID "received packet %x:%d %x %x %x\n",
|
---|
205 | packet->cin, packet->cn,
|
---|
206 | packet->midi[0], packet->midi[1], packet->midi[2]));
|
---|
207 | ring_buffer_write(port->rbuf, packet->midi, pktlen);
|
---|
208 | release_sem_etc(port->open_fd->sem_cb, pktlen, B_DO_NOT_RESCHEDULE);
|
---|
209 | packet++;
|
---|
210 | bytes_left -= sizeof(usb_midi_event_packet);
|
---|
211 | }
|
---|
212 | }
|
---|
213 |
|
---|
214 |
|
---|
215 | /*
|
---|
216 | callback: got a report, issue next request
|
---|
217 | */
|
---|
218 |
|
---|
219 | static void
|
---|
220 | midi_usb_read_callback(void* cookie, status_t status,
|
---|
221 | void* data, size_t actual_len)
|
---|
222 | {
|
---|
223 | status_t st;
|
---|
224 | usbmidi_device_info* midiDevice = cookie;
|
---|
225 |
|
---|
226 | assert(cookie != NULL);
|
---|
227 | DPRINTF_DEBUG((MY_ID "midi_usb_read_callback() -- packet length %ld\n",
|
---|
228 | actual_len));
|
---|
229 |
|
---|
230 | acquire_sem(midiDevice->sem_lock);
|
---|
231 | midiDevice->actual_length = actual_len;
|
---|
232 | midiDevice->bus_status = status; /* B_USB_STATUS_* */
|
---|
233 | if (status != B_OK) {
|
---|
234 | /* request failed */
|
---|
235 | DPRINTF_DEBUG((MY_ID "bus status %d\n", (int)status));
|
---|
236 | if (status == B_CANCELED || !midiDevice->active) {
|
---|
237 | /* cancelled: device is unplugged */
|
---|
238 | usbmidi_port_info** port = midiDevice->ports;
|
---|
239 | DPRINTF_DEBUG((MY_ID "midi_usb_read_callback: cancelled"
|
---|
240 | "(status=%lx active=%d -- deleting sem_cbs\n",
|
---|
241 | status, midiDevice->active));
|
---|
242 | while (*port) {
|
---|
243 | delete_sem((*port)->open_fd->sem_cb);
|
---|
244 | /* done here to ensure read is freed */
|
---|
245 | port++;
|
---|
246 | }
|
---|
247 | release_sem(midiDevice->sem_lock);
|
---|
248 | return;
|
---|
249 | }
|
---|
250 | release_sem(midiDevice->sem_lock);
|
---|
251 | } else {
|
---|
252 | /* got a report */
|
---|
253 | midiDevice->timestamp = system_time(); /* not used... */
|
---|
254 |
|
---|
255 | interpret_midi_buffer(midiDevice);
|
---|
256 | release_sem(midiDevice->sem_lock);
|
---|
257 | }
|
---|
258 |
|
---|
259 | /* issue next request */
|
---|
260 | st = usb->queue_bulk(midiDevice->ept_in->handle,
|
---|
261 | midiDevice->buffer, midiDevice->buffer_size,
|
---|
262 | (usb_callback_func)midi_usb_read_callback, midiDevice);
|
---|
263 | if (st != B_OK) {
|
---|
264 | /* probably endpoint stall */
|
---|
265 | DPRINTF_ERR((MY_ID "queue_bulk() error %d\n", (int)st));
|
---|
266 | }
|
---|
267 | }
|
---|
268 |
|
---|
269 |
|
---|
270 | static void
|
---|
271 | midi_usb_write_callback(void* cookie, status_t status,
|
---|
272 | void* data, size_t actual_len)
|
---|
273 | {
|
---|
274 | usbmidi_device_info* midiDevice = cookie;
|
---|
275 | #ifdef DEBUG
|
---|
276 | usb_midi_event_packet* pkt = data;
|
---|
277 | #endif
|
---|
278 |
|
---|
279 | assert(cookie != NULL);
|
---|
280 | DPRINTF_DEBUG((MY_ID "midi_usb_write_callback()"
|
---|
281 | " status %ld length %ld pkt %p cin %x\n",
|
---|
282 | status, actual_len, pkt, pkt->cin));
|
---|
283 | release_sem(midiDevice->sem_send); /* done with buffer */
|
---|
284 | }
|
---|
285 |
|
---|
286 |
|
---|
287 | /*
|
---|
288 | USB specific device hooks
|
---|
289 | */
|
---|
290 |
|
---|
291 | static status_t
|
---|
292 | usb_midi_added(const usb_device* dev, void** cookie)
|
---|
293 | {
|
---|
294 | usbmidi_device_info* midiDevice;
|
---|
295 | usbmidi_port_info* port;
|
---|
296 | const usb_device_descriptor* dev_desc;
|
---|
297 | const usb_configuration_info* conf;
|
---|
298 | const usb_interface_info* intf;
|
---|
299 | status_t st;
|
---|
300 | uint16 ifno, i;
|
---|
301 | int alt;
|
---|
302 |
|
---|
303 | /* This seems overcomplicated, but endpoints can be in either order...
|
---|
304 | and could possibly have different number of connectors! */
|
---|
305 | int in_cables = 0, out_cables = 0;
|
---|
306 | int cable_count[2] = {0, 0};
|
---|
307 | int iep = 0;
|
---|
308 |
|
---|
309 | assert(dev != NULL && cookie != NULL);
|
---|
310 | DPRINTF_INFO((MY_ID "usb_midi_added(%p, %p)\n", dev, cookie));
|
---|
311 |
|
---|
312 | dev_desc = usb->get_device_descriptor(dev);
|
---|
313 |
|
---|
314 | DPRINTF_INFO((MY_ID "vendor ID 0x%04X, product ID 0x%04X\n",
|
---|
315 | dev_desc->vendor_id, dev_desc->product_id));
|
---|
316 |
|
---|
317 | /* check interface class */
|
---|
318 |
|
---|
319 | if ((conf = usb->get_nth_configuration(dev, DEFAULT_CONFIGURATION))
|
---|
320 | == NULL) {
|
---|
321 | DPRINTF_ERR((MY_ID "cannot get default configuration\n"));
|
---|
322 | return B_ERROR;
|
---|
323 | }
|
---|
324 | DPRINTF_INFO((MY_ID "Interface count = %ld\n", conf->interface_count));
|
---|
325 |
|
---|
326 | for (ifno = 0; ifno < conf->interface_count; ifno++) {
|
---|
327 | int devclass, subclass, protocol;
|
---|
328 |
|
---|
329 | for (alt = 0; alt < conf->interface[ifno].alt_count; alt++) {
|
---|
330 | intf = &conf->interface[ifno].alt[alt];
|
---|
331 | devclass = intf->descr->interface_class;
|
---|
332 | subclass = intf->descr->interface_subclass;
|
---|
333 | protocol = intf->descr->interface_protocol;
|
---|
334 | DPRINTF_INFO((
|
---|
335 | MY_ID "interface %d, alt : %d: class %d,"
|
---|
336 | " subclass %d, protocol %d\n",
|
---|
337 | ifno, alt, devclass, subclass, protocol));
|
---|
338 |
|
---|
339 | if (devclass == USB_AUDIO_DEVICE_CLASS
|
---|
340 | && subclass == USB_AUDIO_INTERFACE_MIDISTREAMING_SUBCLASS)
|
---|
341 | goto got_one;
|
---|
342 | }
|
---|
343 | }
|
---|
344 |
|
---|
345 | DPRINTF_INFO((MY_ID "Midi interface not found\n"));
|
---|
346 | return B_ERROR;
|
---|
347 |
|
---|
348 | got_one:
|
---|
349 |
|
---|
350 | if ((st = usb->set_configuration(dev, conf)) != B_OK) {
|
---|
351 | DPRINTF_ERR((MY_ID "set_configuration() failed %d\n", (int)st));
|
---|
352 | return B_ERROR;
|
---|
353 | }
|
---|
354 |
|
---|
355 | if ((midiDevice = create_device(dev, ifno)) == NULL) {
|
---|
356 | return B_ERROR;
|
---|
357 | }
|
---|
358 |
|
---|
359 | /* get the actual number of ports in and out */
|
---|
360 | for (i = 0; i < intf->generic_count; i++) {
|
---|
361 | usb_generic_descriptor *generic = &intf->generic[i]->generic;
|
---|
362 | DPRINTF_DEBUG((MY_ID "descriptor %d: type %x sub %x\n",
|
---|
363 | i, generic->descriptor_type, generic->data[0]));
|
---|
364 | if (generic->descriptor_type == USB_DESCRIPTOR_CS_ENDPOINT
|
---|
365 | && generic->data[0] == USB_MS_GENERAL_DESCRIPTOR) {
|
---|
366 | /* These *better* be in the same order as the endpoints! */
|
---|
367 | cable_count[iep] = generic->data[1];
|
---|
368 | iep = 1;
|
---|
369 | }
|
---|
370 | }
|
---|
371 |
|
---|
372 | DPRINTF_DEBUG((MY_ID "midiDevice = %p endpoint count = %ld\n",
|
---|
373 | midiDevice, intf->endpoint_count));
|
---|
374 | midiDevice->ept_in = midiDevice->ept_out = NULL;
|
---|
375 |
|
---|
376 | for (i=0; i < intf->endpoint_count && i < 2; i++) {
|
---|
377 | /* we are actually assuming max one IN, one OUT endpoint... */
|
---|
378 | DPRINTF_INFO((MY_ID "endpoint %d = %p %s\n",
|
---|
379 | i, &intf->endpoint[i],
|
---|
380 | (intf->endpoint[i].descr->endpoint_address & 0x80) != 0
|
---|
381 | ? "IN" : "OUT"));
|
---|
382 | if ((intf->endpoint[i].descr->endpoint_address & 0x80) != 0) {
|
---|
383 | if (midiDevice->ept_in == NULL) {
|
---|
384 | midiDevice->ept_in = &intf->endpoint[i];
|
---|
385 | in_cables = cable_count[i];
|
---|
386 | }
|
---|
387 | } else if (midiDevice->ept_out == NULL) {
|
---|
388 | midiDevice->ept_out = &intf->endpoint[i];
|
---|
389 | out_cables = cable_count[i];
|
---|
390 | }
|
---|
391 | }
|
---|
392 |
|
---|
393 | midiDevice->timestamp = system_time(); /* This never seems to be used */
|
---|
394 |
|
---|
395 | /* Create the actual device ports */
|
---|
396 | for (i=0; in_cables || out_cables; i++) {
|
---|
397 | port = create_usbmidi_port(midiDevice, i,
|
---|
398 | (bool)in_cables, (bool)out_cables);
|
---|
399 | midiDevice->ports[i] = port;
|
---|
400 | if (in_cables) in_cables--;
|
---|
401 | if (out_cables) out_cables--;
|
---|
402 | add_port_info(port);
|
---|
403 | }
|
---|
404 |
|
---|
405 | /* issue bulk transfer */
|
---|
406 | DPRINTF_DEBUG((MY_ID "queueing bulk xfer IN endpoint\n"));
|
---|
407 | st = usb->queue_bulk(midiDevice->ept_in->handle, midiDevice->buffer,
|
---|
408 | midiDevice->buffer_size,
|
---|
409 | (usb_callback_func)midi_usb_read_callback, midiDevice);
|
---|
410 | if (st != B_OK) {
|
---|
411 | DPRINTF_ERR((MY_ID "queue_bulk() error %d\n", (int)st));
|
---|
412 | return B_ERROR;
|
---|
413 | }
|
---|
414 |
|
---|
415 |
|
---|
416 | *cookie = midiDevice;
|
---|
417 | DPRINTF_INFO((MY_ID "usb_midi_added: %s\n", midiDevice->name));
|
---|
418 |
|
---|
419 | return B_OK;
|
---|
420 | }
|
---|
421 |
|
---|
422 |
|
---|
423 | static status_t
|
---|
424 | usb_midi_removed(void* cookie)
|
---|
425 | {
|
---|
426 | usbmidi_device_info* midiDevice = cookie;
|
---|
427 | int cable;
|
---|
428 |
|
---|
429 | assert(cookie != NULL);
|
---|
430 |
|
---|
431 | DPRINTF_INFO((MY_ID "usb_midi_removed(%s)\n", midiDevice->name));
|
---|
432 | midiDevice->active = false;
|
---|
433 | for (cable=0; cable < 16; cable++) {
|
---|
434 | usbmidi_port_info* port = midiDevice->ports[cable];
|
---|
435 | DPRINTF_DEBUG((MY_ID "removing port %d\n", cable));
|
---|
436 | if (!port) break;
|
---|
437 | if (port->open_fd) {
|
---|
438 | remove_port_info(port);
|
---|
439 | port->open_fd->port = NULL;
|
---|
440 | port->open_fd->device = NULL;
|
---|
441 | delete_sem(port->open_fd->sem_cb);
|
---|
442 | /* done here to ensure read is freed */
|
---|
443 | }
|
---|
444 | remove_port(port);
|
---|
445 | }
|
---|
446 | usb->cancel_queued_transfers(midiDevice->ept_in->handle);
|
---|
447 | usb->cancel_queued_transfers(midiDevice->ept_out->handle);
|
---|
448 | DPRINTF_DEBUG((MY_ID "usb_midi_removed: doing remove: %s\n",
|
---|
449 | midiDevice->name));
|
---|
450 | remove_device(midiDevice);
|
---|
451 | return B_OK;
|
---|
452 | }
|
---|
453 |
|
---|
454 |
|
---|
455 | static usb_notify_hooks my_notify_hooks =
|
---|
456 | {
|
---|
457 | usb_midi_added, usb_midi_removed
|
---|
458 | };
|
---|
459 |
|
---|
460 | #define SUPPORTED_DEVICES 1
|
---|
461 | usb_support_descriptor my_supported_devices[SUPPORTED_DEVICES] =
|
---|
462 | {
|
---|
463 | {
|
---|
464 | USB_AUDIO_DEVICE_CLASS,
|
---|
465 | USB_AUDIO_INTERFACE_MIDISTREAMING_SUBCLASS,
|
---|
466 | 0, 0, 0
|
---|
467 | },
|
---|
468 | };
|
---|
469 |
|
---|
470 |
|
---|
471 | /*
|
---|
472 | Device Driver Hook Functions
|
---|
473 | -- open, read, write, close, and free
|
---|
474 | */
|
---|
475 |
|
---|
476 | static status_t
|
---|
477 | usb_midi_open(const char* name, uint32 flags,
|
---|
478 | driver_cookie** out_cookie)
|
---|
479 | {
|
---|
480 | driver_cookie* cookie;
|
---|
481 | usbmidi_port_info* port;
|
---|
482 | int mode = flags & O_RWMASK;
|
---|
483 |
|
---|
484 | assert(name != NULL);
|
---|
485 | assert(out_cookie != NULL);
|
---|
486 | DPRINTF_INFO((MY_ID "usb_midi_open(%s) flags=%lx\n", name, flags));
|
---|
487 |
|
---|
488 | if ((port = search_port_info(name)) == NULL)
|
---|
489 | return B_ENTRY_NOT_FOUND;
|
---|
490 |
|
---|
491 | if (!port->has_in && mode != O_RDONLY)
|
---|
492 | return B_PERMISSION_DENIED; /* == EACCES */
|
---|
493 | else if (!port->has_out && mode != O_WRONLY)
|
---|
494 | return B_PERMISSION_DENIED;
|
---|
495 |
|
---|
496 | if ((cookie = malloc(sizeof(driver_cookie))) == NULL)
|
---|
497 | return B_NO_MEMORY;
|
---|
498 |
|
---|
499 | cookie->sem_cb = create_sem(0, DRIVER_NAME "_cb");
|
---|
500 | if (cookie->sem_cb < 0) {
|
---|
501 | DPRINTF_ERR((MY_ID "create_sem() failed %d\n", (int)cookie->sem_cb));
|
---|
502 | free(cookie);
|
---|
503 | return B_ERROR;
|
---|
504 | }
|
---|
505 |
|
---|
506 | cookie->port = port;
|
---|
507 | cookie->device = port->device;
|
---|
508 |
|
---|
509 | acquire_sem(usbmidi_port_list_lock);
|
---|
510 | if (port->open_fd != NULL) {
|
---|
511 | /* there can only be one open channel to the device */
|
---|
512 | delete_sem(cookie->sem_cb);
|
---|
513 | free(cookie);
|
---|
514 | release_sem(usbmidi_port_list_lock);
|
---|
515 | return B_BUSY;
|
---|
516 | }
|
---|
517 | port->open_fd = cookie;
|
---|
518 | port->open++;
|
---|
519 | release_sem(usbmidi_port_list_lock);
|
---|
520 |
|
---|
521 | *out_cookie = cookie;
|
---|
522 | DPRINTF_INFO((MY_ID "usb_midi_open: device %s open (%d)\n",
|
---|
523 | name, port->open));
|
---|
524 | return B_OK;
|
---|
525 | }
|
---|
526 |
|
---|
527 |
|
---|
528 | static status_t
|
---|
529 | usb_midi_read(driver_cookie* cookie, off_t position,
|
---|
530 | void* buf, size_t* num_bytes)
|
---|
531 | {
|
---|
532 | status_t err = B_ERROR;
|
---|
533 | usbmidi_port_info* port;
|
---|
534 | usbmidi_device_info* midiDevice;
|
---|
535 |
|
---|
536 | assert(cookie != NULL);
|
---|
537 | port = cookie->port;
|
---|
538 | midiDevice = cookie->device;
|
---|
539 |
|
---|
540 | if (!midiDevice || !midiDevice->active)
|
---|
541 | return B_ERROR; /* already unplugged */
|
---|
542 |
|
---|
543 | DPRINTF_DEBUG((MY_ID "usb_midi_read: (%ld byte buffer at %ld cookie %p)\n",
|
---|
544 | *num_bytes, (int32)position, cookie));
|
---|
545 | while (midiDevice && midiDevice->active) {
|
---|
546 | DPRINTF_DEBUG((MY_ID "waiting on acquire_sem_etc\n"));
|
---|
547 | err = acquire_sem_etc(cookie->sem_cb, 1,
|
---|
548 | B_RELATIVE_TIMEOUT, 1000000);
|
---|
549 | if (err == B_TIMED_OUT) {
|
---|
550 | DPRINTF_DEBUG((MY_ID "acquire_sem_etc timed out\n"));
|
---|
551 | continue; /* see if we're still active */
|
---|
552 | }
|
---|
553 | if (err != B_OK) {
|
---|
554 | *num_bytes = 0;
|
---|
555 | DPRINTF_DEBUG((MY_ID "acquire_sem_etc aborted\n"));
|
---|
556 | break;
|
---|
557 | }
|
---|
558 | DPRINTF_DEBUG((MY_ID "reading from ringbuffer\n"));
|
---|
559 | acquire_sem(midiDevice->sem_lock);
|
---|
560 | /* a global semaphore -- OK, I think */
|
---|
561 | ring_buffer_user_read(port->rbuf, buf, 1);
|
---|
562 | release_sem(midiDevice->sem_lock);
|
---|
563 | *num_bytes = 1;
|
---|
564 | DPRINTF_DEBUG((MY_ID "read byte %x -- cookie %p)\n",
|
---|
565 | *(uint8*)buf, cookie));
|
---|
566 | return B_OK;
|
---|
567 | }
|
---|
568 | DPRINTF_INFO((MY_ID "usb_midi_read: loop terminated"
|
---|
569 | " -- Device no longer active\n"));
|
---|
570 | return B_CANCELED;
|
---|
571 | }
|
---|
572 |
|
---|
573 |
|
---|
574 | const uint8 CINcode[] = { /* see USB-MIDI Spec */
|
---|
575 | 0x4, /* 0x0 - sysex start */
|
---|
576 | 0, /* 0x1 -- undefined */
|
---|
577 | 0x3, /* 0x2 -- song pos */
|
---|
578 | 0x2, /* 0x3 -- song select */
|
---|
579 | 0, /* 0x4 -- undefined */
|
---|
580 | 0, /* 0x5 -- undefined */
|
---|
581 | 0x2, /* 0x6 -- tune request */
|
---|
582 | 0x5, /* 0x7 -- sysex end */
|
---|
583 | 0x5, /* 0x8 -- clock */
|
---|
584 | 0, /* 0x9 -- undefined */
|
---|
585 | 0x5, /* 0xA -- start */
|
---|
586 | 0x5, /* 0xB -- continue */
|
---|
587 | 0x5, /* 0xC -- stop */
|
---|
588 | 0, /* 0xD -- undefined */
|
---|
589 | 0x5, /* 0xE -- active sensing */
|
---|
590 | 0x5, /* 0x0 -- system reset */
|
---|
591 | };
|
---|
592 |
|
---|
593 |
|
---|
594 | static status_t
|
---|
595 | usb_midi_write(driver_cookie* cookie, off_t position,
|
---|
596 | const void* buf, size_t* num_bytes)
|
---|
597 | {
|
---|
598 | usbmidi_port_info* port;
|
---|
599 | usbmidi_device_info* midiDevice;
|
---|
600 | uint8* midiseq = (uint8*)buf;
|
---|
601 | uint8 midicode = midiseq[0]; /* preserved for reference */
|
---|
602 | status_t st;
|
---|
603 | size_t bytes_left = *num_bytes;
|
---|
604 | size_t buff_lim;
|
---|
605 | uint8 cin = ((midicode & 0xF0) == 0xF0) ? CINcode[midicode & 0x0F]
|
---|
606 | : (midicode >> 4);
|
---|
607 |
|
---|
608 | assert(cookie != NULL);
|
---|
609 | port = cookie->port;
|
---|
610 | midiDevice = cookie->device;
|
---|
611 |
|
---|
612 | if (!midiDevice || !midiDevice->active)
|
---|
613 | return B_ERROR; /* already unplugged */
|
---|
614 |
|
---|
615 | buff_lim = midiDevice->buffer_size * 3 / 4;
|
---|
616 | /* max MIDI bytes buffer space */
|
---|
617 |
|
---|
618 | DPRINTF_DEBUG((MY_ID "MIDI write (%ld bytes at %Ld)\n",
|
---|
619 | *num_bytes, position));
|
---|
620 | if (*num_bytes > 3 && midicode != 0xF0) {
|
---|
621 | DPRINTF_ERR((MY_ID "Non-SysEx packet of %ld bytes"
|
---|
622 | " -- too big to handle\n", *num_bytes));
|
---|
623 | return B_ERROR;
|
---|
624 | }
|
---|
625 |
|
---|
626 | while (bytes_left) {
|
---|
627 | size_t xfer_bytes = (bytes_left < buff_lim) ? bytes_left : buff_lim;
|
---|
628 | usb_midi_event_packet* pkt = midiDevice->out_buffer;
|
---|
629 | int packet_count = 0;
|
---|
630 |
|
---|
631 | st = acquire_sem_etc(midiDevice->sem_send,
|
---|
632 | 1, B_RELATIVE_TIMEOUT, 2000000LL);
|
---|
633 | if (st != B_OK)
|
---|
634 | return st;
|
---|
635 |
|
---|
636 | while (xfer_bytes) {
|
---|
637 | uint8 pkt_bytes = CINbytes[cin];
|
---|
638 | memset(pkt, 0, sizeof(usb_midi_event_packet));
|
---|
639 | pkt->cin = cin;
|
---|
640 | pkt->cn = port->cable;
|
---|
641 | DPRINTF_DEBUG((MY_ID "using packet data (code %x -- %d bytes)"
|
---|
642 | " %x %x %x\n", pkt->cin, CINbytes[pkt->cin],
|
---|
643 | midiseq[0], midiseq[1], midiseq[2]));
|
---|
644 | memcpy(pkt->midi, midiseq, pkt_bytes);
|
---|
645 | DPRINTF_DEBUG((MY_ID "built packet %p %x:%d %x %x %x\n",
|
---|
646 | pkt, pkt->cin, pkt->cn,
|
---|
647 | pkt->midi[0], pkt->midi[1], pkt->midi[2]));
|
---|
648 | xfer_bytes -= pkt_bytes;
|
---|
649 | bytes_left -= pkt_bytes;
|
---|
650 | midiseq += pkt_bytes;
|
---|
651 | packet_count++;
|
---|
652 | pkt++;
|
---|
653 | if (midicode == 0xF0 && bytes_left < 4) cin = 4 + bytes_left;
|
---|
654 | /* see USB-MIDI Spec */
|
---|
655 | }
|
---|
656 | st = usb->queue_bulk(midiDevice->ept_out->handle,
|
---|
657 | midiDevice->out_buffer,
|
---|
658 | sizeof(usb_midi_event_packet) * packet_count,
|
---|
659 | (usb_callback_func)midi_usb_write_callback, midiDevice);
|
---|
660 | if (st != B_OK) {
|
---|
661 | DPRINTF_ERR((MY_ID "midi write queue_bulk() error %d\n", (int)st));
|
---|
662 | return B_ERROR;
|
---|
663 | }
|
---|
664 | }
|
---|
665 | return B_OK;
|
---|
666 | }
|
---|
667 |
|
---|
668 |
|
---|
669 | static status_t
|
---|
670 | usb_midi_control(void* cookie, uint32 iop,
|
---|
671 | void* data, size_t len)
|
---|
672 | {
|
---|
673 | return B_ERROR;
|
---|
674 | }
|
---|
675 |
|
---|
676 |
|
---|
677 | static status_t
|
---|
678 | usb_midi_close(driver_cookie* cookie)
|
---|
679 | {
|
---|
680 | usbmidi_port_info* port;
|
---|
681 | usbmidi_device_info* midiDevice;
|
---|
682 |
|
---|
683 | assert(cookie != NULL);
|
---|
684 | delete_sem(cookie->sem_cb);
|
---|
685 | port = cookie->port;
|
---|
686 | midiDevice = cookie->device;
|
---|
687 | DPRINTF_INFO((MY_ID "usb_midi_close(%p device=%p port=%p)\n",
|
---|
688 | cookie, midiDevice, port));
|
---|
689 |
|
---|
690 |
|
---|
691 | acquire_sem(usbmidi_port_list_lock);
|
---|
692 | if (port) {
|
---|
693 | /* detach the cookie from port */
|
---|
694 | port->open_fd = NULL;
|
---|
695 | --port->open;
|
---|
696 | }
|
---|
697 | release_sem(usbmidi_port_list_lock);
|
---|
698 | DPRINTF_DEBUG((MY_ID "usb_midi_close: complete\n"));
|
---|
699 |
|
---|
700 | return B_OK;
|
---|
701 | }
|
---|
702 |
|
---|
703 |
|
---|
704 | static status_t
|
---|
705 | usb_midi_free(driver_cookie* cookie)
|
---|
706 | {
|
---|
707 | usbmidi_port_info* port; /* all only for info */
|
---|
708 | usbmidi_device_info* midiDevice;
|
---|
709 |
|
---|
710 | assert(cookie != NULL);
|
---|
711 | port = cookie->port;
|
---|
712 | midiDevice = cookie->device;
|
---|
713 | DPRINTF_INFO((MY_ID "usb_midi_free(%p device=%p)\n", cookie, midiDevice));
|
---|
714 |
|
---|
715 | free(cookie);
|
---|
716 |
|
---|
717 | return B_OK;
|
---|
718 | }
|
---|
719 |
|
---|
720 |
|
---|
721 | static device_hooks usb_midi_hooks = {
|
---|
722 | (device_open_hook)usb_midi_open,
|
---|
723 | (device_close_hook)usb_midi_close,
|
---|
724 | (device_free_hook)usb_midi_free,
|
---|
725 | (device_control_hook)usb_midi_control,
|
---|
726 | (device_read_hook)usb_midi_read,
|
---|
727 | (device_write_hook)usb_midi_write,
|
---|
728 | NULL, NULL, NULL, NULL
|
---|
729 | };
|
---|
730 |
|
---|
731 |
|
---|
732 | /*
|
---|
733 | Driver Registration
|
---|
734 | */
|
---|
735 |
|
---|
736 | _EXPORT status_t
|
---|
737 | init_hardware(void)
|
---|
738 | {
|
---|
739 | DPRINTF_DEBUG((MY_ID "init_hardware() version:"
|
---|
740 | __DATE__ " " __TIME__ "\n"));
|
---|
741 | return B_OK;
|
---|
742 | }
|
---|
743 |
|
---|
744 |
|
---|
745 | _EXPORT status_t
|
---|
746 | init_driver(void)
|
---|
747 | {
|
---|
748 |
|
---|
749 | DPRINTF_INFO((MY_ID "init_driver() version:" __DATE__ " " __TIME__ "\n"));
|
---|
750 |
|
---|
751 | if (get_module(B_USB_MODULE_NAME, (module_info**)&usb) != B_OK)
|
---|
752 | return B_ERROR;
|
---|
753 |
|
---|
754 | if ((usbmidi_port_list_lock = create_sem(1, "dev_list_lock")) < 0) {
|
---|
755 | put_module(B_USB_MODULE_NAME);
|
---|
756 | return usbmidi_port_list_lock; /* error code */
|
---|
757 | }
|
---|
758 |
|
---|
759 | usb->register_driver(usb_midi_driver_name, my_supported_devices,
|
---|
760 | SUPPORTED_DEVICES, NULL);
|
---|
761 | usb->install_notify(usb_midi_driver_name, &my_notify_hooks);
|
---|
762 | DPRINTF_INFO((MY_ID "init_driver() OK\n"));
|
---|
763 |
|
---|
764 | return B_OK;
|
---|
765 | }
|
---|
766 |
|
---|
767 |
|
---|
768 | _EXPORT void
|
---|
769 | uninit_driver(void)
|
---|
770 | {
|
---|
771 | DPRINTF_INFO((MY_ID "uninit_driver()\n"));
|
---|
772 | usb->uninstall_notify(usb_midi_driver_name);
|
---|
773 |
|
---|
774 | delete_sem(usbmidi_port_list_lock);
|
---|
775 | put_module(B_USB_MODULE_NAME);
|
---|
776 | free_port_names();
|
---|
777 | DPRINTF_INFO((MY_ID "uninit complete\n"));
|
---|
778 | }
|
---|
779 |
|
---|
780 |
|
---|
781 | _EXPORT const char**
|
---|
782 | publish_devices(void)
|
---|
783 | {
|
---|
784 | DPRINTF_INFO((MY_ID "publish_devices()\n"));
|
---|
785 |
|
---|
786 | if (usbmidi_port_list_changed) {
|
---|
787 | free_port_names();
|
---|
788 | alloc_port_names();
|
---|
789 | if (usbmidi_port_names != NULL)
|
---|
790 | rebuild_port_names();
|
---|
791 | usbmidi_port_list_changed = false;
|
---|
792 | }
|
---|
793 | assert(usbmidi_port_names != NULL);
|
---|
794 | return (const char**)usbmidi_port_names;
|
---|
795 | }
|
---|
796 |
|
---|
797 |
|
---|
798 | _EXPORT device_hooks*
|
---|
799 | find_device(const char* name)
|
---|
800 | {
|
---|
801 | assert(name != NULL);
|
---|
802 | DPRINTF_INFO((MY_ID "find_device(%s)\n", name));
|
---|
803 | if (search_port_info(name) == NULL)
|
---|
804 | return NULL;
|
---|
805 | return &usb_midi_hooks;
|
---|
806 | }
|
---|