/* ACCESS HANDLING COROUTINE The disc handling coroutine loops processing disc requests. Requests from the file handler WORK coroutine have priority over requests implicit in the cache. When such requests cannot be satisfied, generally for want of a cache slot, then an implicit cache slot request is processed hence freeing a slot. The WORK coroutine request will (probably) then be able to be satisfied. The types of request available are: GETBLOCK Access a disc block. UNTILFREE Wait for a block to be written to disc. Return is immediate should the block not be present in the cache, or should the block be present and available. */ SECTION "ACCESS" GET "libhdr" GET "fh2hdr" GET "manhdr" GET "iohdr" LET Access() BE $( // Assume work to do Access.Idle := FALSE // Assume we are to resume the main coroutine Next.Co := Main.Co Block := 0 // Identify type of access request SWITCHON Access.Action INTO $( DEFAULT: // Unrecognised request Abort(Abort.ActionNotKnown) LOOP CASE Action.Nil: ENDCASE CASE Action.ResumeWork: Next.Co := Work.Co Access.Action := Action.Nil ENDCASE CASE Action.GetBlock: Block := FindSlot(AnyKey, Available) // If no slot is available go process // an outstanding cache operation IF Block=0 ENDCASE // Unless this is a get available slot // request, read the block UNLESS Access.Key=0 DO $( block!cache.status := read /***************************/ Device(Act.Read, Access.Key, Block) $) // Schedule to return the result to the WORK coroutine SetCoResult(Work.Co, Block) Access.Action := Action.Nil Block := 0 ENDCASE CASE Action.UntilFree: Block := FindSlot(Access.Key, Read|Write) IF Block=0 DO // The block is now free $( // Schedule to resume the WORK coroutine SetCoResult(Work.Co) Access.Action := Action.Nil ENDCASE $) // If we can hasten the freeing of the // awaited block ... UNLESS Block!Cache.AfterKey=0 DO // We cannot hasten its availability // at this stage Block := 0 $) // Process an implicit cache request // Find the most outstanding disc operation // prefering the awaited block if possible IF Block=0 DO Block := FindSlot(AnyKey, Read|Write) IF Block=0 DO // If there is no such block there is no work to // be done and we can become idle $( Access.Idle := TRUE PktWait(0, 0) LOOP $) // Otherwise we should read/write the block to disc Device([Block!Cache.Status=write -> Act.write, Act.read], Block!Cache.Key, Block) // and then go repeat the process ... $) REPEAT AND Device(Action, Key, Buffer) BE // Perform requested function on this handler's // drive. $( LET KeyREMCyl = Key REM N.BlocksPerCylinder LET Status, Res = 0, ? UNLESS KeyLowerBound<=Key<=KeyUpperBound DO Abort(Abort.KeyOutOfRange, Key) REPEAT // (REPEAT in order that a continue from // abort is less dangerous) // Set the Buffer key Buffer!Cache.Key := Key FOR i = 1 TO 8 DO // Try the disc operation $( // Check the checksum if writing // Mark as ready-to-write all cache Buffers which // depended on this Buffer being written IF Action=Act.Write DO CheckBlock(Buffer) /****************************************************/ // Reset the cache status // Buffer!Cache.Status := Available /****************************************************/ // Send the request packet to the driver $( LET Cyl = LowerCylinder+Key/N.BlocksPerCylinder Seek(Cyl) // Reset the resumption coroutine Next.Co := Main.Co Res := SendPkt(-1, DeviceId, Action, ?, ?, Buffer, Size.Block, UnitNo, Cyl, KeyREMCyl REM N.Surfaces, [KeyREMCyl/N.Surfaces]*N.SectorsPerBlock+Sector.Origin) $) // Should the transfer be successful ... IF Res=0 DO // A successful operation (perhaps at last) // Do any cache updating necessary $( // Check the checksum anyway UNLESS Checksum(Buffer, Size.Block)=0 TEST Action=Act.Read THEN GOTO failaccess ELSE Abort(Abort.InvalidCheckSum) REPEAT /************************************************/ buffer!cache.status := available /************************************************/ IF Action=Act.Write FOR i = 1 TO !Cache DO $( LET Cachei = Cache!i IF Cachei!Cache.Status=Write & Cachei!Cache.AfterKey=Key DO Cachei!Cache.AfterKey := 0 $) RETURN $) failaccess: // If the transfer was unsuccessful, keep a // record of the error bits Status := Status | Res // Status thus contains a 'cumulative' // contoller status word // Also re-establish the status Buffer!Cache.Status := Action=Act.Write -> Write, Read $) Abort(Abort.DiscError, Status) seek(2) seek(-80) $) REPEAT // The abort may be continued. AND Seek(Cylinder) BE SendPkt(-1, DeviceId, Act.Seek, ?, ?, ?, ?, UnitNo, Cylinder) AND FindSlot(Key, Status) = VALOF // Find the least recently used disc block // with the given status $( let c = 0 unless key=0 do $( for i = 1 to !cache if cache!i!cache.key=key do c := c + 1 if c>=2 do abort(999) $) FOR i = !Cache TO 1 BY -1 DO $( // Match if KEY=0 or a key match // AND block available if available wanted // or some overlap on status bits. LET Cachei = Cache!i LET CacheKey = Cachei!Cache.Key LET CacheStatus = Cachei!Cache.Status IF [Key=0 | Key=CacheKey] & [CacheStatus=0 -> Status=0, (CacheStatus&Status) \= 0] RESULTIS ShuffleCache(i, 0) $) // If there isn't one ... RESULTIS 0 $) AND ShuffleCache(Index, Key) = VALOF // Move the allocated slot to the top of the cache // list to indicate most recently used // The cache status will always be 'available' $( LET Slot = ? IF Index=0 DO // If a block is given as opposed to an index, // convert the block to an index $( Index := 1 FOR i = 1 TO !Cache IF Key=Cache!i!Cache.Key DO $( Index := i BREAK $) $) Slot := Cache!Index // Promote the block to cache slot 1 FOR i = Index-1 TO 1 BY -1 DO Cache![i+1] := Cache![i] Cache!1 := Slot RESULTIS Slot $) AND SetCoResult(Coroutine, Result) BE $( Access.Action := Action.Nil Next.Co, Co.Result := Coroutine, Result $) AND Resume(Action, Key) = VALOF // Resume the relevent coroutine after a request // is made pending. The choice depends upon // whether the access coroutine is idle or not. $( Access.Action, Access.Key := Action, Key RESULTIS ResumeCo([Access.Idle -> Access.Co, Main.Co]) $)