// // usbmidi.c // // USPi - An USB driver for Raspberry Pi written in C // Copyright (C) 2016 R. Stange // Copyright (C) 2016 J. Otto // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // Refer to "Universal Serial Bus Device Class Specification for MIDI Devices" #include #include #include #include #include static const char FromMIDI[] = "umidi"; static unsigned s_nDeviceNumber = 1; static boolean USBMIDIDeviceStartRequest (TUSBMIDIDevice *pThis); static void USBMIDIDeviceCompletionRoutine (TUSBRequest *pURB, void *pParam, void *pContext); // This handy table from the Linux driver encodes the mapping between MIDI // packet Code Index Number values and encapsulated packet lengths. static const unsigned cin_to_length[] = { 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 }; #define EVENT_PACKET_SIZE 4 void USBMIDIDevice (TUSBMIDIDevice *pThis, TUSBDevice *pDevice) { assert (pThis != 0); USBDeviceCopy (&pThis->m_USBDevice, pDevice); pThis->m_USBDevice.Configure = USBMIDIDeviceConfigure; pThis->m_pEndpointIn = 0; pThis->m_pPacketHandler = 0; pThis->m_pURB = 0; pThis->m_pPacketBuffer = 0; } void _CUSBMIDIDevice (TUSBMIDIDevice *pThis) { assert (pThis != 0); if (pThis->m_pPacketBuffer != 0) { free (pThis->m_pPacketBuffer); pThis->m_pPacketBuffer = 0; } if (pThis->m_pEndpointIn != 0) { _USBEndpoint (pThis->m_pEndpointIn); free (pThis->m_pEndpointIn); pThis->m_pEndpointIn = 0; } _USBDevice (&pThis->m_USBDevice); } boolean USBMIDIDeviceConfigure (TUSBDevice *pUSBDevice) { TUSBMIDIDevice *pThis = (TUSBMIDIDevice *)pUSBDevice; assert (pThis != 0); TUSBConfigurationDescriptor *pConfDesc = (TUSBConfigurationDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_CONFIGURATION); if ( pConfDesc == 0 || pConfDesc->bNumInterfaces < 1) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromMIDI); return FALSE; } TUSBInterfaceDescriptor *pInterfaceDesc; while ((pInterfaceDesc = (TUSBInterfaceDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_INTERFACE)) != 0) { if ( pInterfaceDesc->bNumEndpoints == 0 || pInterfaceDesc->bInterfaceClass != 0x01 // Audio class || pInterfaceDesc->bInterfaceSubClass != 0x03 // MIDI streaming || pInterfaceDesc->bInterfaceProtocol != 0x00) // unused, must be 0 { continue; } // Our strategy for now is simple: we'll take the first MIDI streaming // bulk-in endpoint on this interface we can find. To distinguish // between the MIDI streaming bulk-in endpoints we want (which carry // actual MIDI data streams) and 'transfer bulk data' endpoints (which // are used to implement features like Downloadable Samples that we // don't care about), we'll look for an immediately-accompanying // class-specific endpoint descriptor. TUSBAudioEndpointDescriptor *pEndpointDesc; while ((pEndpointDesc = (TUSBAudioEndpointDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_ENDPOINT)) != 0) { if ( (pEndpointDesc->bEndpointAddress & 0x80) != 0x80 // Input EP || (pEndpointDesc->bmAttributes & 0x3F) != 0x02) // Bulk EP { continue; } TUSBMIDIStreamingEndpointDescriptor *pMIDIDesc = (TUSBMIDIStreamingEndpointDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_CS_ENDPOINT); if ( pMIDIDesc == 0 || (u8 *)pEndpointDesc + pEndpointDesc->bLength != (u8 *)pMIDIDesc) { continue; } assert (pThis->m_pEndpointIn == 0); pThis->m_pEndpointIn = malloc (sizeof (TUSBEndpoint)); assert (pThis->m_pEndpointIn != 0); pThis->m_usBufferSize = pEndpointDesc->wMaxPacketSize; pThis->m_usBufferSize -= pEndpointDesc->wMaxPacketSize % EVENT_PACKET_SIZE; assert (pThis->m_pPacketBuffer == 0); pThis->m_pPacketBuffer = malloc (pThis->m_usBufferSize); assert (pThis->m_pPacketBuffer != 0); USBEndpoint2 (pThis->m_pEndpointIn, &pThis->m_USBDevice, (TUSBEndpointDescriptor *) pEndpointDesc); } } if (pThis->m_pEndpointIn == 0) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromMIDI); return FALSE; } if (!USBDeviceConfigure (&pThis->m_USBDevice)) { LogWrite (FromMIDI, LOG_ERROR, "Cannot set configuration"); return FALSE; } TString DeviceName; String (&DeviceName); StringFormat (&DeviceName, "umidi%u", s_nDeviceNumber++); DeviceNameServiceAddDevice (DeviceNameServiceGet (), StringGet (&DeviceName), pThis, FALSE); _String(&DeviceName); return USBMIDIDeviceStartRequest (pThis); } void USBMIDIDeviceRegisterPacketHandler (TUSBMIDIDevice *pThis, TMIDIPacketHandler *pPacketHandler) { assert (pThis != 0); assert (pPacketHandler != 0); pThis->m_pPacketHandler = pPacketHandler; } boolean USBMIDIDeviceStartRequest (TUSBMIDIDevice *pThis) { assert (pThis != 0); assert (pThis->m_pEndpointIn != 0); assert (pThis->m_pPacketBuffer != 0); assert (pThis->m_pURB == 0); pThis->m_pURB = malloc (sizeof (TUSBRequest)); assert (pThis->m_pURB != 0); USBRequest (pThis->m_pURB, pThis->m_pEndpointIn, pThis->m_pPacketBuffer, pThis->m_usBufferSize, 0); USBRequestSetCompletionRoutine (pThis->m_pURB, USBMIDIDeviceCompletionRoutine, 0, pThis); return DWHCIDeviceSubmitAsyncRequest (USBDeviceGetHost (&pThis->m_USBDevice), pThis->m_pURB); } void USBMIDIDeviceCompletionRoutine (TUSBRequest *pURB, void *pParam, void *pContext) { TUSBMIDIDevice *pThis = (TUSBMIDIDevice *) pContext; assert (pThis != 0); assert (pURB != 0); assert (pThis->m_pURB == pURB); if ( USBRequestGetStatus (pURB) != 0 && USBRequestGetResultLength (pURB) % EVENT_PACKET_SIZE == 0) { assert (pThis->m_pPacketBuffer != 0); u8 *pEnd = pThis->m_pPacketBuffer + USBRequestGetResultLength(pURB); for (u8 *pPacket = pThis->m_pPacketBuffer; pPacket < pEnd; pPacket += EVENT_PACKET_SIZE) { // Follow the Linux driver's example and ignore packets with Cable // Number == Code Index Number == 0, which some devices seem to // generate as padding in spite of their status as reserved. if (pPacket[0] != 0) { if (pThis->m_pPacketHandler != 0) { unsigned nCable = pPacket[0] >> 4; unsigned nLength = cin_to_length[pPacket[0] & 0xf]; pThis->m_pPacketHandler(nCable, nLength, &pPacket[1]); } } } } _USBRequest (pThis->m_pURB); free (pThis->m_pURB); pThis->m_pURB = 0; USBMIDIDeviceStartRequest (pThis); }