/* * * * * * * * * * * * * * * * * * * OPiuM Driver by fseidel/cmucc * * Version 0.01 * * 68K for life! * * * * * * * * * * * * * * * * * * */ TRAP4_INT = 0x0024 NUM_SVCS = 0x4 OPM_ADDR_PORT = 0xe90001 OPM_DATA_PORT = 0xe90003 .include "../doscalls.S" .text .globl _start _start: //print start message pea start_msg dc.w _DOS_PRINT addq.l #4, %a7 //enter supervisor mode pea 0x0000 dc.w _DOS_SUPER addq.l #4, %a7 move.l %d0, %d2 //hold on to the SSP value in a callee-save register //check if removal was requested //NO, THIS WON'T WORK. THE COPY INVOKED TO REMOVE IS LOADED AFTER //NEED TO PROVIDE EXIT SERVICE CALL move.b (%a2), %d0 bne unload_driver //install trap handler pea trap_handler move.w #TRAP4_INT, -(%a7) dc.w _DOS_INTVCS addq.l #6, %a7 lea old_handler(%pc), %a0 move.l %d0, (%a0) //save the old handler pea installed_msg dc.w _DOS_PRINT addq.l #4, %a7 //return to user mode before exit move.l %d2, -(%a7) dc.w _DOS_SUPER addq.l #4, %a7 //now we terminate and stay resident move.w #0, -(%a7) //push a 0 return value move.l #PROGSIZE, -(%a7) //push size to reserve dc.w _DOS_KEEPPR unload_driver: //restore old handler pea unhook_msg dc.w _DOS_PRINT addq.l #4, %a7 move.l (old_handler), %d2 //TODO: this should probably use a constant reg cmp.l #0x00000000, %d2 //check against default value beq not_loaded pea unhook_success_msg dc.w _DOS_PRINT addq.l #4, %a7 move.l (old_handler), -(%a7) move.w #TRAP4_INT, -(%a7) dc.w _DOS_INTVCS addq.l #6, %a7 bra done not_loaded: pea not_loaded_msg dc.w _DOS_PRINT addq.l #4, %a7 done: dc.w _DOS_EXIT //here comes the fun part trap_handler: //start by saving our callee-save registers movem.l %d2-%d7/%a2-%a6, -(%a7) moveq.l #0, %d7 //keep d7 as a 0 register move.w %d0, %d2 //save service number move.w %a0, %a2 //save arg0 if there is one move.w %a1, %a3 //save arg1 if there is one //print a message to show we've been invoked pea invocation_msg dc.w _DOS_PRINT addq.l #4, %a7 //d2 now contains our service number, let's see what was requested cmpi.w #NUM_SVCS, %d2 //compare to number of services bhs service_error //branch to error handler if arg out of range //else just jump to the right place (via PIC jump table) lsl.w #1, %d2 //shift left by 1 to get the index into the table lea jump_table, %a0 sub.l %a1, %a1 //clear a1 move.w (%a0, %d2), %a1 jmp 2(%pc, %a1) //the 2 sets the PC at the start of the jump table jump_table: dc.w load_song - jump_table dc.w unload_song - jump_table dc.w get_routine - jump_table dc.w init_song - jump_table //none of these matched, return -1 service_error: move.l #0xffffffff, %d0 bra handler_done load_song: //a2 is pointer to song in memory //a3 is size of song in memory pea load_msg dc.w _DOS_PRINT addq.l #4, %a7 move.l (%a2), %d0 //check magic number ("OPM\0") cmp.l #0x4f504d00, %d0 bne magic_check_fail //magic number was okay, proceed to save song lea song_ptr(%pc), %a0 move.l %a2, (%a0) lea song_len(%pc), %a0 move.l %a3, (%a0) //go handle song init stuff bra init_song_internal magic_check_fail: pea magic_fail_msg dc.w _DOS_PRINT addq.l #4, %a7 bra handler_done init_song: //TODO: optimize address calculations using pointer to start of vars lea song_ptr(%pc), %a0 move.l (%a0), %a2 lea song_len(%pc), %a0 move.l (%a0), %a3 init_song_internal: //entry point for when we already have values in regs lea play_ptr(%pc), %a0 //compute address of playback position var. move.l %a2, %d0 //stick the address of the song into d2 add.l 0x10(%a2), %d0 //add offset where register log begins move.l %d0, (%a0) //store to playback position lea frame_ctr(%pc), %a0 move.w %d7, (%a0) //clear the frame counter bra handler_done unload_song: //TODO: implement bra handler_done get_routine: lea play_routine(%pc), %a0 move.l %a0, %d0 //fall through into the handler return sequence handler_done: //restore callee-save registers movem.l (%a7)+, %d2-%d7/%a2-%a6 rte play_routine: //play routine to be called once per frame movem.l %d2-%d7/%a2-%a6, -(%a7) //save the registers //start by checking if we even need to do anything on this frame lea frame_ctr(%pc), %a0 move.w (%a0), %d0 beq do_update //nonzero frame counter => we wait sub #1, %d0 bra done_play do_update: lea play_ptr(%pc), %a0 //compute address of playback position var. move.l (%a0), %a0 //a0 is now playback pointer move.l (%a0), %d1 //d1 contains {update count, block flags} //TODO: Check the block flags lsr.w #8, %d1 //d1.b is now the update counter move.w 0x02(%a0), %d0 //d0 contains delay time update_loop: //if we're done updating, get out of loop dbra %d1, done_play //note that dbra only affects %d1.w move.w 0x04(%a0), %d2 //grab a register update block move.b %d2, (OPM_ADDR_PORT) //save port to the sound chip addr_loop: move.w (OPM_DATA_PORT), %d3 //check if busy bmi addr_loop //bit 7 set => chip is busy lsr.w #8, %d2 //shift over so d2.b contains data move.b %d2, (OPM_DATA_PORT) //save data to the sound chip data_loop: move.w (OPM_DATA_PORT), %d3 //check if busy bmi data_loop //bit 7 set => chip is busy dbra %d0, update_loop done_play: move.w %d0, (%a0) //save the new frame counter movem.l (%a7)+, %d2-%d7/%a2-%a6 //restore registers rts //space in memory image where we can save persistent data old_handler: dc.l 0x00000000 song_ptr: dc.l 0x00000000 song_len: dc.l 0x00000000 //playback variable section begins here play_ptr: //pointer to where we resume playback dc.l 0x00000000 frame_ctr: //frame delay pointer to see if we update on a given frame dc.w 0x0000 //strings used by OPiuM .string start_msg: .asciz "OPiuM v0.01 by fseidel/cmucc\r\n" installed_msg: .asciz "Driver is resident in memory and will now terminate.\r\n" invocation_msg: .asciz "Trap has been invoked.\r\n" load_msg: .asciz "Got load command.\r\n" unhook_msg: .asciz "Attempting to unload driver.\r\n" unhook_success_msg: .asciz "OPiuM was successfully unloaded.\r\n" not_loaded_msg: .asciz "OPiuM did not detect that it was loaded.\r\n" magic_fail_msg: .asciz "File not loaded. Bad magic string." //compute how much space we need to reserve when we terminate PROGSIZE = . - _start .end