! 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"

%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

%ownrecord(semaphore fm) disc interrupt = 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

%ownbyte disc r int = 0

%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,
                              %integer seek required)

%ownbyte null byte = 0

%ownbyte disc status = 0

%constinteger fatal = 16_40

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

%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')
      disc r int = disc r %while disc r int # 16_A0
      {} put sym('2')
      disc w = op
      {} put sym('3')
      i = disc r %until i = 16_A1
      {} put sym('4')
      disc w = 16_FF
      {} put sym('5')
      %for i = 1, 1, 50 %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, 50 %cycle;  %repeat;  ! Placate disc controller
      %if op & 16_40 = 0 %start
         ! No interrupt expected here
         %cycle
            {} put sym('9')
         %repeat %until disc r = 16_FF
      %else
         ! Interrupt expected
!        %cycle
            {} put sym('W')
            semaphore wait(disc interrupt)
!        %repeat %until disc r int = 16_FF
      %finish
      {} put sym('A')
      disc w = 16_FE
!      %cycle
         {} put sym('B')
         semaphore wait(disc interrupt)
!      %repeat %until disc r int & 16_80 = 0
      {} put sym('C')
      disc status = disc r
      {} put sym('D')
      disc w = 0
      semaphore wait(disc interrupt)
      {} 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(interrupt handler fm) handler = 0
   %ownrecord(disc address fm) da = 0
   %integer i
   %label h, x
      !! printstring("Initialise disc: ")
      !! write(sectors, 0);  printstring("s ")
      !! write(heads, 0);  printstring("h ")
      !! write(cylinders, 0);  print symbol('c')
      !! newline
      setup semaphore(disc interrupt)
      setup interrupt handler(handler, addr(h))
      add interrupt handler(handler, 3)
      disc a = 255;  ! Arm the interface
      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
      %return

   H: D0 = 50
   X: *move.l D1, D1
      *dbra D0, X
      {} put sym('I')
      *move.b disc r, disc r int
      int signal semaphore(disc interrupt)
      return from interrupt
      *move.l D0, D0;  ! In case of "optimisations"
%end

%routine split address(%integer block, unit, %record(disc address fm)%name address)
   %owninteger last cylinder = -1
   %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
      %if c = last cylinder %start
         address_seek required = 0
      %else
         last cylinder = c
         address_seek required = 1
      %finish
%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)
         %if a_seek required # 0 %start
            command(dc seek, 8, a_unit, 0)
            analyse error(dc seek, a) %if disc status # 0
         %finish
         %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)
         %if a_seek required # 0 %start
            command(dc seek, 8, a_unit, 0)
            analyse error(dc seek, a) %if disc status # 0
         %finish
         %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
