title SNDFRC -- Epoc/Os Sound Device Driver Using FRC subttl Copyright (c) Psion PLC 1992 name SNDFRC ; ; VER DATE BY DESCRIPTION ; ----- -------- ---- ----------- ; 1.00A 21/04/92 JH Alpha release ; TURBOC equ 1 ; include epocdef.inc include epocmac.inc include epocpan.inc include epoclib.inc include epocser.inc include ossibo.inc HandlerDisable equ 000h ; the hnadler should not be called HandlerEnable equ 001h ; the handler should be called MAX_NOTES equ 500 ; This is allocated in the user space for the I/O channel SndFrcEnt struc SndFrcIo ChanEnt <> ; I/O channel hdr, MUST be first SndFrcWrite RqEnt <> SndFrcHandler dw ? SndFrcFlags dw ? SndFrcPstat dw ? SndFrcEnt ends SndChannel struc Pid dw ? ; owning channel pid OpenFrcChan db ? ; channel open status (KEEP ORDER) WriteStat db ? ; write completion status (ORDER) DataPtr dw ? ; where we are in buffer DataLength dw ? ; number of notes to play DataPreBuffer dw ? ; First Frc timout ptr DataBuffer dw MAX_NOTES dup (?) SndChannel ends CodeSeg ProcBegin@ SndfrcLDD ; ========== ; The externally loadable device table dw LDDSignature db 'MUS',0,0,0,0,0 dw (VectorEnd-Vector)/2 Vector: dw SndfrcInstall dw SndfrcRemove dw SndfrcHold dw SndfrcResume dw SndfrcReset dw SndfrcUnits dw SndfrcOpen dw SndfrcStrategy VectorHandler: dw SndfrcHandler VectorEnd: ProcEnd noret SoundChan SndChannel <> ProcBegin@ disableFrcInt ; ============= ; Disable the generation of Frc interrupts ; pushf cli in al, A1InterruptMask and al, not (mask FrcExpired) out A1InterruptMask, al ; stop Interrupt controller from popf ; generating Frc interrupts ret ProcEnd ; ProcBegin@ enableFrcInt ; ============ ; Starts the free running counter. ; pushf cli in al, A1Control or al, mask FrcSource or mask FrcMode out A1Control, al ; set 512khz and prescale mode mov ax, 5120 ; request a FrcInt every 10ms out A1FrcControl, ax out A1FrcEoi, al ; clear any junk Frc Interrupt in al, A1InterruptMask or al, mask FrcExpired ; allow interrupt controller to out A1InterruptMask, al ; generate a Frc Interrupt popf ret ProcEnd noret ; ProcBegin@ FrcInterrupt,far ; ============ ; The Frc interrupt routine. ; Although we could get addressability to the clients data space since it is ; possible that the operating system is currently moving the ; data space of the client in which case the data buffer to be played ; may not be valid. ; The ISR is called by the operating system after all registers have been ; preserved. ; We MUST NOT cause a reschedule directly since the ISR would take a long ; time to complete if we did. ; ; Called in the context of whatever was running when the interrupt went off ; out A1FrcEoi, al ; clear Frc Interrupt mov si, SoundChan.DataPtr cmp byte ptr cs:[si+1], 0 ; look at the duration byte je finishedThatNote ; if zero goto the next note dec byte ptr cs:[si+1] ; reduce timeout endFrcInterrupt: stc ; no potential reschedule required endFrcInterruptFlags: out A1NonSpecificEoi, al ; finish the ISR ret finishedThatNote: cmp SoundChan.DataLength, 0 je finishedAllNotes dec SoundChan.DataLength ; one less note to play inc si inc si ; find next note,volume and duration mov SoundChan.DataPtr, si ; updated ptr mov al, byte ptr cs:[si] push ax shr ax, 1 ; factor out the volume bits and al, 60h ; into bits 5+6 for the hardware mov ah, al mov al, A2IWrite out A2Index, al mov al, ah out A2Control, al ; set the volume for that note pop ax mov dx, PSoundControl and al, 3fh ; bottom 6 bits are note info out dx, al ; and play that note jmp short endFrcInterrupt finishedAllNotes: call StopSound mov SoundChan.WriteStat, 0 ; finished playing that lot mov bx, SoundChan.Pid ; get the channel owner IoSignalByPidNoReSched ; signal ourselves, causes handler clc ; reschedule if possible jmp short endFrcInterruptFlags ProcEnd noret ; ProcBegin@ setFrcIntVector ; =============== ; Set the ISR pointer to our FRC interrupt routine. ; MUST be done with ints disabled so we cant be moved by the operating ; system whilst setting the address. ; pushf cli push bx mov al, HwIrq5Revector mov bx, offset FrcInterrupt mov cx, cs ; this ok as ints disabled. GenSetRevector pop bx popf ret ProcEnd noret ; ProcBegin@ StopSound ; ========= ; Stop the sound generation. ; call disableFrcInt ; Stop Frc interrupts mov dx, PSoundControl mov al, 1 out dx, al ; stop sound chip HwComboOff ; stop amplifier ret ProcEnd noret ProcBegin@ SndfrcInstall,far ; ============= ; Install vector called when application calls p_loadldd() ; Set OpenFrcChan to zero. ; In: ; NONE ; Out: ; Carry clear if installed ok ; Carry set if failed to install ; and SoundChan.OpenFrcChan, 0 clc ; installed OK ret ProcEnd noret ; ProcBegin@ SndfrcRemove,far ; ============ ; Remove vector called when application calls p_devdel() ; cmp SoundChan.OpenFrcChan, 0 je soundRemoved mov al, InUseErr stc ; channel open, fail to remove ret ProcEnd noret ; ProcBegin@ SndfrcReset,far ; =========== ; Reset vector called if the client terminated. ; We can only get a reset if weve got channel open and client died ; since we cancel the reset request when the channel is closed. ; NOTE: The clients data space no longer exists thus all data required to ; close down the channel must be avaliable in the drivers space. ; In: ; NONE ; Out: ; NONE ; call StopSound ; disable Frc ints etc mov al, HwIrq5Revector GenResetRevector ; restore the default ROM ISR vector mov al, mask FrcExpired HwFreeChannel ; free Frc resource HwFreeCombo ; free up sound usage resource and word ptr SoundChan.OpenFrcChan, 0 ; all closed now soundRemoved: clc ; removed OK. ret ProcEnd noret ProcBegin@ SndfrcHold,far ; ========== ; Hold called when another driver loaded or switched off/power fail. ; In: ; AH - reason. ; Out: ; NONE cmp SoundChan.OpenFrcChan, 0 je soundHeld ; dont do anything if not open call StopSound ; shut down ints and sound soundHeld: ret ProcEnd noret ProcBegin@ SndfrcResume,far ; ============ ; Resume called after a Hold to allow the driver to continue. ; If we were running enabling the Frc interrupt will cause the ; FrcInt routine to be called thus allowing us to count down the current ; duration as though nothing had happened before going to the next note. ; In: ; NONE ; Out: ; NONE ; cmp SoundChan.OpenFrcChan, 0 je soundResumed ; dont do anything if not open call setFrcIntVector ; we may have moved, set CS correctly cmp SoundChan.WriteStat, PendingErr jne soundResumed ; dont enable ints if no write queued HwComboOn ; switch amplifier on call enableFrcInt ; continue where we left off soundResumed: ret ProcEnd noret ProcBegin@ SndfrcUnits,far ; =========== ; We can only handle 1 sound channel at a time. ; mov ax, 1 ; support 1 unit at a time ret ProcEnd noret ProcBegin@ SndfrcOpen,far ; ========== ; Open the sound device driver. ; This runs is the context of the process that has called p_open("MUF:"). ; ; In: ; DS=ES=SS - Process data space. ; DX - OS device handle ; SI - ptr to OpenEnt struct ; Out: ; DX - OS device handle ; Carry Clear ; BX - channel, DX is as passed (device handle) ; Carry Set ; AL - error number ; ; All device drivers MUST have a ChanEnt as first item at returned BX ; HwGetCombo ; see if snd already in use, if not grab it jc sndAlreadyInUse mov al, mask FrcExpired HwGetChannel ; see if FRC already in use, if not grab it jc endFrcInUse mov cx, (size SndFrcEnt) HeapAllocateCell jc endOpenNoMemory ; get users I/O channel mov bx, ax ; the allocated cell handle mov al, (VectorHandler-Vector)/2 IoAddHandler ; handler handle in AX jc endOpenFreeMemory mov [bx].SndFrcHandler, ax xchg bx, dx xor cx, cx IoRequestReset ; call Reset if client terminates xchg bx, dx mov [bx].SndFrcIo.ChanNext, bx ; we are a root device mov [bx].SndFrcIo.ChanSignature, IoChanSignature mov [bx].SndFrcIo.ChanLibHandle, dx ; This is our device handle and [bx].SndFrcPstat, 0 ; no write pending yet and [bx].SndFrcFlags, 0 ProcId mov SoundChan.Pid, ax ; remember client id for ISR mov SoundChan.OpenFrcChan, 1 ; now open call setFrcIntVector ; set up the ISR address ptr clc ret endOpenFreeMemory: push ax HeapFreeCell pop ax endOpenNoMemory: push ax mov al, mask FrcExpired HwFreeChannel ; free Frc resource pop ax endFrcInUse: push ax HwFreeCombo ; free sound resource as failed somewhere pop ax stc sndAlreadyInUse: ret ProcEnd noret ProcBegin@ SndfrcHandler,far ; ============== ; Called when an I/O signal is generated and were enabled. We should only ; enabled when a write request is outstanding. ; We need to check the LOW side drivers completion status to see if all the ; notes have been played yet (SoundChan.WriteStat!=E_FILE_PENDING). ; Typically write the handler defensivly since complex drivers may leave the ; handler enabled other than when a request is outstanding. ; ; In: ; DS,ES,SS == Process data space ; BX == channel control block from open request ; Out: ; Carry clear ; Did not consume signal ; Carry set ; al = 0 - Leave handler disabled ; al = non 0 - Enable handler ; test [bx].SndFrcFlags, RQ_DISABLE_HANDLER jnz signalNotForUs ; we are in StategyVector code cmp [bx].SndFrcPstat, 0 je signalNotForUs ; no write request queued yet mov al, SoundChan.WriteStat cmp al, PendingErr je signalNotForUs ; pending write request not finished cbw xor si, si xchg si, [bx].SndFrcPstat ; no write queued if Pstat is zero mov [si], ax IoSignal ; complete the original write request xor ax, ax ; disable handler now stc ret signalNotForUs: clc ret ProcEnd noret ProcBegin@ CancelWrite ; =========== ; Cancel any outstanding write request ; xor di, di xchg di, [bx].SndFrcPstat or di, di ; see if any write is currently queued jz noWriteQueued push bx mov bx, [bx].SndFrcHandler mov cl, HandlerDisable IoEnableHandler ; make SndfrcHandler non callable pop bx call StopSound ; stops Frc ints mov word ptr [di], CancelErr ; set write completion stat IoSignal ; and signal write completion noWriteQueued: mov SoundChan.WriteStat, 0 ret ProcEnd noret ; ProcBegin@ SndfrcStrategy,far ; ============== ; Strategy vector called by an I/O request on the opened sound device driver ; channel. ; The wait handler may be called (when enabled ) unless we block it when we ; call a sync I/O request. Allowing the waithandler to run whilst in the ; strategy vector is generally not a good thing to do. ; In this particular example there are infact no sync calls to the I/O system ; thus the wait handler cannot be run. In more complex drivers it may be ; difficult to determine all possible paths through the code thus the driver ; should be written defensivly (use RQ_DISABLE_HANDLER type mechanism). ; In: ; DS,ES,SS == Process data space ; BX == channel control block from open request ; DX == device handle ; SI == RqEnt pointer ; Out: ; Carry clear ; - request queued sucessfully (may have completed) ; Carry set ; - error queuing the I/O request ; cld or [bx].SndFrcFlags, RQ_DISABLE_HANDLER mov al, byte ptr [si].RqFunction cmp al, IoFuncWrite jne tryCancel cmp [bx].SndFrcPstat, 0 jne writeQueued ; panic if write already queued mov di, [si].RqA2Ptr mov cx, [di] ; length to write cmp cx, MAX_NOTES jae writeQueued ; length too much, panic dvr mov di, [si].RqStatusPtr mov word ptr [di], PendingErr mov [bx].SndFrcPstat, di ; write request now queued mov si, [si].RqA1Ptr ; ptr to data to write as sound mov di, offset SoundChan.DataPreBuffer and word ptr [di], 0 ; set pre buffer len to zero mov SoundChan.DataPtr, di inc di inc di mov SoundChan.DataLength, cx ; this many notes to play mov SoundChan.WriteStat, PendingErr pushf cli ; we can set ES when ints are off push cs pop es ; copy the notes+vol and duration popf ; into buf we can access in ISR rep movsw ; dont use CS override mov es, [bp].IntES push bx mov bx, [bx].SndFrcHandler mov cl, HandlerEnable IoEnableHandler ; allow waithandler to be called pop bx HwComboOn call enableFrcInt ; start the sound off and [bx].SndFrcFlags, NOT RQ_DISABLE_HANDLER xor ax, ax ; queued OK ret writeQueued: mov al, PanicIoPending ProcPanic ; stop bad applications tryCancel: cmp al, IoFuncCancel jne tryClose call CancelWrite ; stop any queued write and [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER jmp short signalOk ; cancel completed ok. tryClose: cmp al, IoFuncClose jne notForUs call CancelWrite ; stop any queued writes push bx push [bx].SndFrcHandler push dx mov al, HwIrq5Revector GenResetRevector pop bx ; channel handle xor cx, cx IoRequestResetCancel ; dont call Reset vector now pop bx ; handler handle IoRemoveHandler pop bx ; alloc handle HeapFreeCell mov al, mask FrcExpired HwFreeChannel ; free Frc resource HwFreeCombo ; not in use any more and word ptr SoundChan.OpenFrcChan, 0 ; all closed now signalOk: xor ax, ax mov di, [si].RqStatusPtr stosw ; the I/O request completed OK IoSignal xor ax, ax ; also clears carry ret notForUs: and [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER IoRoot ; pass on request as not for us ret ProcEnd noret EndCodeSeg ; stack segment stack para 'data' ; stack ends ; end SndfrcLDD