// A Somewhat Useful Sample Real Time Client. // Author: Joseph Ballantyne // Date: 3/12/99 // This is the first real time client that actually does something // useful. This is a midi MPU401 sequencer. It is simple, but it // works and uses less CPU than our current 98 and NT WDM sequencer. // Of course since this is a sample and I am not in the business of // writing MIDI sequencers, it has some limitations. // The data to be sequenced is passed in in one big block. // It is passed in already formatted and timestamped appropriately. // This code simply processes the buffer it was passed, and when // it has sequenced everything that was passed in, it returns - // which kills the real time thread. #include "common.h" #include "rt.h" #include "sequence.h" // This code only supports the MPU401 at a fixed IO location of 0x330. // MPU401 defines #define MPU401BASEADDRESS 0x330 #define MPU401_REG_DATA 0x00 // Data in/out register offset from base address #define MPU401_REG_COMMAND 0x01 // Command register offset from base address #define MPU401_REG_STATUS 0x01 // Status register offset from base addess #define MPU401_DRR 0x40 // Output ready (for command or data) #define MPU401_DSR 0x80 // Input ready (for data) #define MPU401_CMD_RESET 0xFF // Reset command #define MPU401_CMD_UART 0x3F // Switch to UART mod #define MPU401_ACK 0xFE // Ack from MPU401 after successful command. #define MidiWriteOK(status) ((status & MPU401_DRR) == 0) #define MidiReadOK(status) ((status & MPU401_DSR) == 0) // Here is the format of the data we process. #pragma pack(push,1) typedef struct MidiChunk{ struct MidiChunk *next; struct MidiChunk *previous; ULONGLONG timestamp; ULONG numbytes; UCHAR data[3]; } MidiMessage, *PMidiMessage; #pragma pack(pop) // Remember, everything we touch HAS to be locked down. #pragma LOCKED_CODE #pragma LOCKED_DATA MidiMessage testmidi[2]={ {&testmidi[1],&testmidi[1],0,3,0x99,0x25,0x7f}, {&testmidi[0],&testmidi[0],240,3,0x99,0x25,0} }; #pragma warning ( disable : 4035 ) #define rdtsc __asm _emit 0x0f __asm _emit 0x31 LONGLONG __inline ReadCycleCounter(VOID) { __asm { rdtsc } } #pragma warning ( default : 4035 ) VOID OutB(ULONG address, ULONG data) { __asm { mov edx,address mov eax,data out dx,al } } #pragma warning( disable : 4035 ) ULONG InB(ULONG address) { __asm { mov edx,address xor eax,eax in al,dx } } #pragma warning( default : 4035 ) // This routine sends a command to the MPU401 and waits for an acknowledge from // the MPU that the command succeeded. If no ack is recieved in 200ms then // it returns FALSE, otherwise it returns TRUE. BOOL SendMpuCommand(ULONG MidiBaseAddress, UCHAR command) { LONG count=0; // Wait until OK to write a command. while (!MidiWriteOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) { RtYield(0, 0); } // Send command to the MPU401. OutB(MidiBaseAddress+MPU401_REG_COMMAND, command); // Wait for the MPU acknowlege. If we don't get the correct response // in 200ms or less, then punt. while (!MidiReadOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) { count++; if (count<=200) { RtYield(0, 0); } else { // We did not get any response, perhaps we were in UART mode. #if 0 return FALSE; #else break; #endif } } // At this point we received something from the MPU, check if it is an ack. if (InB(MidiBaseAddress+MPU401_REG_DATA)!=0xfe) { // Not a command acknowledge. Trap(); return FALSE; } return TRUE; } VOID PlayMidi(PVOID Context, ThreadStats *Statistics) { PMidiMessage RealTimeMidiData; ULONG MidiBaseAddress; ULONG MidiStatus; ULONG count; LONGLONG starttime; //Trap(); RealTimeMidiData=(PMidiMessage)Context; MidiBaseAddress=MPU401BASEADDRESS; count=0; // First wait until the MPU401 comes on line. We need to do this because // many MPU401s are plug and play devices and do not appear until // the hardware is setup. Note that on most machines, when there is // no device on the bus at the I/O port address, InB will return // 0xff. while (InB(MidiBaseAddress+MPU401_REG_STATUS)==0xff) { RtYield(0, 0); } //Trap(); // At this point, there is detected hardware at 331. // Now wait an extra 10 seconds. starttime=Statistics->ThisPeriodStartTime; while((Statistics->ThisPeriodStartTime-starttime)/SEC<10) { RtYield(0, 0); } //Trap(); // Now read the MPU401 data register until it is empty. while (MidiReadOK(InB(MidiBaseAddress+MPU401_REG_STATUS))) { InB(MidiBaseAddress+MPU401_REG_DATA); RtYield(0, 0); } //Trap(); // Now reset the MPU401. If this succeeds, we know we have a real // MPU at our base address. Note that the MPU may be in UART // mode. If so, then we will NOT get an acknowlege back when we reset it. // To handle this case, if there is no acknowlege on the first // reset, we will retry once and see if we get the acknowlege the second // time. If not, then we punt. if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_RESET)) { if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_RESET)) { Trap(); return; } } // Now put the MPU into UART mode. if (!SendMpuCommand(MidiBaseAddress, MPU401_CMD_UART)) { // For now we disable the trap and the thread exit. // We do this because if we run this code on current WDM driven devices, // the acknowlege code will be handled by the read interrupt service // routine of the driver and so we will not see it. Trap(); return; } // Now reset the starttime. starttime=ReadCycleCounter(); // Run until we are out of data to send. while (RealTimeMidiData!=NULL) { // Wait until we need to send the next chunk of data. // if (Statistics->totalperiods*MSECtimestamp) { if ((ULONGLONG)(ReadCycleCounter()-starttime)/200000timestamp) { RtYield(0, 0); continue; } // Read the status of the device. MidiStatus=InB(MidiBaseAddress+MPU401_REG_STATUS); // Make sure its OK to write to the device. We should ALWAYS // be able to, since we syncronize with the hardware. If not, // then update the syncronization, and slip to our next time slice. if (!MidiWriteOK(MidiStatus)) { RtYield(0, 0); continue; } // Now write the next byte out to the MPU. OutB(MidiBaseAddress+MPU401_REG_DATA, RealTimeMidiData->data[count]); // Update our state. count++; if (count>=RealTimeMidiData->numbytes) { RealTimeMidiData->timestamp+=250; RealTimeMidiData=RealTimeMidiData->next; count=0; } // And yeild - we are done until its time for the next byte. RtYield(0, 0); } }