;=======================================
; diskutils.s (disk utility routines)
;--------------------------------------
; Error codes are returned as words:
; error in the low byte, source in the
; high byte (see E_ constants in c64.s).
;=======================================

program diskutils own export

list 0
include library
list 0
include prosys
include c64
list 1

export con numdrives=23
con numdrvtypes=11
con numparttypes=9

data byte drvtypes[]=
  0,1,41,71,81,$e0,$c0,$f0,$80,$71,$72
data word drvdescs[]="none","unknown",
  "1541","1571","1581","CMD FD","CMD HD",
  "CMD RD","CMD RL","uIEC","sd2iec"

data byte parttypes[]=
  0,1,2,3,4,5,6,7,255
data word partdescs[]="not created",
  "CMD native", "1541 emulation",
  "1571 emulation", "1581 emulation",
  "1581 CP/M emulation", "print buffer",
  "foreign","system"

data word infocmd= "M-R\a4\fe\02\0d"
data word info4171= "M-R\c5\e5\02\0d"
data word info1581="M-R\e8\a6\02\0d"
data word xq="X?"
data word pound="#"

export byte drives[numdrives]
byte resp[3]    ;drive response
byte xqresp[9]  ;response to xq query
byte u1[16]
byte gp[6]
byte cmdbuf[64] ;command channel buffer
export byte partinfo[30] ;G-P output

;--------------------------------------
; Get description for drive type.
; pass:   Drive type.
; return: Address of description (or
;         null if not found).
;--------------------------------------
export func word drvdesc
arg byte drvtype
word descptr
word i

begin

descptr=0
i=0
while i < numdrvtypes
  if drvtype=drvtypes[i]
    descptr=drvdescs[i]
    break
  i=i+1
return descptr

end

;--------------------------------------
; Get description for partition type.
; pass:   Partition type.
; return: Address of description (or
;         null if not found).
;--------------------------------------
export func word partdesc
arg byte parttype
word descptr
word i

begin

descptr=0
i=0
while i < numparttypes
  if parttype=parttypes[i]
    descptr=partdescs[i]
    break
  i=i+1
return descptr

end

;--------------------------------------
; Get first free logical file number
; between 8 and 13 inclusive.
; pass: Nothing.
; return: First available file number,
;         or 255 if none available.
;--------------------------------------
export func byte getlfn
word w
byte lfn
byte fn
byte used ;boolean

begin

lfn=255
fn=8
while fn<14
  used=false
  for w=0 to m[$98]-1 ;no. open files
    if m[$259+w]=fn   ;lfn table
      used=true
  if used
    fn=fn+1
  else
    lfn=fn
    break
return lfn

end

;--------------------------------------
; Send command over serial bus. No file
; is opened (low-level kernal routines).
; pass: Device number, command address.
; return: Error code.
;--------------------------------------
export func word sendcmd
arg byte dev
arg word cmd
word err
word w

begin

m[cstatus]=0
jsr clisten,dev
jsr csecond,$6f
if m[cstatus] and $80 ;dev not present?
  err=E_STATUS+m[cstatus]
  jsr cunlsn
  return err
for w=0 to lenstr(cmd)-1
  jsr cciout,m[cmd+w]
jsr cunlsn
return 0

end

;--------------------------------------
; Read command channel. No file is
; opened (low-level kernal routines).
; pass: Device number, buffer address.
; return: Error code.
;--------------------------------------
export func word readcmd
arg byte dev
arg word buffer
word bufptr
word err
byte eof

begin
m[cstatus]=0
jsr ctalk,dev
if m[cstatus] and $80
  return E_STATUS+m[cstatus]
jsr ctksa,$6f
if m[cstatus] and $80
  err=E_STATUS+m[cstatus]
  jsr cuntlk
  return err

bufptr=0
eof=false
repeat
  jsr cacptr
  if m[cstatus] and $40
    eof=true
  m[buffer+bufptr]=m[$a4]
  bufptr=bufptr+1
until eof
m[buffer+bufptr]=0
jsr cuntlk

return 0

end

;--------------------------------------
; Get partition info (pass 255 to get
; current partition info). Use partdesc
; (passing partinfo[0]) to return
; partition type description.
; pass: Device number, partition number.
; return: Error code, partinfo array is
; initialized. See documentation for
; CMD G-P command.
;--------------------------------------
export func word getpartinfo
arg byte dev
arg byte partno
word err
word w

begin

gp[0]='G'
gp[1]='-'
gp[2]='P'
gp[3]=partno
gp[4]=$0d
gp[5]=0

err=sendcmd(dev,gp)
if err>0
  return err
err=readcmd(dev,partinfo)

return err

end

;--------------------------------------
; Read directory header.
; pass:   Device number, buffer address.
; return: Error code.
;--------------------------------------
export func word getdirhead
arg byte dev
arg word buffer ;byte[254]
word i
byte lfn
byte dollar
word err
byte temp

begin

err=0

;send I0 over command channel
err=sendcmd(dev,"I0")
if err>0
  return err

lfn=getlfn
if lfn=255
  return E_DOS+70 ;no channel

dollar='$'
jsr csetlfs,lfn,dev,lfn ;sa=lfn
jsr csetnam,1,(#dollar):<,(#dollar):>
jsr copen
if regf and regfc
  err=rega+E_ACCUM
  jsr cclose,lfn
  return err

m[cstatus]=0

jsr ctalk,dev
if m[cstatus] > 127
  jsr cclose,lfn
  return m[cstatus]+E_STATUS
jsr ctksa,lfn or $60 ;sa=lfn
if m[cstatus] > 127
  jsr cuntlk
  jsr cclose,lfn
  return m[cstatus]+E_STATUS

for i=0 to 253 ;254 bytes (no t/s ptr)
  jsr cacptr
  temp=rega
  if m[cstatus]<>0
    if not m[cstatus] and $40 ;error?
      temp=m[cstatus]
      jsr cuntlk
      m[cstatus]=temp
      jsr cclose,lfn
      return m[cstatus]+E_STATUS
  m[buffer+i]=m[$a4]

jsr cuntlk
jsr cclose,lfn
return 0

end

;--------------------------------------
; Check if drive is a CMD.
; pass:   drive index (from 0, not 8)
; return: boolean
;--------------------------------------
func byte checkcmd
arg byte ndx
word err
byte result

begin

result=true

err=sendcmd(ndx+8,infocmd)
err=readcmd(ndx+8,resp)
resp[2]=0

if cmpstr(resp,"=","FD")
  m[drives+ndx]=$e0
else if cmpstr(resp,"=","HD")
  m[drives+ndx]=$c0
else if cmpstr(resp,"=","RD")
  m[drives+ndx]=$f0
else if cmpstr(resp,"=","RL")
  m[drives+ndx]=$80
else
  result=false

return result

end

;--------------------------------------
; Check if drive is a CBM 1541 or 1571.
; pass:   drive index (from 0, not 8)
; return: boolean
;--------------------------------------
func byte check4171
arg byte ndx
word err
byte result

begin

result=true

err=sendcmd(ndx+8,info4171)
err=readcmd(ndx+8,resp)
resp[2]=0
if cmpstr(resp,"=","54")
  m[drives+ndx]=41
else if cmpstr(resp,"=","57")
  m[drives+ndx]=71
else
  result=false

return result

end

;--------------------------------------
; Check if drive is a CBM 1581.
; pass:   drive index (from 0, not 8)
; return: boolean
;--------------------------------------
func byte check81
arg byte ndx
word err
byte result

begin

result=true

err=sendcmd(ndx+8,info1581)
err=readcmd(ndx+8,resp)
resp[2]=0
if cmpstr(resp,"=","58")
  m[drives+ndx]=81
else
  result=false

return result

end

;--------------------------------------
; Check if drive is a uIEC or sd2iec.
; pass:   drive index (from 0, not 8)
; return: boolean
;--------------------------------------
func byte checksd
arg byte ndx
word err
byte result

begin

result=true

err=sendcmd(ndx+8,xq)
err=readcmd(ndx+8,xqresp)
;skip three bytes ("00,")
if cmpstr(xqresp+3,"=","UIEC",false,4)
  m[drives+ndx]=$71
else if cmpstr(xqresp+3,"=","SD2IEC",false,6)
  m[drives+ndx]=$72
else
  result=false

return result

end

;--------------------------------------
; Enumerate drives on serial bus.
; Based on code by Todd Elliot.
; pass: Address of drive types buffer.
; return: Nothing (drives buffer is
;         initialized).
;--------------------------------------
export proc drvquery
word i

begin

;check for device present
for i=0 to 22
  m[drives+i]=0
  m[cstatus]=0
  jsr clisten,i+8
  jsr csecond,$ff
  if m[cstatus] < $80
    m[drives+i]=i+8
  jsr cunlsn
m[drives+6]=0 ;dev. 14 (printer interface)

;check drive types for devices present
for i=0 to numdrives-1
  if m[drives+i] ;device present?
    if not checkcmd(i)
      if not check4171(i)
        if not check81(i)
          if not checksd(i)
            m[drives+i]=1 ;unknown type

end

;--------------------------------------
; Read a track/sector into memory.
; pass: Device number, track and sector
;       to read, address of buffer.
; return: Error code.
;--------------------------------------
export func word readsect
arg byte dev
arg byte track
arg byte sector
arg word buffer
word err
word i
byte lfn

begin

;open DA ("#") file
lfn=getlfn
if lfn=255
  return E_DOS+70 ;no channel
jsr csetlfs,lfn,dev,lfn ;sa=lfn
jsr csetnam,1,pound:<,pound:>
jsr copen
if regf and regfc
  err=rega+E_ACCUM
  jsr cclose,lfn
  return err

u1[0]='U'
u1[1]='1'
u1[2]=' '
u1[3]=lfn or $30 ;DA channel no. (sa=lfn)
u1[4]=' '
u1[5]='0' ;unit number
wordstr track,u1+6,10,4
wordstr sector,u1+10,10,4
u1[14]=$0d
u1[15]=0

err=sendcmd(dev,u1)
if err>0
  return err
err=readcmd(dev,cmdbuf)
if err>0
  return err
err=(cmdbuf[0] and $0f)*10
err=err+(cmdbuf[1] and $0f)
if err > 0
  err=err+E_DOS
  jsr cclose,lfn
  return err

jsr cchkin,0,lfn ;read from DA file
if regf and regfc
  err=rega+E_ACCUM
  jsr cclose,lfn
  return err

for i=0 to 255
  jsr cchrin
  m[buffer+i]=rega
  if m[cstatus] <> 0
    if not m[cstatus] and $40
      err=m[cstatus]+E_STATUS
      jsr cclose,lfn
      return err
jsr cclrchn

jsr cclose,lfn
return 0

end

;=======================================
; mainline (no code)
;=======================================
begin
end
