522 lines
18 KiB
NASM
522 lines
18 KiB
NASM
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
|
|
|