! Partition control module.

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

! To do: don't signal %while....

%option "-low-nonstandard-nocheck-nodiag-noline-nostack"
!%option "-low-nonstandard"

%include "Moose:Mouse.Inc"

%systemintegerfnspec global heap get(%integer size)

%constinteger cache size = 32
%constinteger chunk size = 7;  ! 2**n - 1
! The cache size must be larger than the expected number of calling
! processes, otherwise we may end up overwriting cache entries before we've
! even had a chance to use one of the blocks.

%constinteger retry limit = 3

{} %include "GDMR_H:Dump.Inc"


! Error codes

%constinteger partition ID         error = -51
%constinteger partition validity   error = -52
%constinteger partition block ID   error = -53
%constinteger partition protection error = -54
%constinteger partition readin     error = -55
%constinteger buffer alignment     error = -56


! Dictionary access

%externalroutinespec FS insert(%string(31) name, %integer value)
%externalpredicatespec FS lookup(%string(31) name, %integername value)


! Partition tables

%constinteger last partition = 31
%recordformat partition table fm(%integer unit, disc start, size, flags)

%constinteger partition shift = 24
%constinteger block mask      = 16_00FFFFFF


! Disc tables

%recordformat header partition fm(%integer start, finish, lp, flags,
                                  %string(15) name)
%recordformat disc header fm((%integer checksum,
                              %record(header partition fm)%array p(1 : 15)) %c
                         %or %integerarray x(1 : 128))

%constinteger hazarded   flag = 1
%constinteger structured flag = 2
%constinteger valid      flag = 4

%recordformat bad list fm((%integer checksum, %integerarray block(1 : 511)) %c
                      %or %integerarray x(0 : 511))

! Cache tables

%constinteger cache chunk mask = \chunk size

%recordformat block fm(%bytearray b(0 : 511))

%recordformat cache fm(%integer chunk, stamp, state,
                       %record(semaphore fm) arrival,
                       %integerarray status(0 : chunk size),
                       %record(block fm)%array b(0 : chunk size))


! Global tables

%recordformat tables fm(%integer cache stamp,
                        %record(partition table fm)%array %c
                           partition table(1 : last partition),
                        %record(disc header fm)%array disc header(0 : 1),
                        %record(bad list fm)%array disc bad list(0 : 1),
                        %integerarray disc size(0 : 1),
                        %record(cache fm)%array cache(1 : cache size),
                        %integer cache read hits,     { Already there
                        %integer cache read misses,   { Brought in
                        %integer cache write copies,  { There, so overwritten
                        %integer cache write misses,  { Not there, so ignored
                        %record(semaphore fm) cache mutex)

%ownrecord(tables fm)%name tables == nil


! Miscellaneous other stuff

%systemroutinespec phex(%integer i)

!%externalintegerfnspec disc size(%integername size 0, size 1)
!%externalintegerfnspec disc read(%integer block, %bytename buffer)
!%externalintegerfnspec disc read N(%integer block, blocks, %bytename buffer)
!%externalintegerfnspec disc write(%integer block, %bytename buffer)

%include "GDMR_H:Disc.Inc"

%constinteger infinity = 16_7FFFFFFF


! Initialisation

%routine initialise partitions
   %record(partition table fm)%name p
   %record(header partition fm)%name hp
   %integer status, i, j, c, drive
   %bytename b
      %if FS lookup("FS_PARTITION_TABLES", i) %start
         ! Already initialised
         !! printstring("Already initialised, tables at ")
         !! phex(i);  newline
         tables == record(i)
         semaphore wait(tables_cache mutex)
         signal semaphore(tables_cache mutex)
      %else
         tables == record(global heap get(size of(tables)));  tables = 0
         setup semaphore(tables_cache mutex)
         FS insert("FS_PARTITION_TABLES", addr(tables))
         !! printstring("Initialising, tables at ")
         !! phex(addr(tables));  newline
         !! printstring("Cache: ");  write(cache size, 0)
         !! printstring(" * ");  write(chunk size + 1, 0)
         !! printstring(" (");  write(cache size * (chunk size + 1) * 512, 0)
         !! printstring(" bytes)");  newline
         %for i = 1, 1, cache size %cycle
            tables_cache(i)_chunk = -1
            setup semaphore(tables_cache(i)_arrival)
         %repeat
         status = disc size(tables_disc size(0), tables_disc size(1))
         %if status # 0 %start
            printstring("Can't find disc size");  newline
            %stop
         %finish
         !! printstring("Size 0 ");  write(tables_disc size(0), 0)
         !! printstring(", size 1 ");  write(tables_disc size(1), 0);  newline
         %for drive = 0, 1, 1 %cycle
            !! printstring("Drive ");  write(drive, 0);  newline
            %if tables_disc size(drive) > 0 %start
               status = disc read((drive << 28) ! (tables_disc size(drive) - 1),
                                  byteinteger(addr(tables_disc header(drive))))
               %if status # 0 %start
                  printstring("Initialise partitions: read header status ")
                  phex(status);  newline
                  %stop
               %finish
               !! printstring("Disc tables read from ")
               !! write(tables_disc size(drive) - 1, 0);  newline
               c = 0
               c <- c + tables_disc header(drive)_x(i) %for i = 1, 1, 128
               %if c # 0 %start
                  printstring("Initialise partitions: disc header checksum error")
                  newline
                  %stop
               %finish
               status = disc read((drive << 28) ! (tables_disc size(drive) - 8),
                                  byteinteger(addr(tables_disc bad list(drive))))
               %if status # 0 %start
                  printstring("Initialise partitions: read bad list status ")
                  phex(status);  newline
                  %stop
               %finish
               !! printstring("Disc bad list read from ")
               !! write(tables_disc size(drive) - 8, 0);  newline
               c = 0
               c <- c + tables_disc bad list(drive)_x(i) %for i = 0, 1, 511
               %if c # 0 %start
                  printstring("Initialise partitions: disc bad list checksum error ")
                  write(c, 0);  newline
                  !! dump(512 * 4, byteinteger(addr(tables_disc bad list(drive))))
                  !? %stop
               %finish
               printstring("Drive ");  write(drive, 0)
               printstring(" bad list:");  newline
               %for i = 1, 1, 511 %cycle
                  %exit %if tables_disc bad list(drive)_block(i) <= 0
                  write(tables_disc bad list(drive)_block(i), 9)
                  newline %if i & 7 = 0
               %repeat
               newline %if i & 7 # 0 %c
                       %and tables_disc bad list(drive)_block(1) > 0
               %for i = 1, 1, 15 %cycle
                  hp == tables_disc header(drive)_p(i)
                  %continue %if hp_flags & valid flag = 0
                  printstring("PP ");  write(drive, 0)
                  print symbol('.');  write(i, 0)
                  hp_lp = hp_lp + (drive << 4)
                  %if 0 < hp_lp <= last partition %start
                     p == tables_partition table(hp_lp)
                     p_unit = drive << 28
                     p_disc start = hp_start
                     p_size = hp_finish - hp_start + 1
                     p_flags = hp_flags
                     printstring(": LP ");  write(hp_lp, 0)
                     printstring(" (");  printstring(hp_name)
                     printstring("), ");  write(hp_start, 0)
                     printstring("..");  write(hp_finish, 0)
                     printstring(" (");  write(p_size, 0)
                     printstring(" blocks)")
                     printstring(", unstructured") %if p_flags & structured flag = 0
                     printstring(", *hazarded*") %if p_flags & hazarded flag # 0
                  %else
                     printstring("*bad LP number ")
                     write(hp_lp, 0)
                     print symbol('*')
                  %finish
                  newline
               %repeat
            %finish
         %repeat
         signal semaphore(tables_cache mutex)
      %finish
%end


! Translation & I/O

%integerfn do read(%integer block, %bytename buffer)
   %record(partition table fm)%name p
   %integer partition
      partition = block >> partition shift
      %result = partition ID error %unless 0 < partition <= last partition
      p == tables_partition table(partition)
      %result = partition validity error %if p_flags & valid flag = 0
      block = block & block mask
      %result = partition block ID error %unless 0 <= block < p_size
      %result = disc read N(p_unit ! (block + p_disc start),
                            chunk size + 1, buffer)
%end

%integerfn do read one(%integer block, %bytename buffer)
   %record(partition table fm)%name p
   %integer partition
      !! printstring("Do read one: ");  phex(block);  newline
      partition = block >> partition shift
      %result = partition ID error %unless 0 < partition <= last partition
      p == tables_partition table(partition)
      %result = partition validity error %if p_flags & valid flag = 0
      block = block & block mask
      %result = partition block ID error %unless 0 <= block < p_size
      %result = disc read(p_unit ! (block + p_disc start), buffer)
%end

%integerfn do write(%integer block, %bytename buffer)
   %record(partition table fm)%name p
   %integer partition
      !! printstring("Do write: ");  phex(block);  newline
      partition = block >> partition shift
      %result = partition ID error %unless 0 < partition <= last partition
      p == tables_partition table(partition)
      %result = partition validity error %if p_flags & valid flag = 0
      %result = partition protection error %if p_flags & hazarded flag = 0
      block = block & block mask
      %result = partition block ID error %unless 0 <= block < p_size
      %result = disc write(p_unit ! (block + p_disc start), buffer)
%end


! Cache management

%integerfn cache stamp
   tables_cache stamp = tables_cache stamp + 1
   %result = tables_cache stamp
%end

%record(cache fm)%map cache find(%integer chunk)
   %record(cache fm)%name c
   %integer oldest, oldest stamp = infinity
   %integer i
      %for i = 1, 1, cache size %cycle
         c == tables_cache(i)
         %if c_chunk = chunk %start
            !! printstring("Found chunk ");  phex(chunk)
            !! printstring(" at ");  write(i, 0)
            !! newline
            %result == c
         %finish
         oldest = i %and oldest stamp = c_stamp %if c_stamp < oldest stamp
      %repeat
      !! printstring("Can't find chunk ");  phex(chunk)
      !! printstring(", oldest is ");  write(oldest, 0)
      !! newline
      %result == tables_cache(oldest)
%end

%routine copy buffer(%record(*)%name from, to)
   ! From (A0), to (A1)
   D0 = 127
L: *move.l (A0)+, (A1)+
   *dbra D0, L
%end


! Exported interface, talking via cache

%externalintegerfn partition read(%integer block, %record(*)%name buffer)
   %record(cache fm)%name c
   %integer status, chunk, offset, i, tries
!! %integer CPU before, CPU after
      !! CPU before = CPU time
      initialise partitions %if tables == nil
      %result = buffer alignment error %if addr(buffer) & 1 # 0
      chunk = block & cache chunk mask;  offset = block & chunk size
      !! printstring("Partition read ");  phex(block)
      !! printstring(" (chunk ");  phex(chunk)
      !! printstring(" offset ");  write(offset, 0)
      !! print symbol(')');  newline
      semaphore wait(tables_cache mutex)
      c == cache find(chunk)
      %if c_chunk = chunk %start
         !! printstring("Hit at ");  phex(addr(c));  newline
         tables_cache read hits = tables_cache read hits + 1
         c_stamp = infinity
         %while c_state # 0 %cycle
            signal semaphore(tables_cache mutex)
            semaphore wait(c_arrival)
            semaphore wait(tables_cache mutex)
         %repeat
         %unless c_chunk = chunk %start
            signal semaphore(tables_cache mutex)
            %result = partition readin error
         %finish
         copy buffer(c_b(offset), buffer)
         c_stamp = cache stamp
         signal semaphore(tables_cache mutex)
         !! dump(512, byteinteger(addr(buffer)))
         !! CPU after = CPU time
         !! printstring("Hit: ");  write(CPU after - CPU before, 0);  newline
         %result = c_status(offset)
      %else
         !! printstring("Miss, using ");  phex(addr(c));  newline
         tables_cache read misses = tables_cache read misses + 1
         c_chunk = chunk;  c_stamp = infinity;  ! Prevent reuse
         c_state = -1;  ! In transit
         !! c_chunk = -1;  ! Effectively disable the cache <<<<<<<<<<<<<<<<<<<<
         signal semaphore(tables_cache mutex)
         status = do read(chunk, byteinteger(addr(c_b(0))))
         %if status = 0 %start
            c_status(i) = 0 %for i = 0, 1, chunk size
         %else
            %for i = 0, 1, chunk size %cycle
               !! printstring("Retrying chunk ");  phex(chunk)
               !! printstring(" offset ");  write(i, 0);  newline
               tries = retry limit - 1;  ! Already had one attempt
               %while tries > 0 %cycle
                  status = do read one(chunk + i, byteinteger(addr(c_b(i))))
                  %exit %if status = 0
                  tries = tries - 1
               %repeat
               c_status(i) = status
               {} %if status # 0 %start
               {}    printstring("Chunk ");  phex(chunk)
               {}    printstring(" offset ");  write(i, 0)
               {}    printstring(" status ");  write(status, 0);  newline
               {} %finish
            %repeat
         %finish
         semaphore wait(tables_cache mutex)
         c_state = 0;  ! Arrived
         c_stamp = cache stamp
         copy buffer(c_b(offset), buffer)
         signal semaphore(c_arrival) %while c_arrival_count < 0;  !???
         !! dump(512, byteinteger(addr(buffer)))
         signal semaphore(tables_cache mutex)
         !! CPU after = CPU time
         !! printstring("Miss: ");  write(CPU after - CPU before, 0);  newline
         %result = c_status(offset)
      %finish
%end

! We have to be very careful here about the order we do things in, as the
! disc driver is at liberty to reorder requests from ourselves and our peers.
! In particular, we have to write the block out to the disc FIRST, and only
! when it has got there can we copy into the cache entry (if there is one).
! We don't have to wait for everyone else to finish actively using the slot,
! however, as either we got the disc before the other process, in which case
! it will merely recopy the same data, or we got there after, in which case
! we overwrite its data.  Either way, the result is the same (?)....

%externalintegerfn partition write(%integer block, %record(*)%name buffer)
   %record(cache fm)%name c
   %integer status, chunk, offset, tries = retry limit
      initialise partitions %if tables == nil
      %result = buffer alignment error %if addr(buffer) & 1 # 0
      !! dump(512, byteinteger(addr(buffer)))
      chunk = block & cache chunk mask;  offset = block & chunk size
      !! printstring("Partition write ");  phex(block)
      !! printstring(" (chunk ");  phex(chunk)
      !! printstring(" offset ");  write(offset, 0)
      !! print symbol(')');  newline
      %while tries > 0 %cycle
         status = do write(block, byteinteger(addr(buffer)))
         %exit %if status = 0
         tries = tries - 1
      %repeat
      semaphore wait(tables_cache mutex)
      c == cache find(chunk)
      %if c_chunk = chunk %start
         !! printstring("Hit, copy in");  newline
         tables_cache write copies = tables_cache write copies + 1
         c_stamp = cache stamp
         copy buffer(buffer, c_b(offset))
      %else
         !! printstring("Miss");  newline
         tables_cache write misses = tables_cache write misses + 1
      %finish
      signal semaphore(tables_cache mutex)
      %result = status
%end
      

! Enquiry

%externalroutine partition enquiry(%integername v, s, h)
   %integer  i
      initialise partitions %if tables == nil
      v = 0
      %for i = 1, 1, last partition %cycle
         v = v ! (1 << i) %if tables_partition table(i)_flags & valid flag # 0
      %repeat
      s = 0
      %for i = 1, 1, last partition %cycle
         s = s ! (1 << i) %if tables_partition table(i)_flags & structured flag # 0
      %repeat
      h = 0
      %for i = 1, 1, last partition %cycle
         h = h ! (1 << i) %if tables_partition table(i)_flags & hazarded flag # 0
      %repeat
%end

%externalroutine cache enquiry(%integername crh, crm, cwc, cwm)
   initialise partitions %if tables == nil
   crh = tables_cache read hits
   crm = tables_cache read misses
   cwc = tables_cache write copies
   cwm = tables_cache write misses
%end

%recordformat partition bad list fm(%integer n,
                                    %integerarray b(0 : 511))

%externalroutine partition bad list(%integer pn,
                                    %record(partition bad list fm)%name b)
   %record(bad list fm)%name db
   %record(partition table fm)%name p
   %integer x, end
      b_n = 0
      %return %unless 0 < pn <= last partition
      p == tables_partition table(pn)
      %return %if p_flags & valid flag = 0
      db == tables_disc bad list(p_unit >> 28)
      x = 1
      %cycle
         %return %if db_block(x) <= 0;  ! End of list
         %exit %if db_block(x) >= p_disc start
         x = x + 1
      %repeat
      end = p_disc start + p_size - 1
      %while 0 < db_block(x) <= end %cycle
         b_n = b_n + 1
         b_b(b_n) = db_block(x) - p_disc start
         x = x + 1
      %repeat
%end

%externalroutine partition info(%integer pn,
                                %integername unit, disc start, size, flags)
   %record(partition table fm)%name p
      %return %unless 0 < pn <= last partition
      p == tables_partition table(pn)
      size = 0 %and %return %if p_flags & valid flag = 0
      unit = p_unit;  disc start = p_disc start
      size = p_size;  flags = p_flags
%end

%end %of %file
