! Ambit disc controller module, speaking to RWT-style interface.
! Include before disc independent part.

%externalstring(47) copyright ambit %alias "GDMR_(C)_DISC.AMBIT" = %c
   "Copyright (C) 1987 George D.M. Ross"

! Because of the utterly crazy way the hardware operates, an interrupt-driven
! controller cannot be made to work in any sensible manner whatsoever: we get
! interrupts to tell us that things which happen more-or-less instantaneously
! have happened, but we have to busy-wait for things which do take a while,
! such as seeks and disc transfers!  Interrupt mode is therefore considered 
! useless with this hardware, which leaves us a choice of ways to poll the
! device: straightforward polling in the command routine interacts non-optimally
! with a reasonable choice of disc process priority; busy-waiting in a lower
! priority process could be most responsive, but would soak up valuable CPU
! time and bus bandwidth; while polling every clock tick would use least other
! resources, but could add noticeable latency.  This version of the driver
! tries option 3.


! Disc controller definitions

%constinteger read    bit = 16_01
%constinteger write   bit = 16_02
%constinteger intwait bit = 16_04
%constinteger int     bit = 16_08
%constinteger doing   bit = 16_10
%constinteger adding  bit = 16_20

%owninteger disc reads  = 0
%owninteger disc writes = 0
%owninteger disc HW     = 0

%constinteger sector 0 mask = sectors 0 - 1
%constinteger sector 1 mask = sectors 1 - 1

%constinteger unit shift = 28
%constinteger block mask = \(16_FFFFFFFF << unit shift)

@16_ED0000 %readonly %volatile %byte disc r;  ! Read data
@16_ED0000 %writeonly          %byte disc w;  ! Write data
!16_ED0001 %writeonly          %byte disc a;  ! Arm interrupts
!16_ED0001 %readonly %volatile %byte disc d;  ! Disarm interrupts

%constinteger dc initialise   = 16_02
%constinteger dc initialise 1 = 16_07
%constinteger dc read disc    = 16_21
%constinteger dc write disc   = 16_22
%constinteger dc drive status = 16_25
%constinteger dc seek         = 16_28
%constinteger dc read buffer  = 16_41
%constinteger dc write buffer = 16_42

%recordformat disc address fm(%byte unit, head,
                              %byte track l, track h,
                              %byte sector,
                              %byte z1, z2, z3)

%ownbyte null byte = 0

%ownbyte disc status = 0

%constinteger fatal = 16_40

%owninteger soft errors = 0
%owninteger hard errors = 0


! Clock definitions and interrupt handler

%constinteger clock priority = 6

%ownrecord(semaphore fm) poll wait = 0
%ownbyte polling = 0

%routine start ticking
   %ownrecord(interrupt handler fm) handler = 0
   %label clock handler, no
      setup interrupt handler(handler, addr(clock handler))
      add interrupt handler(handler, clock priority)
      %return

clock handler:
      *tst.b polling             { Should we be checking the disc interface?
      *beq no                    { No, nothing more, ->
      *move.b disc r, D0         { Read disc data register
      *cmp.b #16_FF, D0          { Operation done?
      *bne no                    { Not yet, ->
      *clr.b polling             { So we don't wake up next time
      int signal semaphore(poll wait)
no:   return from interrupt
      *move.l D0, D0;  ! HMD-proof it
%end


! Disc handler proper starts here....

%routine analyse error(%integer command, %record(disc address fm)%name a)
   printstring("Disc error: status ");  phex2(disc status)
   printstring(", command ");  phex2(command)
   %if a ## nil %start
      printstring(", address ")
      write(a_unit, 0);  printstring("u ")
      phex2(a_track h);  phex2(a_track l)
      printstring("t ")
      phex2(a_head);  printstring("h ")
      phex2(a_sector);  print symbol('s')
   %finish
   %if disc status & fatal = 0 %start
      soft errors = soft errors + 1
      printstring(" (soft)")
   %else
      hard errors = hard errors + 1
      printstring(" (*HARD*)")
   %finish
   newline
%end

%routine command(%integer op, bytes, %bytename buffer, %integer fill)
   ! bytes +  -> write to controller
   ! bytes 0  -> no parameter part
   ! bytes -  -> read from controller
   ! intwait # 0 iff interrupts are to be used
   %integer i, junk
      !! put sym('.');  put long(op);  put sym('.')
      !! put sym('1')
      %while disc r # 16_A0 %cycle;  %repeat
      !! put sym('2')
      disc w = op
      !! put sym('3')
      %while disc r # 16_A1 %cycle;  %repeat
      !! put sym('4')
      disc w = 16_FF
      !! put sym('5')
      %for i = 1, 1, 20 %cycle; %repeat;  ! Placate disc controller
      !! put sym('6')
      %if bytes > 0 %start
         ! Write to controller
         D0 = bytes - 1
         A0 = addr(buffer)
      wl:*move.b (A0)+, disc w
         *dbra D0, wl
         %if fill > 0 %start
            D0 = fill - 1
         wf:*move.b #0, disc w
            *dbra D0, wf
         %finish
      %else %if bytes < 0
         D0 = -bytes - 1
         A0 = addr(buffer)
      rl:*move.b disc r, (A0)+
         *dbra D0, rl
         %if fill > 0 %start
            D0 = fill - 1
         rf:*move.b disc r, D1
            *dbra D0, rf
         %finish
      %finish
      !! put sym('7')
      disc w = 16_FF
      !! put sym('8')
      %for i = 1, 1, 20 %cycle;  %repeat;  ! Placate disc controller
      %if op & 16_20 = 0 %start
         ! Controller-only operation.  Poll for completion here
         %cycle
            !! put sym('9')
         %repeat %until disc r = 16_FF
      %else
         ! Operation involves a disc transfer.  Rather than polling
         ! here we let the clock tick handler do it for us.
         !! put sym('P')
         polling = 1
         semaphore wait(poll wait)
      %finish
      !! put sym('A')
      disc w = 16_FE
      !! put sym('B')
      %while disc r & 16_80 # 0 %cycle;  %repeat
      !! put sym('C')
      disc status = disc r
      !! put sym('D')
      disc w = 0
      !! put sym('#')
%end

%routine initialise disc
   %ownbytearray b0(0 : 15) =
         255, 0, 0,        { Pre-compensation }
         0, 0, 0,          { RWC }
         4,                { Buffered step }
         (cylinders 0 - 1) & 255,
         (cylinders 0 - 1) >> 8,
                           { Max track }
         0, 0,             { Last user track (disabled) }
         0,                { Bad track mapping }
         0,                { Removable drive }
         0, 0, 0           { Reserved, mbz }
   %ownbytearray b1(0 : 15) =
         255, 0, 0,        { Pre-compensation }
         0, 0, 0,          { RWC }
         4,                { Buffered step }
         (cylinders 1 - 1) & 255,
         (cylinders 1 - 1) >> 8,
                           { Max track }
         0, 0,             { Last user track (disabled) }
         0,                { Bad track mapping }
         0,                { Removable drive }
         0, 0, 0           { Reserved, mbz }
   %ownrecord(disc address fm) da = 0
   %integer i
      !! printstring("Initialise disc: ")
      !! write(sectors, 0);  printstring("s ")
      !! write(heads, 0);  printstring("h ")
      !! write(cylinders, 0);  print symbol('c')
      !! newline
      setup semaphore(poll wait)
      start ticking
      disc w = 0;    ! Initialise the protocol
      ! Set up drive 0
      command(dc write buffer, 16, b0(0), 512 - 16)
      %if disc status # 0 %start
         printstring("Disc init 0: write buffer status ")
         phex2(disc status);  newline
         %stop
      %finish
      command(dc initialise, 0, null byte, 0)
      %if disc status # 0 %start
         printstring("Disc init 0: init status ");  phex2(disc status)
         newline
         %stop
      %finish
      command(dc seek, 8, da_unit, 0)
      !! printstring("0 seek status ");  phex2(disc status);  newline
      drive 0 size = heads 0 * sectors 0 * cylinders 0 %if disc status = 0
      ! Now for drive 1
      command(dc write buffer, 16, b1(0), 512 - 16)
      %if disc status # 0 %start
         printstring("Disc init 1: write buffer status ")
         phex2(disc status);  newline
         %stop
      %finish
      command(dc initialise 1, 0, null byte, 0)
      %if disc status # 0 %start
         printstring("Disc init 1: init status ");  phex2(disc status)
         newline
         %stop
      %finish
      da_unit = 1
      command(dc seek, 8, da_unit, 0)
      !! printstring("1 seek status ");  phex2(disc status);  newline
      drive 1 size = heads 1 * sectors 1 * cylinders 1 %if disc status = 0
%end

%routine split address(%integer block, unit, %record(disc address fm)%name address)
   %integer c, h, s
      address_unit = unit
      %if unit = 0 %start
         h = block // sectors 0;  s = block - h * sectors 0
         c = h // heads 0;  h = h - c * heads 0
      %else {%if unit = 1
         h = block // sectors 1;  s = block - h * sectors 1
         c = h // heads 1;  h = h - c * heads 1
      %finish
      !! printstring("Split address ");  write(block, 0)
      !! printstring(" -> ")
      !! write(c, 0);  printstring("c ")
      !! write(h, 0);  printstring("h ")
      !! write(s, 0);  print symbol('s');  newline
      address_head = h
      address_sector = s
      address_track l = c & 255;  address_track h = c >> 8
      address_z1 = 0;  address_z2 = 0;  address_z3 = 0
%end

%integerfn do disc read(%integer block, n, %bytename buffer)
   %record(disc address fm) a
   %integer unit, sector mask
      %result = -2 %if n <= 0
      unit = block >> unit shift;  block = block & block mask
      !! printstring("Unit ");  write(unit, 0)
      !! printstring(" block ");  write(block, 0);  newline
      %if unit = 0 %start
         %result = -2 %unless 0 <= block %and block + n <= drive 0 size
         sector mask = sector 0 mask
      %else {%if unit = 1
         %result = -2 %unless 0 <= block %and block + n <= drive 1 size
         sector mask = sector 1 mask
      %finish
      disc reads = disc reads + 1
      %cycle
         split address(block, unit, a)
         %cycle
            command(dc read disc, 8, a_unit, 0)
            analyse error(dc read disc, a) %if disc status # 0
            %if disc status & fatal = 0 %start
               command(dc read buffer, -512, buffer, 0)
               analyse error(dc read buffer, a) %if disc status # 0
            %finish
            %result = disc status ! 16_80000000 %if disc status & fatal # 0
            n = n - 1;  %result = 0 %if n <= 0
            block = block + 1
            buffer == buffer [512]
            a_sector = (a_sector + 1) & sector mask
         %repeat %until a_sector = 0
      %repeat
%end

%integerfn do disc write(%integer block, n, %bytename buffer)
   %record(disc address fm) a
   %integer unit, sector mask
      %result = -2 %if n <= 0
      unit = block >> unit shift;  block = block & block mask
      %if unit = 0 %start
         %result = -2 %unless 0 <= block %and block + n <= drive 0 size
         sector mask = sector 0 mask
      %else {%if unit = 1
         %result = -2 %unless 0 <= block %and block + n <= drive 1 size
         sector mask = sector 1 mask
      %finish
      !! printstring("Disc write to ");  write(block, 0);  newline
      disc writes = disc writes + 1
      %cycle
         split address(block, unit, a)
         %cycle
            command(dc write buffer, 512, buffer, 0)
            analyse error(dc write buffer, a) %if disc status # 0
            %if disc status & fatal = 0 %start
               command(dc write disc, 8, a_unit, 0)
               analyse error(dc write disc, a) %if disc status # 0
            %finish
            %result = disc status ! 16_80000000 %if disc status & fatal # 0
            n = n - 1;  %result = 0 %if n <= 0
            block = block + 1
            buffer == buffer [512]
            a_sector = (a_sector + 1) & sector mask
         %repeat %until a_sector = 0
      %repeat
%end

%end %of %file
