DIRECTORY Basics USING[ BITAND, BITNOT, BITOR, LongNumber, LongMult], DDC, DeviceCleanup USING [Await, Item, Reason], DiskFace USING [Label], DLionInputOutput USING [GetRealPage, IOPage, Input, Output], PrincOps USING [wordsPerPage], PrincOpsUtils USING [LowHalf, PageNumberForAddress], SA4000Face, SA4000HeadDLionConstants; SA4000HeadDLion: PROGRAM IMPORTS Basics, DeviceCleanup, DLionInputOutput, PrincOpsUtils EXPORTS SA4000Face SHARES SA4000Face = { OPEN DDC, SA4000Face, SA4000HeadDLionConstants; Base: TYPE = LONG BASE POINTER; CSB: TYPE = MACHINE DEPENDENT RECORD [ cylinder(0): CARDINAL, next(1): Base RELATIVE POINTER TO ChannelCommand, tail(2): IOCBshortPtr, -- last iocb. Used by initiate transferMask(3): WORD, -- naked notify mask. needRecalibrate(4:0..15): BOOL]; -- TRUE => recalibrate required. csb: LONG POINTER TO CSB = LOOPHOLE[DLionInputOutput.IOPage+0B]; FilePageRep: TYPE = MACHINE DEPENDENT RECORD[ n(0): SELECT OVERLAID * FROM cedar => [cedar(0): INT], pilot => [ filePageLo(0): CARDINAL, pilotHi(1): RECORD[ filePageHi(0:0..6): [0..128), -- restricts to 23-bit page numbers (32-bit byte counts) pad1 (0:7..12): [0..64) _ 0, -- always zero immutable (0:13..13): BOOL _ FALSE, -- valid only in label of page 0 temporary (0:14..14): BOOL _ FALSE, -- valid only in label of page 0 zeroSize (0:15..15): BOOL _ FALSE ] ], raw => [ lowHalf(0): CARDINAL, highHalf(1): CARDINAL ] ENDCASE ]; p : Base = LOOPHOLE[LONG[0]]; step: WORD = 200B; -- as part of Output causes heads to move directionInBit: WORD = 100B; -- on => heads move inwards (higher # cyls) driveSelect: WORD = 2000B; -- must be on for drive to listen clearSA1000WriteFault: WORD = 0B; -- reset disk to clear SA1000 fault sa1000PagesPerTrack: CARDINAL = 16; sa1000TracksPerCylinder: CARDINAL = 4; --Assumes SA1004 sa1000Cylinders: CARDINAL = 256; clearSA4000WriteFault: WORD = 3000B; -- reset disk to clear SA4000 fault sa4000PagesPerTrack: CARDINAL = 28; sa4000TracksPerCylinder: CARDINAL = 8; --Assumes SA4008 sa4000Cylinders: CARDINAL = 202; q2040PagesPerTrack: CARDINAL = 16; q2040TracksPerCylinder: CARDINAL = 8; -- assumes q2040 q2040Cylinders: CARDINAL = 512; diskDeviceHandle: DeviceHandle = LOOPHOLE[1]; --#nulldeviceHandle errorMask: WORD = 77B; -- selects error bits in the device status KCtl: CARDINAL = 3; -- number of disk control register KStatus: CARDINAL = 3; -- number of disk status register startDiskCode: WORD = 2040B; -- control word sent to start the disk globalStateSize: PUBLIC CARDINAL _ 0; -- no fixup IOCB needed. nullDeviceHandle: PUBLIC DeviceHandle _ LOOPHOLE[177777B]; operationSize: PUBLIC CARDINAL _ SIZE[IOCB]; driveIsSA1000: BOOL; pagesPerTrack: CARDINAL; sectorsPerCyl: CARDINAL; maxCylinderNum: CARDINAL; -- last cylinder maxHeadNum: CARDINAL; -- last head resetDisk: WORD; -- = clearSA?000WriteFault totalErrors: PUBLIC CARDINAL _ 0; -- total errors reported by Poll pollRaceSpin: LONG CARDINAL _ 0; recalSeekSpin: LONG CARDINAL _ 0; potentialKludgeErrors: LONG CARDINAL _ 0; GetDeviceAttributes: PUBLIC PROC [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = { RETURN[maxCylinderNum + 1, maxHeadNum + 1, 0, pagesPerTrack]}; GetTrueDeviceAttributes: PUBLIC PROC [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = { [cylinders, movingHeads, fixedHeads, sectorsPerTrack] _ GetDeviceAttributes[device]; }; GetNextDevice: PUBLIC PROC [device: DeviceHandle] RETURNS [DeviceHandle] = { RETURN[IF device = nullDeviceHandle THEN diskDeviceHandle ELSE nullDeviceHandle]}; Initialize: PUBLIC PROC [t: WORD, globalState: GlobalStatePtr] = { csb.next _ nil; csb.tail _ nil; csb.transferMask _ t; csb.needRecalibrate _ TRUE; Reset[diskDeviceHandle]; -- reset the disk }; InitializeCleanup: PUBLIC PROC = { OPEN DeviceCleanup; item: Item; sCsb: CSB; DO reason: Reason = Await[@item]; SELECT reason FROM turnOff, disconnect, kill => {WHILE GetDDCHardwareBits[].firmwareBusy DO ENDLOOP; sCsb _ csb^}; turnOn => {sCsb.cylinder_csb.cylinder; sCsb.needRecalibrate_TRUE; csb^ _ sCsb}; ENDCASE ENDLOOP }; Initiate: PUBLIC PROC [o: OperationPtr] = { OPEN iocb: LOOPHOLE[o, IOCBlongPtr]; iocbShort: IOCBshortPtr = LOOPHOLE[PrincOpsUtils.LowHalf[@iocb]]; sectorsInPresentCyl: CARDINAL; { pageAddr: LONG POINTER _ o.dataPtr; pageCount: CARDINAL = IF o.incrementDataPtr THEN o.pageCount ELSE 1; PageCrossCheck[iocbShort]; SELECT DataOperation[o.command] FROM read => THROUGH [0..pageCount) DO pageAddr^ _ 0; pageAddr _ pageAddr + PrincOps.wordsPerPage ENDLOOP; write => THROUGH [0..pageCount) DO d: CARDINAL = pageAddr^; pageAddr _ pageAddr + PrincOps.wordsPerPage; ENDLOOP; ENDCASE; }; IF ~csb.needRecalibrate THEN iocb.iocbState _ startSeek ELSE {iocb.iocbState _ startRecal; csb.needRecalibrate _ FALSE}; sectorsInPresentCyl _ sectorsPerCyl - (o.clientHeader.head*pagesPerTrack+o.clientHeader.sector); iocb.destCylinder _ IF o.pageCount <= sectorsInPresentCyl THEN o.clientHeader.cylinder ELSE o.clientHeader.cylinder + ((o.pageCount - sectorsInPresentCyl - 1)/sectorsPerCyl) + 1; o.deviceStatus.a _ 177777B; iocb.savedError _ inProgress; iocb.nextIOCB _ nil; iocb.label _ o.labelPtr^; iocb.unusedLabel _ 0; [] _ NextIOCBState[@iocb, IF csb.next = nil THEN csb.cylinder ELSE p[csb.tail].destCylinder]; IF csb.next = nil THEN { csb.next _ LOOPHOLE[iocbShort+entryOffset]; Output[startDiskCode, @iocb]} ELSE { p[csb.tail].nextIOCB _ LOOPHOLE[iocbShort + entryOffset]; IF p[csb.tail].iocbState = transferFinish THEN p[csb.tail].transferCommand.finishTransfer.nextIOCB _ p[csb.tail].nextIOCB; }; csb.tail _ iocbShort; }; CantGetHere: ERROR = CODE; Poll: PUBLIC PROC [o: OperationPtr] RETURNS [status: Status] = { OPEN iocb: LOOPHOLE[o, IOCBlongPtr]; originalDeviceStatus: WORD _ 0; IF ~LOOPHOLE[ o.deviceStatus.b _ LOOPHOLE[GetDDCHardwareBits[]], DDCHardwareBits].firmwareBusy AND o.deviceStatus.a = 0 THEN status _ hardwareError ELSE IF o.deviceStatus.a = 0 THEN RETURN[inProgress] ELSE { OPEN Basics; IF BITAND[originalDeviceStatus _ o.deviceStatus.a, errorMask]=0 THEN csb.cylinder _ iocb.presentCylinder; -- update cyl only if no error. status _ NextIOCBState[@iocb, csb.cylinder]; }; SELECT status FROM inProgress => IF o.deviceStatus.a#0 THEN ERROR CantGetHere ELSE Output[startDiskCode, @iocb]; goodCompletion => { IF (csb.next _ iocb.nextIOCB) # nil AND ~LOOPHOLE[originalDeviceStatus, DDCStatusBits].firmwareBusy THEN { WHILE GetDDCHardwareBits[].firmwareBusy DO pollRaceSpin _ pollRaceSpin+1 ENDLOOP; Output[ startDiskCode --, @p[LOOPHOLE[csb.next-entryOffset, IOCBshortPtr]]--]; }; o.labelPtr^ _ iocb.label; o.deviceStatus.a _ originalDeviceStatus; }; ENDCASE => { IF GetDDCHardwareBits[].writeFault THEN Output[resetDisk, NIL]; csb.next _ nil; totalErrors _ totalErrors + 1; IF status # labelCheck THEN o.labelPtr^ _ iocb.label; o.deviceStatus.a _ originalDeviceStatus; -- restore for diagnostics }; }; Recalibrate: PUBLIC PROC [device: DeviceHandle] = {csb.needRecalibrate _ TRUE}; Reset: PUBLIC PROC [device: DeviceHandle] = {Output[resetDisk, NIL]}; Start: PROC = { IF (driveIsSA1000 _ GetDDCHardwareBits[].driveIsSA1000) THEN { IF GetDDCKTest[].notSector THEN { pagesPerTrack _ sa1000PagesPerTrack; sectorsPerCyl _ sa1000TracksPerCylinder * sa1000PagesPerTrack; maxCylinderNum _ sa1000Cylinders - 1; maxHeadNum _ sa1000TracksPerCylinder - 1; resetDisk _ clearSA1000WriteFault; } ELSE { pagesPerTrack _ q2040PagesPerTrack; sectorsPerCyl _ q2040TracksPerCylinder * q2040PagesPerTrack; maxCylinderNum _ q2040Cylinders - 1; maxHeadNum _ q2040TracksPerCylinder - 1; resetDisk _ clearSA1000WriteFault; }; } ELSE { pagesPerTrack _ sa4000PagesPerTrack; sectorsPerCyl _ sa4000PagesPerTrack * sa4000TracksPerCylinder; maxCylinderNum _ sa4000Cylinders - 1; maxHeadNum _ sa4000TracksPerCylinder - 1; resetDisk _ clearSA4000WriteFault; }; }; GetDDCHardwareBits: PROC RETURNS [DDCHardwareBits] = INLINE { RETURN[LOOPHOLE[Basics.BITNOT[DLionInputOutput.Input[KStatus]]]]}; DDCKTest: TYPE = MACHINE DEPENDENT RECORD [ diskReadClk (0: 0.. 0): BOOL, diskReadData (0: 1.. 1): BOOL, diskOutputClk (0: 2.. 2): BOOL, diskWriteData (0: 3.. 3): BOOL, notSeekComplete (0: 4.. 4): BOOL, notDirectionIn (0: 5.. 5): BOOL, bHoriz (0: 6.. 6): BOOL, notReduceIW (0: 7.. 7): BOOL, ttlVideo (0: 8.. 8): BOOL, notSector (0: 9.. 9): BOOL, notDriveSelect (0:10..10): BOOL, notBVert (0:11..11): BOOL, notTtlvideo (0:12..12): BOOL, notStep (0:13..13): BOOL, notReadGate (0:14..14): BOOL, notWriteGate (0:15..15): BOOL]; GetDDCKTest: PROC RETURNS[DDCKTest] = INLINE{ KTest: CARDINAL = 6; RETURN[LOOPHOLE[Basics.BITNOT[DLionInputOutput.Input[KTest]]]]}; ImpossibleProcessingState: ERROR = CODE; NextIOCBState: PROC [iocb: IOCBlongPtr, curCyl: CARDINAL] RETURNS [iocbStatus: Status] = { OPEN o: iocb.operation; runLength: CARDINAL; -- number of pages to transfer physIOCBAddr: CARDINAL; -- low 16 bits of IOCB physical address IF o.deviceStatus.a = 177777B THEN iocbStatus _ IF (SELECT HeaderOperation[o.command] FROM verify => FALSE, IN [noop..read] => (LabelOperation[o.command] = write OR DataOperation[o.command] = write OR o.pageCount > 1), ENDCASE => --write-- (driveIsSA1000 AND (o.pageCount#pagesPerTrack OR o.clientHeader.sector#0)) ) THEN hardwareError ELSE inProgress ELSE { hwdStatus: DDCStatusBits = LOOPHOLE[o.deviceStatus.a]; hadError: BOOL = (Basics.BITAND[LOOPHOLE[hwdStatus], errorMask] # 0); IF ~hadError AND iocb.iocbState IN [transfer..labelRead] THEN { OPEN cH: o.clientHeader, l: iocb.label; IF HeaderOperation[o.command] = read THEN IF (cH.sector _ cH.sector + 1) = sectorsPerCyl THEN {cH.head _ cH.head + 1; cH.sector _ 0}; IF cH.head > maxHeadNum THEN {cH.head _ 0; cH.cylinder _ cH.cylinder + 1}; IF LabelOperation[o.command] # read THEN { kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@l.filePage]; kludge^ _ [pilot[pilotHi: [filePageHi: kludge.pilotHi.filePageHi], filePageLo: kludge.filePageLo]]; }; }; IF iocb.iocbState IN [transfer..transferFinish] THEN { runLength _ iocb.countThisStage-iocb.transferParm.sectorCount; o.pageCount _ o.pageCount - runLength; IF o.incrementDataPtr THEN o.dataPtr _ o.dataPtr + Words[runLength]; }; SELECT TRUE FROM -- first set the status for any of the disastrous faults iocb.savedError # inProgress => iocbStatus _ iocb.savedError; hwdStatus.memoryFault => { IF LabelOperation[o.command] # read THEN { kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage]; kludge.filePageLo _ kludge.filePageLo - 1; }; iocbStatus _ hardwareError; }; hwdStatus.driveNotReady => iocbStatus _ notReady; hwdStatus.overrun, hwdStatus.writeFault => iocbStatus _ hardwareError; ENDCASE => iocbStatus _ inProgress; SELECT hwdStatus.lastField FROM 0 => iocbStatus _ IF hadError THEN hardwareError ELSE inProgress; 1 => SELECT TRUE FROM hwdStatus.verifyError, hwdStatus.CRCError => iocbStatus _ wrongSector; iocbStatus = inProgress => iocbStatus _ hardwareError; ENDCASE; 2 => SELECT TRUE FROM hwdStatus.CRCError => iocbStatus _ labelError; -- bad label hwdStatus.verifyError => iocbStatus _ labelCheck; iocbStatus = inProgress => iocbStatus _ hardwareError; ENDCASE; 3 => SELECT TRUE FROM hwdStatus.verifyError, hwdStatus.CRCError => iocbStatus _ dataError ENDCASE; ENDCASE; }; statusCount[iocbStatus] _ statusCount[iocbStatus] + 1; IF iocbStatus=inProgress OR iocbStatus=labelCheck THEN DO stateCount[iocb.iocbState] _ stateCount[iocb.iocbState] + 1; SELECT iocb.iocbState FROM startRecal => { iocb.presentCylinder _ IF curCyl=0 THEN 1 ELSE 0; -- force movement iocb.iocbState _ recalSeek; EXIT; }; recalSeek => { UNTIL GetDDCHardwareBits[].seekComplete DO --wait for hardware-- recalSeekSpin _ recalSeekSpin+1 ENDLOOP; IF GetDDCHardwareBits[].track00 THEN { curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; } ELSE { -- not on cylinder 0 yet, try stepping in 16 cylinders csb.cylinder _ 0; -- just to make things consistent iocb.presentCylinder _ 1; iocb.iocbState _ recalStepIn; -- start process of stepping in EXIT; }; }; recalStepIn => SELECT TRUE FROM GetDDCHardwareBits[].track00 => { curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; }; iocb.presentCylinder < 16 => { iocb.presentCylinder _ iocb.presentCylinder + 1; iocb.iocbState _ recalStepIn; -- continue stepping in EXIT; }; ENDCASE => { csb.cylinder _ maxCylinderNum + 20; iocb.presentCylinder _ maxCylinderNum + 19; iocb.iocbState _ recalStepOut; -- start process of stepping out EXIT; }; recalStepOut => SELECT TRUE FROM GetDDCHardwareBits[].track00 => { curCyl _ iocb.presentCylinder _ csb.cylinder _ 0; iocb.iocbState _ startSeek; LOOP; }; iocb.presentCylinder > 0 => { iocb.presentCylinder _ iocb.presentCylinder - 1; iocb.iocbState _ recalStepOut; -- continue process of stepping out EXIT; }; ENDCASE => { iocbStatus _ hardwareError; iocb.iocbState _ recalStepOut; -- loop here if get called again EXIT; }; startSeek => IF (iocb.presentCylinder _ curCyl) = o.clientHeader.cylinder THEN {iocb.iocbState _ startTransfer; LOOP} -- on cyl, start transfer ELSE IF o.clientHeader.cylinder > maxCylinderNum THEN {iocbStatus _ seekTimeout; -- can't get there; clientError-- EXIT} ELSE { iocb.presentCylinder _ o.clientHeader.cylinder; iocb.iocbState _ doSeek; EXIT; }; doSeek, -- => {iocb.iocbState _ startTransfer; LOOP}; startTransfer => SELECT TRUE FROM driveIsSA1000 AND HeaderOperation[o.command]=write => {iocb.iocbState _ eraseSA1000Track; runLength _ 1; EXIT}; (o.pageCount=0) => {iocb.iocbState _ transferFinish; LOOP}; ENDCASE => {iocb.iocbState _ transfer; LOOP}; eraseSA1000Track => { kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage]; kludge.filePageLo _ kludge.filePageLo - 1; o.clientHeader.sector _ 0; iocb.iocbState _ transfer; LOOP; }; transfer => SELECT iocbStatus FROM inProgress => { headerOp: FieldOperation = HeaderOperation[o.command]; sectorsLeftInCyl: CARDINAL = sectorsPerCyl - (o.clientHeader.head*pagesPerTrack + o.clientHeader.sector); runLength _ MIN[o.pageCount, sectorsLeftInCyl]; IF headerOp # write AND LabelOperation[o.command]#read THEN { OPEN l: iocb.label; kludge: LONG POINTER TO FilePageRep = LOOPHOLE[@iocb.label.filePage]; IF kludge.filePageLo = 0 THEN { IF kludge.pilotHi.filePageHi = 0 THEN runLength _ 1} -- pg 0 ELSE runLength _ MIN[runLength, 65535 - kludge.filePageLo + 1]; IF driveIsSA1000 AND headerOp IN [noop..read] THEN runLength _ 1; }; IF o.pageCount = runLength THEN iocb.iocbState _ transferFinish ELSE { IF runLength = sectorsLeftInCyl THEN IF iocb.presentCylinder >= maxCylinderNum THEN iocbStatus _ seekTimeout -- should be client error ELSE iocb.presentCylinder _ curCyl + 1; iocb.iocbState _ transfer; }; EXIT; }; labelCheck => {iocbStatus _ inProgress; iocb.iocbState _ labelRead; EXIT}; ENDCASE => ImpossibleProcessingState; transferFinish => SELECT iocbStatus FROM inProgress => {iocbStatus _ goodCompletion; iocb.iocbState _ transferFinish; EXIT}; labelCheck => {iocbStatus _ inProgress; iocb.iocbState _ labelRead; EXIT}; ENDCASE => ImpossibleProcessingState; labelRead => { o.clientHeader _ o.diskHeader; -- restore Header saved when IOCB set up o.command _ iocb.savedIOCBCmd; -- restore command saved when IOCB set up IF ~MatchLabels[o.labelPtr, @iocb.label] OR iocb.unusedLabel # 0 THEN { hardLabelChecks _ hardLabelChecks+1; iocbStatus _ labelCheck; EXIT} ELSE IF o.labelPtr.dontCare = iocb.label.dontCare THEN { iocbStatus _ labelError; EXIT } ELSE { softLabelChecks _ softLabelChecks+1; iocb.iocbState _ startTransfer; LOOP}; }; ENDCASE => ImpossibleProcessingState; ENDLOOP; { OPEN lp: LOOPHOLE[iocb, Basics.LongNumber]; wPP: CARDINAL = PrincOps.wordsPerPage; physIOCBAddr _ (LOOPHOLE[DLionInputOutput.GetRealPage[lp.lowbits/wPP], CARDINAL]*wPP) + (lp.lowbits MOD wPP); }; IF iocbStatus # inProgress THEN { OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; sC _ seekCmdBlock[dummyIOCB]; -- set the dummy IOCB commands fS.nextIOCB _ iocb.nextIOCB; -- microcode chains to next IOCB fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; } ELSE SELECT iocb.iocbState FROM IN [recalStepIn..recalStepOut] => { OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; sC _ seekCmdBlock[step]; -- set the Step IOCB commands IF iocb.iocbState = recalStepIn THEN { sC.controlWd1 _ Basics.BITOR[sC.controlWd1, directionInBit]; sC.controlWd2 _ Basics.BITOR[sC.controlWd2, directionInBit]; sC.waitCmd _ Basics.BITOR[sC.waitCmd, directionInBit]; }; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; fS.nextIOCB _ nil; -- stop after this IOCB completes fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; }; recalSeek, doSeek => { OPEN sC: iocb.seekCommand, fS: iocb.seekCommand.finishSeek; sC _ seekCmdBlock[seek]; -- set the Seek IOCB commands IF iocb.presentCylinder > curCyl THEN { sC.controlWd1 _ Basics.BITOR[sC.controlWd1, directionInBit]; sC.controlWd2 _ Basics.BITOR[sC.controlWd2, directionInBit]; sC.waitCmd _ Basics.BITOR[sC.waitCmd, directionInBit]; sC.negDistance _ curCyl - iocb.presentCylinder; } ELSE sC.negDistance _ iocb.presentCylinder - curCyl; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; fS.nextIOCB _ nil; -- stop after this IOCB completes fS.transferMask _ csb.transferMask; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; fS.statusAddr _ fS.statusAddr + physIOCBAddr; }; eraseSA1000Track, transfer, transferFinish => { OPEN tP: iocb.transferParm; headerOp: FieldOperation = HeaderOperation[o.command]; tP.headerOp _ headerOpBlock[headerOp]; tP.labelOp _ labelOpBlock[LabelOperation[o.command]]; tP.dataOp _ dataOpBlock[DataOperation[o.command]]; tP.dataOp.dataPgNum _ IF DataOperation[o.command] = noop THEN PrincOpsUtils.PageNumberForAddress[DLionInputOutput.IOPage] ELSE (IF o.incrementDataPtr THEN 100000B ELSE 0) + PrincOpsUtils.PageNumberForAddress[o.dataPtr]; { OPEN m: tP.miscCmds, cH: o.clientHeader; m _ miscCmdBlock[driveIsSA1000]; -- set findSectMk and Stop commands m.freezeCmd _ m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4 m.findSectMkCmd _ m.findSectMkCmd + cH.head*8*256; }; iocb.countThisStage _ tP.sectorCount _ runLength; IF headerOp IN [noop..write] THEN { OPEN sC: iocb.seekCommand, cH: o.clientHeader; IF driveIsSA1000 THEN { sC _ seekCmdBlock[findSA1000Sector0]; sC.controlWd2 _ sC.controlWd2 + physIOCBAddr; SELECT headerOp FROM IN [noop..read] => { OPEN Basics; tP.sectorCount _ cH.sector + 1; tP.dataOp.dataPgNum _ BITAND[tP.dataOp.dataPgNum, 77777B]; }; ENDCASE => --write, can't be verify here { IF iocb.iocbState = eraseSA1000Track THEN tP.dataOp.dataCnt _ (sa1000PagesPerTrack+4) * PrincOps.wordsPerPage }; } ELSE { sC _ seekCmdBlock[findSA4000Sector0]; sC.negDistance _ SA4000SectorDistance[cH.sector]; sC.negDistanceAddr _ sC.negDistanceAddr + physIOCBAddr; sC.sendCtlAddr _ sC.sendCtlAddr + physIOCBAddr; sC.initRegsParm _ sC.initRegsParm + physIOCBAddr; }; tP.failCount _ 1; -- quit immediately on Header Field errors } ELSE -- headerOp=verify { OPEN sC: iocb.seekCommand; sC _ seekCmdBlock[plainTransfer]; sC.controlWd1 _ sC.controlWd1 + physIOCBAddr; tP.failCount _ IF driveIsSA1000 THEN 3*2*sa1000PagesPerTrack --(3 field/sector) ELSE 2*sa4000PagesPerTrack --(1 field/sector) }; { OPEN tC: iocb.transferCommand; IF iocb.presentCylinder = curCyl + 1 THEN tC _ transferCmdBlock[doStep] ELSE { tC _ transferCmdBlock[noStep]; IF iocb.iocbState = transferFinish THEN tC.finishTransfer.nextIOCB _ iocb.nextIOCB; tC.stepCmdWd1 _ tC.stepCmdWd1 + physIOCBAddr; }; tC.parmBlockAddr _ tC.parmBlockAddr + physIOCBAddr; tC.finishAddr _ tC.finishAddr + physIOCBAddr; tC.finishTransfer.statusAddr _ tC.finishTransfer.statusAddr + physIOCBAddr; tC.finishTransfer.transferMask _ csb.transferMask; }; tP.headerOp.headerAddr _ tP.headerOp.headerAddr + physIOCBAddr; tP.labelOp.labelAddr _ tP.labelOp.labelAddr + physIOCBAddr; }; -- of setting up a normal transfer IOCB labelRead => { OPEN tP: iocb.transferParm; o.labelPtr^ _ iocb.label; -- save expected label iocb.savedIOCBCmd _ o.command; -- save failed operation o.command _ vr; -- read the offending label o.diskHeader _ o.clientHeader; -- save correct header iocb.presentCylinder _ curCyl; -- we will be on this cylinder tP.sectorCount _ 1; -- read one label only tP.failCount _ IF driveIsSA1000 THEN 2*3*sa1000PagesPerTrack -- (3 fields/sector) ELSE 2*sa4000PagesPerTrack; -- (1 field /sector) tP.headerOp _ headerOpBlock[verify]; -- verify header tP.labelOp _ labelOpBlock[read]; -- then read the label tP.dataOp _ dataOpBlock[noop]; -- forget the data tP.dataOp.dataPgNum _ PrincOpsUtils.PageNumberForAddress[DLionInputOutput.IOPage]; { OPEN m: tP.miscCmds, cH: o.clientHeader; m _ miscCmdBlock[driveIsSA1000]; -- set findSectMk, Stop m.freezeCmd _ m.freezeCmd + cH.head*8*256; -- "or" head into bits 1..4 m.findSectMkCmd _ m.findSectMkCmd + cH.head*8*256; }; iocb.seekCommand _ seekCmdBlock[plainTransfer]; -- just looking iocb.seekCommand.controlWd1 _ iocb.seekCommand.controlWd1 + physIOCBAddr; { OPEN tC: iocb.transferCommand; tC _ transferCmdBlock[noStep]; -- no poststep tC.finishTransfer.nextIOCB _ nil; -- stop after label read tC.stepCmdWd1 _ tC.stepCmdWd1 + physIOCBAddr; tC.parmBlockAddr _ tC.parmBlockAddr + physIOCBAddr; tC.finishAddr _ tC.finishAddr + physIOCBAddr; tC.finishTransfer.statusAddr _ tC.finishTransfer.statusAddr + physIOCBAddr; tC.finishTransfer.transferMask _ csb.transferMask; }; tP.headerOp.headerAddr _ tP.headerOp.headerAddr + physIOCBAddr; tP.labelOp.labelAddr _ tP.labelOp.labelAddr + physIOCBAddr; }; ENDCASE; o.deviceStatus.a _ 0; -- mark iocb as not having been seen by uCode iocb.savedError _ iocbStatus; -- this will be used in no-op IOCB's RETURN[iocbStatus]; }; MatchLabels: PROC[p1, p2: LONG POINTER TO DiskFace.Label] RETURNS[BOOL] = INLINE { MatchableLabel: TYPE = MACHINE DEPENDENT RECORD [ a, b, c, d, dontCare: RECORD [UNSPECIFIED, UNSPECIFIED] ]; { OPEN m1: LOOPHOLE[p1^, MatchableLabel], m2: LOOPHOLE[p2^, MatchableLabel]; RETURN[m1.a=m2.a AND m1.b=m2.b AND m1.c=m2.c AND m1.d=m2.d] }; }; Output: PROC [cmd: UNSPECIFIED, i: IOCBlongPtr _ NIL] = INLINE { DLionInputOutput.Output[cmd, KCtl]}; IOCBCrossesPage: ERROR = CODE; PageCrossCheck: PROC [i: IOCBshortPtr] = INLINE { IF LOOPHOLE[i, CARDINAL] MOD PrincOps.wordsPerPage >= PrincOps.wordsPerPage - SIZE[IOCB] THEN ERROR IOCBCrossesPage; }; Words: PROC [sectors: CARDINAL] RETURNS [LONG CARDINAL] = INLINE { RETURN[Basics.LongMult[sectors, PrincOps.wordsPerPage]]}; stateCount: ARRAY DDC.ProcessingState OF INT _ ALL[0]; statusCount: ARRAY SA4000Face.Status OF INT _ ALL[0]; hardLabelChecks: INT _ 0; softLabelChecks: INT _ 0; Start[]; }.... LOG Time: June 13, 1980 3:40 PM, By: DDavies Action: start change to Dandelion Head Time: June 16, 1980 5:11 PM, By: Davies Action: end first pass at Dandelion Head Time: June 17, 1980 5:12 PM, By: Davies Action: prepare for compilation Time: June 23, 1980 3:51 PM, By: Forrest Action: Fix bug, add IOCB check. Reformat/maulOver. Time: July 16, 1980 1:25 PM, By: Forrest Action: Take out code supporting initial debugging, add ability to format parts of SA4000 tracks. Time: September 9, 1980 2:33 PM, By: Forrest Action: Reformat (again), work on Label check bug. Time: September 9, 1980 2:33 PM, By: Sandman Action: AddressFault bug. Time: January 22, 1981 3:10 PM, By: Gobbel Action: Don't reset cylinder when first building IOCB. Reformat. Time: February 11, 1981 1:53 PM, By: Forrest Action: Add code (state) to format SA1000 track. Time: February 14, 1981 2:25 PM, By: Gobbel Action: Fix bug in NextIOCBState. Time: March 31, 1981 9:44 AM, By: Forrest Action: change the pre adjustment or pageCOunt and DataPointer in NextIOCBState to adjustment after the IOCB is cooked. Remark: July 22, 1983 9:23 am: Sturgis: add test for quantum disk, assume only diff from SA1000 is shape of disk. (required adding q2040 constants, and modifying Start.) Added GetDDCKTest. Time: January 20, 1984 4:42 pm, By: Andrew Birrell Action: Convert to Cedar 5.1. Time: January 20, 1984 4:42 pm, By: Bob Hagmann Action: add GetTrueDeviceAttributes ;ΰSA4000HeadDLion.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Forrest on: February 14, 1981 2:25 PM Sturgis: July 22, 1983 9:50 am Bob Hagmann: May 9, 1984 5:49:45 pm PDT Willie-Sue, January 31, 1985 2:35:39 pm PST Russ Atkinson (RRA) February 19, 1985 3:52:50 pm PST Carl Hauser, November 15, 1985 10:26:58 am PST Things to do/dubious features: 1. If more than one drive, or if different kinds of Shugarts are used, Get attributes (at least) has to be fixed. 2 Some of the error flags returned will be different than those returned by the D0. For example, the Dandelion controller cannot simultaneously read and verify header fields. As a consequence, whenever an error is encountered during a header operation, the "wrongSector" status is returned. It is also not possible to issue any sort of timeout flag since the Head has no clock. 3. Runs of Header noops/reads are supported, they are done a page-at-a-time on the SA1000; more work probably has to be done here and for rr and rrr. 4. The calculation of the low part of the IOCB's read address depends upon knowledge that IOCB's live in the first 64K, as well as the page number being in the rightmost part of the flag word. 5. Rename (within mesa) some of the fields within the IOCB. For example, the incrementDataPtr bit is the high order bit of the DataPageNumber, and should be a seperate boolean rather than 100000B. Similarly, the head bits are now or'ed into freezeCmd, but should be seperatly defined. 6. Consider calculating the low half of the physical IOCB address once in initiate (and stashing it in the IOCB) rather then each time through the Next IOCB loop. 7. Does deviceCleanup do the right thing. 8. what should incrementDataPtr on ?,?,nop do?? 9. get rid of spin counters someday. 10. The increment field of the IOCB is the bit 0 of the page number. this conflicts with the fat memory boards and with large virtual address spaces and should be moved. TYPE DEFINITIONS Controller status block, only one per DLion. Only state.next is known by microcode. Microcode implements Pilot file page number layout in labels, but Cedar Face expects INT's CONSTANTS GLOBAL VARIABLES Counters to help track down apparent funnies in head Not used in current Pilot CurrentOperation: PUBLIC PROC [device: DeviceHandle] RETURNS [OperationPtr] = { RETURN[LOOPHOLE[@p[csb.next]]] }; THIS MUST BE MODIFIED TO HANDLE FIXED HEADS, SINGLE-PLATTER DRIVES AND OTHER MODELS OF SHUGART SA100X AND SA400X AND SA410X DRIVES ASSUMES EXACTLY ONE DRIVE IS CONNECTED TO THE DANDELION don't update cylinder I'm not sure what the right thing to do here is. Always recal seems safe. This procedure initiates an operation. The client passes an IOCB with most of the parameters filled in. We fill in the parameters that are unique to this implementation of the Head and microcode. Then we chain the IOCB onto the CSB an wake up the microcode. between start of run and end of current cylinder touch (dirty if write) each page; Schedule a recalibrate?? Set special value into device status to indicate new IOCB. Set SavedError so NextIOCBState won't find initial error. use cylinder that the disk will be at when we start this operation. Arrange for execution. Disk busy; queue IOCB. A race is possible and is fixed in Poll. microcode should finish so chain new one on This procedure checks the status of an operation and returns the status to the client Present IOCB not yet complete. If the microcode thinks it is stopped, restart it; this will be the case when a disk request has to be done in several operations. Fall through and return. all the iocb fields have been corrected by NextIOCBState and the microcode should have already begun the next IOCB in the chain, if any, so set the CSB's next pointer, check to be sure that the microcode will in fact do that IOCB and return with this status we lost the race. Microcode should be stopped, but check. copy NEXT[lastReadLabel], including boot chain links for debugging purposes (was zeroed) Trouble. Get things moving again for now, stop all processing on error IF (csb.next _ iocb.nextIOCB) # nil THEN BEGIN next: IOCBlongPtr = @p[LOOPHOLE[csb.next - entryOffset, IOCBshortPtr]]; ----find next IOCB from chain. ----Recalculate the next IOCB considering the cylinder may be screwed up. ----BE CONSERVATIVE FOR NOW. IF TRUE OR next.iocbState = recalSeek THEN next.iocbState _ startRecal ELSE next.iocbState _ startSeek; next.operation.deviceStatus.a _ 177777B; ++ pretend this is a new IOCB next.savedError _ inProgress; [] _ NextIOCBState[next, csb.cylinder]; ++ think on it... Output[startDiskCode, next]; ++ start it. END; copy in NEXT[lastReadLabel]. This includes the boot chain link read in the last succussful operation,or initially supplied. This operation was done if status=labelCheck, and is not IOCB contains the label read from the disk, usefull for debugging this test copied from Trinity version sa1000 quantum assumes SA4008 in constants defined above DLion boards return status with negatvie logic following added in analogy to trinity version Sturgis: July 22, 1983 9:41 am DDCKTest copied from [idun]>Faces>Private>ddc.mesa specification of KTest Register. Note this has to be inverted after being obtained from DlionInput.Input[KTest] since negative drivers are used. |rd dsk|rd dsk|dsk out|dsk wrt|~seek|~dir| b |~IW |TTL| ~ |~drv|~b |~ttl| ~ |~rd |~wrt| |clock | data | clock | data |cmplt| in |horiz|rduc|vid|sctr|slct|vert|vidi|step|gate|gate| | 8 4 2 1 | 8 4 2 1 | 8 4 2 1 | 8 4 2 1 | | 1 | 4 2 1 | 4 2 1 | 4 2 1 | 4 2 1 | 4 2 1 | |rd dsk|rd dsk|dsk out|dsk wrt|~seek|~dir| b |~IW |TTL| ~ |~drv|~b |~ttl| ~ |~rd |~wrt| |clock | data | clock | data |cmplt| in |horiz|rduc|vid|sctr|slct|vert|vidi|step|gate|gate| Prepare the next stage of processing an IOCB. If nothing to do prepare a null IOCB. Never submits an IOCB, puts an IOCB on the queue, or starts the Disk uCode. Called only by Initiate on on a new request (with deviceStatus=-1) or Poll if the microcode has finished with the IOCB (deviceStatus=0). First time for this IOCB. Check for illegal operation. Check header read/noop with a run of multiple pages, label write or data write. something was done to IOCB. decoded status bits from the controller set if last IOCB had an error fix up header, label if there no error. first fix Headers; if header read, simulate advance of client header. THIS WOULD BE WRONG FOR A RUN OF READ HEADERs fix Labels; if label read, no updating occured Take care to clear Pilot's flags in the pageNumber field! Fix up the dataPtr and pageCount. SectorCount will be non-zero if there was an error. The intended count is countThisStage. There are three main areas here, Recalibration, Seeking and DataTransfer. The recalibration algorithm proceeds as follows: 1. seek out by the current cylinder number, this should get us directly to cylinder zero. 2. If the Track00 signal is set, set the StartSeek and try to set the new state from there. If not on track 0, step in 16 tracks one at a time looking for track 0. This is done in case we overshot. 3. If track 00 is found at any step, set StartSeek and start a data transfer, if any. If all 16 steps are exhausted, step out maxCylinderNum+20 cylinders , one at a time. 4. If the Track00 is found at any step, stop and preceed as above. If not, post "SeekError" and return. If a Seek operation is needed to put the heads over the desired track, The next state _ "DoSeek", the distance and direction are computed and control is passed to the third section of NextIOCBState to build the Seek IOCB. The destination cylinder is also posted in the CSB. If a data transfer operation is needed, we first determine whether the first sector is to be found by verifying headers or by counting from the index mark. It is assumed that if the second method is to be employed (the Header Operation = read or noop), exactly one sector will be read and there will be no write operations involved. Next the maximum length of the transfer is computed. A run of pages may be broken before its natural finish for a number of reasons. First, page zero of any file is transferred independently of all other pages. This allows special treatment of its flags. Runs of pages are broken at 64K page boundries so the microcode need never increment two words of page labels. They are also broken at cylinder boundries so a seek may be inserted. The distance in pages to the closest boundry determines the length of the transfer. Finally, there may have been an error on the previous transfer. If so, it must be decoded. If it was a label check, we do not return to the client. Instead, a new IOCB is formulated to read the label that failed. The first six words of that label are compared to the first 6 of the clientLabel. If these do not match, there was a real error and the IOCB must be set to simulate an error on the original label verify (this involves setting the former pageCount and dataPtr fields). If the six words match, the last section of the label read is substituted into the clientLabel and the operation that included the label verify is restarted on the offending sector. If any other type of error occured, its identity is simply returned. Error decode; used to decide the next state. this fails if the memory error occured WITHIN the iocb lastField is 0=>seek, 1=>header, 2=>label, 3=>data. shouldn't have stopped if no error CRC ok, verify failed; go try read/label fixup. shouldn't have stopped if there was no error If fall through, leave value set in first select Next State decision Loop. Note this may take several iterations. For example, the starting state could be "StartSeek" while the cylinder specified is the current cylinder. In this case the current state is set to "StartTransfer" and the code enters the next state selection procedure again. have just done seek to where cylinder 0 should be tried stepping in one (more) cylinder in search of cylinder 0 made it to cylinder 0 keep stepping-in in case we overshot cylinder 0 have stepped in; cylinder 0 can't be that way so step out assume we are all the way in on the disk, past the last cyl tried stepping out one (more) cylinder in search of cylinder 0 made it to cylinder 0 keep stepping out searching for cylinder 0 stepped in as far as possible, cyl 0 not be found => quit either just started or just finished recalibrating. See if a Seek is needed ( on destination cylinder now?). If so, set up state for Seek, else set state to startTransfer and go investigate need for transfer do the seek last run went ok stop run at 0/1 boundaries, cyl bounds, 65K page bounds test for overrun, set poststep we have read a disk label to see if it matches the client's label. Note one cannot get a label check on a label read operation since the verifyError bit is masked off. If there was a CRC error, there would have been a labelError and we wouldn't be here now. Hence, the fact that this code is being executed implies the label read operation had no errors. iocbStatus already _ inProgress temp patch: complain about spurious label verify errors: build the IOCB from the information given above; following block depends upon iocb being in first 64K, and layout of flag words with flags in rightmost part physIOCBAddr _ Basics.LowHalf[DLionInputOutput.GetRealAddress[iocb]]; build a null IOCB. This will do nothing if run but cause and interrupt to the calling mesa process. This is needed if an IOCB specifying no action or an illegal action is given. When the iocb is finished or the next action specified is illegal, this type of null IOCB is also built. the present cylinder is already set the destination cylinder is already set as is the saved IOCB state, though this does not apply here the next IOCB field is also already set don't bother setting the transfer parameters, they won't be used build step iocb present cylinder is already set to where transfer starts destination cylinder is already set to where transfer ends transfer parameters not used or in the DirectionIn bit set in the wait command so it is set up before first step pulse present cylinder is already set to where transfer starts destination cylinder is already set to where transfer ends transfer parameters not used OR in DirectionInBit set in wait command so it is set up before first step pulse negative of distance to move heads in cylinders out wants negative of distance to move heads in cylinders build transfer IOCB, run length in runLength (adjust if Header Read or Noop), command in o.command, add post-step of heads if iocb.present cylinder=currentCylinder+1, find sector 0 if header op # verify. Pre-increment the dataPtr and pageCount for run of pages if necessary. the present cylinder is already set the destination cylinder is already set as is the saved IOCB state, it doesn't apply here the next IOCB field is also already set fill in the transfer parameter block does this transfer involve a verify of the Header fields or should the index mark be used to find the first sector? The vast majority of operations are done using header verifies to find the proper sector. The header is read or Nooped only during scavenging and diagnostic operations. It is written only when an entire track is being formatted. this is not a verify so insert the commands used to find sector 0 via the index mark the SA1000 drive is connected insert phys address of start of transfer block For Trinity: if runLength=1 isn't this AND unnecessary???? or is it necessary because a header nop/read is really a bunch of reads on the SA1000 runLength _ (already) 1; to erase a SA1000Track, write one LARGE sector find sector on SA4000 by counting after the index mark Fill in commands to skip over most of the seekCommandBlock; the transfer starts as soon as SeekComplete is detected. the 2 is for a cheap, quick retry set the transfer command block. It either specifies a post-transfer step or not, depending on whether iocb.presentCylinder=currentCylinder+1 have the microcode do a post step before returning from the IOCB no post step after transfer continue with next IOCB in the chain; the default value for nextIOCB is 0, which will stop the microcode after this stage, so unless set otherwise above, the microcode will stop. Note if this is the last IOCB in the chain, iocb.nextIOCB would have been equal to "nil", which was declared to be zero, so the microcode will stop in that case too. set up iocb for read of one label dest cylinder already set after the operation completes. This could have been set to curCyl+1 in the failing transfer operation. saved IOCB state was set when the next state was determined the next IOCB field is set fill in the transfer parameter block the 2 is for a cheap, quick retry Matches labels ignoring bootChainLink field. Code stolen from PilotDisk interface Temporary statistics gathering (ADB 1/30/84) Carl Hauser, November 14, 1985 10:35:36 am PST fixed bug causing free pages with bad labels (wrong FilePage) to be written changes to: Initiate the "Fix up label file page number representation" code must be idempotent: added "IF kludge.pilotHi.filePageHi = 0" test. Κχ˜codešœ™Kšœ Οmœ1™Kšžœ ˜Kšžœžœžœ'˜E—K˜Kšœ™Kšœ,™,Kšœ&™&Kš œžœžœžœžœ˜š žœžœžœž œžœ˜&Kšœžœ˜#Kšœžœžœžœ˜=Kšœ)Οc˜GKšœžœ Ÿ˜?KšœžœŸ ˜F—Kš œžœžœžœžœžœ˜@K˜KšœZ™Zšœ žœžœž œžœžœžœž˜Jšœ˜Kšœ žœ˜—˜ Kšœžœ˜šœ žœ˜KšœŸ8˜VKšœŸ˜+KšœžœžœŸ ˜DKšœžœžœŸ ˜DKšœžœž˜!K˜—K˜—˜Kšœ žœ˜Kšœ ž˜K˜—Kšž˜Kšœ˜—K˜Kšœ ™ Kšœ žœžœ˜K˜Kšœžœ Ÿ)˜HKšœžœ Ÿ+˜JKšœžœ Ÿ!˜@K˜Kšœžœ Ÿ#˜KKšœžœ˜'KšœžœŸ˜8Kšœžœ˜(K˜Kšœžœ Ÿ#˜KKšœžœ˜'KšœžœŸ˜8Kšœžœ˜(K˜Kšœžœ˜"KšœžœŸ˜6Kšœžœ˜'K˜Kšœ!žœŸ˜AKšœžœ Ÿ*˜LKšœžœŸ"˜DKšœžœŸ!˜CKšœžœ Ÿ&˜HK˜Kšœ™Kšœžœžœ Ÿ˜DKšœžœžœ ˜:Kš œžœžœžœžœ˜3K˜Kšœžœ˜Kšœžœ˜Kšœžœ˜KšœžœŸ˜/KšœžœŸ ˜+Kšœžœ Ÿ˜9K˜Kšœ4™4KšœžœžœŸ ˜RKšœžœžœ˜.Kšœžœžœ˜.Kšœžœžœ˜,K˜K˜Kšœ™Kšœ4™4Kšœ<™—K˜š  œžœžœžœ7žœ˜‡KšœT˜TKšœ˜K˜—Kšœ7™7š  œžœžœžœ˜Mšžœžœžœžœ˜RK˜——š  œžœžœžœ"˜BK˜K˜K˜Kšœžœ˜Kšœ™KšœŸ˜*Kšœ˜K˜—š œžœžœ˜"Kšžœ˜K˜ Kšœžœ˜ šž˜K˜šžœž˜˜Kšœžœ#žœžœ˜B—˜ Kšœ0™0Kšœ™Kšœ2žœ˜E—Kšž˜—Kšž˜—Kšœ˜K˜—KšœJ™JKšœI™IKšœJ™JKšœ!™!š œžœžœ˜+Kšžœžœ˜$Kšœžœ˜Ašœžœ˜Kšœ0™0K˜—šœ˜Kšœ!™!Kšœ žœžœ ˜$Kš œ žœžœžœ žœ˜DK˜Kšœ˜K˜šžœž˜$šœžœž˜!Kšœ;žœ˜C—šœ žœž˜"Kšœžœ:˜EKšžœ˜—Kšžœ˜—Kšœ˜—Kšœ™šžœ˜Kšžœ˜Kšžœ5žœ˜@—˜K˜J—˜šžœ#˜%Kšžœ˜KšžœW˜[——Kšœ:™:Kšœ9™9K˜9K˜DKšœC™CKšœžœžœžœ˜]Kšœ™šžœ˜šžœ˜Kšœ žœ˜+Kšœ˜—šžœ˜Kšœ@™@Kšœžœ˜9šžœ(ž˜.Kšœ+™+˜5K˜——Kšœ˜——K˜Kšœ˜K˜—Kšœ žœžœ˜š œžœžœžœ˜@KšœU™UKšžœžœ˜$Kšœžœ˜K˜šžœžœ˜ Kšœžœ5˜PKšžœžœ˜4—Kšžœžœžœžœ ˜4šžœ˜Kšžœ˜ šžœžœ7ž˜DKšœ%Ÿ˜D—K˜,Kšœ˜—šžœž˜˜ Kšœ=™=KšœB™BKšœ;™;Kšžœžœžœ ˜,Kšžœ˜"—šœ˜Kšœ@™@KšœB™BKšœ=™=Kšœ?™?šžœ"žœžœ3žœ˜jKšœ:™:šžœ"˜'Kšžœžœ˜)—˜K˜ KšŸ6œ˜8—Kšœ˜—˜Kšœ4™4—˜(Kšœ#™#—Kšœ˜—šžœ˜ Kšœ!™!Kšžœ!žœžœ˜?—šœ%™%K˜—Kšœ(™(Kšœ™Kšœ™Kšœ3™3Kšœ™KšœI™IKšœ™KšœF™FKšœ ™ KšœF™FKšœ™Kšœ:™:Kšœ5™5šœ™K˜Kšœ?™?Kšœ<™K˜&Kšžœžœ*˜DKšœ˜K˜———KšœI™Išœ0™0KšœY™YKšœΗ™ΗKšœ«™«KšœΨ ™Ψ Kšœα™α——˜Kšœ-™-šžœžœžœŸ8˜IK˜=˜Kšœ6™6šžœ"žœ˜*Kš œžœžœžœžœ˜EKšœ*˜*Kšœ˜—K˜Kšœ˜—K˜1˜*K˜—Kšžœ˜#K˜—Kšœ3™3šžœž˜Kšœžœ žœžœ ˜Ašœžœžœž˜˜,K˜—˜6Kšœ"™"—Kšžœ˜—šœžœžœž˜Kšœ/Ÿ ˜;˜1Kšœ0™0—˜6Kšœ,™,—Kšžœ˜—šœžœžœž˜˜,K˜—Kšžœ˜—Kšžœ˜Kšœ0™0—Kšœ˜K˜Kšœ₯™₯—˜Kšœ6˜6šžœžœž˜6šžœ˜Kšœ<˜<šžœž˜šœ˜Kšœžœ žœžœŸ˜CK˜Kšžœ˜Kšœ˜—šœ˜Kšœ1™1šžœ#žœŸ˜AKšœ žœ˜(—šžœž˜$Kšœ˜K˜1K˜Kšžœ˜Kšœ˜—šž˜KšœŸ6˜9KšœŸ!˜?K˜KšœŸ˜=Kšžœ˜Kšœ˜—Kšœ˜—˜Kšœ=™=šžœžœž˜šœ!˜!Kšœ™K˜1K˜Kšžœ˜Kšœ˜—šœ˜Kšœ/™/K˜0KšœŸ˜5Kšžœ˜Kšœ˜—šžœ˜ Kšœ9™9K˜#K˜+Kšœ;™;KšœŸ ˜?Kšžœ˜Kšœ˜———˜Kšœ>™>šžœžœž˜šœ!˜!Kšœ™K˜1K˜Kšžœ˜Kšœ˜—šœ˜Kšœ*™*K˜0KšœŸ#˜BKšžœ˜Kšœ˜—šžœ˜ Kšœ9™9K˜KšœŸ ˜?Kšžœ˜Kšœ˜K˜———˜ Kšœ=™=Kšœ>™>Kšœ6™6Kšœ™šžœ;ž˜AKšœ!žœŸ˜@—šžœžœ*ž˜5KšœŸ!œžœ˜B—šžœ˜Kšœ ™ K˜/K˜Kšžœ˜Kšœ˜K˜——KšœŸ-˜5˜šžœžœž˜šœžœ$˜5Kšœ3žœ˜9—Kšœ5žœ˜;Kšžœ žœ˜-K˜——šœ˜Kš œžœžœžœžœ˜EKšœ*˜*K˜K˜Kšžœ˜Kšœ˜K˜—šœ žœ ž˜"šœ˜Kšœ™K˜6šœžœ˜K˜2K˜—Kšœ žœ ˜/šžœžœ žœ˜>Kšžœ˜Kšœ7™7Kš œžœžœžœžœ˜Ešžœžœ˜KšžœžœŸ˜<—Kšžœ žœ+˜?Kšžœžœ žœžœ˜AKšœ˜—šžœ˜Kšžœ ˜$šžœ˜šžœž˜$Kšœ™šžœ(ž˜.KšœŸ˜2—Kšžœ#˜'—K˜Kšœ˜——Kšžœ˜Kšœ˜—˜ Kšœ6žœ˜<—Kšžœ˜%—K˜šœžœ ž˜(KšœMžœ˜SKšœDžœ˜JKšžœ˜%K˜—šœ˜KšœB™BKšœ‘™‘Kšœ™KšœŸ(˜GKšœŸ)˜Hšžœ'žœ˜@šžœ˜Kšœ$˜$Kšœ˜Kšžœ˜—šž˜Kšœ8™8Kšžœ*˜,šžœ˜Kšœ˜Kšžœ˜—šžœ˜Kšœ$˜$Kšœ˜Kšžœ˜———Kšœ˜—Kšžœ˜%—Kšžœ˜——˜šœ˜Kšœœ™œKšœE™EKšžœžœ˜+Kšœžœ˜&Kš œžœžœ/žœžœ˜mKšœ˜—K˜šžœžœ˜!Kšžœ7˜;šœ™Kšœ#™#Kšœ'™'Kšœ;™;Kšœ'™'Kšœ@™@—KšœŸ˜K˜#K˜7K˜-Kšœ˜—šžœžœž˜šžœ!˜#Kšžœ7˜;Kšœ™Kšœ8™8Kšœ:™:Kšœ™KšœŸ˜6šžœžœ˜&Kšœ™Kšœžœ ˜™>Kšœ ™ K˜!K˜-˜KšžœžœŸ˜AKšžœ+Ÿ˜AKšœ!™!—Kšœ˜—šœ˜KšœŽ™ŽKšžœ˜šžœ#ž˜)K˜KšœH™H—šžœ˜Kšœ™K˜šžœ!ž˜'KšœΩ™ΩK˜+—K˜-Kšœ˜—K˜3K˜-K˜KK˜2Kšœ˜K˜?K˜=KšœŸ'˜*—K˜šœ˜Kšžœ˜Kšœ!™!Kšœ Ÿ˜6Kšœ Ÿ˜8Kšœ Ÿ˜;Kšœ$Ÿ˜;Kšœ™šœ Ÿ˜?Kšœ;™;Kšœ+™+—Kšœ;™;Kšœ™Kšœ$™$KšœŸ˜*˜KšžœžœŸ˜BKšžœ*Ÿ˜BKšœ!™!—Kšœ%Ÿ˜5Kšœ%Ÿ˜;Kšœ%Ÿ˜7˜K˜<—šœžœ$˜*Kšœ+Ÿ˜BKšœ+Ÿ˜FK˜2Kšœ˜—Kšœ0Ÿ˜?˜K˜+—šœžœ˜ Kšœ#Ÿ˜1Kšœ#Ÿ˜;K˜0K˜3K˜0˜K˜,—K˜2Kšœ˜—K˜?K˜=Kšœ˜—Kšžœ˜K˜———KšœŸ-˜KKšœŸ$˜BKšžœ ˜Kšœ˜K˜—š  œžœ žœžœžœžœžœžœ˜RKšœR™Rš œžœžœž œžœ˜1Kšœžœž œž œ˜:—šœ˜Kšžœžœžœ˜JKšžœ žœ žœ žœ ˜;Kšœ˜—Kšœ˜—K˜š  œžœž œžœžœ˜@K˜$K˜—Kšœžœžœ˜š œžœžœ˜1šžœžœžœžœ˜5Kš œžœžœžœžœ˜>—Kšœ˜K˜—š œžœ žœžœžœžœžœ˜BKšžœ3˜9—K˜K˜šœ,™,Kš œ žœžœžœžœžœ˜6Kš œ žœžœžœžœ˜5Kšœžœ˜Kšœžœ˜K˜—K˜K˜Kšœ˜K˜K˜Kšž˜K˜šœžœ ˜)K˜&—šœžœ ˜(K˜(—šœžœ ˜(K˜—šœžœ ˜)Kšœžœ˜4—šœžœ ˜)K˜BK˜—šœžœ ˜-K˜2—šœžœ ˜-K˜—šœžœ ˜+Kšœ1žœ ˜A—šœžœ ˜-K˜0—šœžœ ˜,K˜!—šœžœ ˜*Kšœhžœ ˜wK˜½—šœ2˜2K˜—šœ/˜/K˜#—™.K™LKšœ ΟrœŸ,œ#žœ%™——…—a»ο