Code: Select all
;blkread: READ FROM SCSI BLOCK DEVICE
;
; ———————————————————————————————————————————————————————————————————————
; Synopsis: This function reads one or more blocks from a SCSI block dev-
; ice, e.g., a disk, into a buffer. The logical unit number is
; assumed to be zero.
;
; § All parameters are pointers to data.
;
; § The target device's bus ID is a 16-bit quantity, with the
; MSB set to $00.
;
; § The logical block address (LBA) is a 32-bit quantity, even
; though the target device may not require or accept a 32-bit
; LBA.
;
; § The number of blocks to be accessed is a 16-bit quantity,
; with the MSB set to $00. The maximum number of blocks that
; may be accessed is 127 ($007F). If this field is $0000, no
; operation will occur & this function will immediately ret-
; urn without an error.
;
; § The buffer address is expressed as 32-bits, with the MSB of
; the MSW set to $00. The buffer must be of sufficient size
; to hold the requested number of blocks multiplied by the
; device's block size.
;
; § If the buffer pointer is null, the buffer pointer set by a
; previous call to the SSBUFS BIOS API is assumed to have
; been made. It is recommended this sequence be used if rep-
; itive accesses are to be made to the same buffer. Doing so
; will avoid the overhead of a BIOS API call on each access
; to set the buffer pointer. Use this feature with caution!
;
; § The target SCSI device must have been enumerated during the
; system POST.
; ———————————————————————————————————————————————————————————————————————
; Invocation example: pea #buf >> 16 ;buffer pointer MSW
; pea #buf & $ffff ;buffer pointer LSW
; pea #nblk >> 16 ;block count pointer MSW
; pea #nblk & $ffff ;block count pointer LSW
; pea #lba >> 16 ;LBA pointer MSW
; pea #lba & $ffff ;LBA pointer LSW
; pea #scsi_id >> 16 ;device ID pointer MSW
; pea #scsi_id & $ffff ;device ID pointer LSW
; .IF .DEF(_SCSI_) ;define this symbol...
; JSL blkread ;if making a “far” call...
; .ELSE
; JSR blkread
; .ENDIF
; BCS ERROR
;
; Exit registers: .A: entry value ¹
; .B: entry value ²
; .X: entry value
; .Y: entry value
; DB: entry value
; DP: entry value
; PB: entry value
; SR: nvmxdizc
; ||||||||
; |||||||+———> 0: okay
; ||||||| 1: error
; +++++++————> entry value
;
; Notes: 1) One of the following if an error:
;
; e_ptrnul: null pointer passed
; e_ssabt : transaction aborted
; e_ssblk : too many blocks
; e_sschk : check condition
; e_sscfe : controller fifo error
; e_sscge : controller general error
; e_sscmd : illegal controller command
; e_ssdne : device not enumerated
; e_ssdnp : device not responding
; e_sssnr : SCSI subsystem not ready
; e_sstid : device ID out of range
; e_ssubp : unsupported bus phase
; e_ssubr : unexpected bus reset
; e_ssucg : unsupported command group
; e_ssudf : unsupported driver function
; e_ssudt : unsupported device type
;
; 2) $00 if an error.
; ———————————————————————————————————————————————————————————————————————Next are the stack frame definitions, in which conditional assembly is involved:
Code: Select all
;—————————————————————————————————————————————————————————
;
;LOCAL DEFINITIONS
;
.maxblk =128 ;max blocks per transaction +1
.s_cdb =s_cdbg2 ;size of local CDB
.sfbase .set 0 ;base stack index
.sfidx .set .sfbase ;workspace index
;
;—————————> workspace stack frame start <—————————
;
.wsf =.sfidx ;start of workspace
;
.cdb =.sfidx ;local CDB
.sfidx .= .sfidx+.s_cdb
.nblks =.sfidx ;block count
.sfidx .= .sfidx+s_word
.lba =.sfidx ;LBA
.sfidx .= .sfidx+s_lba
;
;—————————> workspace stack frame end <—————————
;
.s_wsf =.sfidx-.sfbase ;workspace size
.sfbase .set .sfidx
;
;—————————> register stack frame start <—————————
;
.reg_dp =.sfidx ;DP
.sfidx .= .sfidx+s_mpudpx
.reg_db =.sfidx ;DB
.sfidx .= .sfidx+s_mpudbx
.reg_c =.sfidx ;.C
.sfidx .= .sfidx+s_word
.reg_x =.sfidx ;.X
.sfidx .= .sfidx+s_word
.reg_y =.sfidx ;.Y
.sfidx .= .sfidx+s_word
.reg_sr =.sfidx ;SR
.sfidx .= .sfidx+s_mpusrx
.reg_pc =.sfidx ;PC
.sfidx .= .sfidx+s_mpupcx
.if .def(_SCSI_) <——— conditional assembly for “far” call
.reg_pb =.sfidx ;PB
.sfidx .= .sfidx+s_mpupbx
.endif <——— conditional assembly for “far” call
;
;—————————> register stack frame end <—————————
;
.s_rsf =.sfidx-.sfbase ;register frame size
.sfbase .set .sfidx
;
;—————————> parameter stack frame start <—————————
;
.idptr =.sfidx ;*SCSI_ID
.sfidx .= .sfidx+s_dptr
.lbaptr =.sfidx ;*LBA
.sfidx .= .sfidx+s_dptr
.nblkptr =.sfidx ;*NBLK
.sfidx .= .sfidx+s_dptr
.bufptr =.sfidx ;*BUF
.sfidx .= .sfidx+s_dptr
;
;—————————> parameter stack frame end <—————————
;
.s_psf =.sfidx-.sfbase ;parameter frame size
;
;—————————————————————————————————————————————————————————The function’s postamble is as follows—the postamble fixes up the stack:
Code: Select all
.done rep #m_setr|sr_car ;16-bit registers & clear carry
tsc
adc !#.s_wsf ;ephemeral workspace size
tcs ;expunge workspace
adc !#.s_rsf ;register stack frame size
tax ;“from” address for stack realignment
adc !#.s_psf ;parameter stack frame size
tay ;“to” address for stack realignment
lda !#.s_rsf-1
mvp #0,#0 ;realign stack, gets rid of parameter frame
tyx ;fix up...
txs ;stack pointer
pld
plb
pla
plx
ply
plp
.if .def(_SCSI_) <——— conditional assembly for “far” call
rtl
.else <——— conditional assembly for “near” call
rts
.endif <——— conditional assembly endIn the above code fragments, if the symbol _SCSI_ has been defined, the function’s register stack frame will be configured to include the program bank (PB) and the postamble will be assembled to use RTL to return. If _SCSI_ is not defined, assembly will assume that the function is being called with JSR and PB will not be defined as part of the register frame.
Within the main program, if the SCSI library macros are used to call the BLKREAD function, the call will be made with JSL if _SCSI_ has been defined. It’s all based upon conditional assembly. Here’s the BLKREAD macro:
Code: Select all
;————————————————————————————————————————————————————————
;READ BLOCK(S) FROM SCSI DEVICE
;
;blkread *ID,*LBA,*NBLK,*BUF,'<m1>','<m2>','<m3>','<m4>'
;
; *ID — device bus ID
; *LBA — logical block address
; *NBLK — number of blocks to read
; *BUF — buffer
; <m1> - <m4> parameter addressing mode, ‘d’, ‘f’, ‘n’ or ‘r’
; If addressing mode ‘r’ is specified, the pointer is
; expected to be in .X (LSW) & .Y (MSW). Index register
; widths must be set to 16 bits before invoking this
; macro.
;————————————————————————————————————————————————————————
;
blkread .macro ...
.rp =8
.if @0 == .rp
.np =.rp/2
.ct .set .np
.xr .set 0
.rept .ct
.m .= {{@{.ct+.np}} | %00100000}
.if .m == 'd'
pei @.ct+2
pei @.ct
.else
.if .m == 'f'
pea #@.ct >> 16
pea #@.ct & $ffff
.else
.if .m == 'n'
.if .def(execbank)
pei execbank
.else
phk
phk
.endif
per @.ct
.else
.if .m == 'r'
.if .xr
.error ""+@0$+": 'r' addressing can only be used once"
.else
.xr .set 1
phy
phx
.endif
.else
.error ""+@0$+": mode must be 'd', 'f', 'n' or 'r'"
.endif
.endif
.endif
.endif
.ct .= .ct-1
.endr
.else
.error ""+@0$+": missing parameters"
.endif
.if .def(_SCSI_) <——— start of conditional assembly
jsl blkread
.else
jsr blkread
.endif <——— end of conditional assembly
.endmYes.
When I first embarked on writing 816 code and working out how to manage parameter-passing, I decided that since something had to clean up the stack after a function call, it would be more efficient to have the function handle that task—see the above postamble code. Once a function has been fully debugged, it can be called with the confidence of knowing the stack isn’t going to get boogered up by a bug in the calling program—assuming the caller correctly structures the stack frame. The latter is where the macros get into the picture—they will always build a proper stack frame, and will rebuke the programmer (me) if the wrong number or type of parameters is passed.
Incidentally, functions can freely modify the registers by using <offset>,S addressing. When the registers are pulled in the postamble, the caller will see the changes. In many of my functions, DP is pointed to SP+1 after local workspace has been allocated on the stack, which is real convenient for rewriting the registers, accessing the caller’s parameters, etc., using direct-page addressing. Here’s a typical example of how I do it:
Code: Select all
php
rep #m_setr|sr_bdm ;16-bit registers
phy ;save machine state
phx
pha
phb
phd
sec
tsc
sbc !#.s_wsf ;reserve workspace
tcs
inc ;DP is the workspace...
tcd ;stack frame pointerWith the above sequence, the first byte of workspace could be accessed with LDA $00. Something such as STA .reg_c would modify the entry value of the accumulator.