DIRECTORY Basics USING [LongNumber, LowHalf], D0InputOutput USING [ControllerNumber, GetNextController, IOPage, nullControllerNumber, rdc], DeviceCleanup USING [Await, Item, Reason], DiskFace USING [Label], ProcessorFace USING[ProcessorID], PrincOpsUtils USING[ BITAND, LowHalf], RDC, SA4000Face; SA4000HeadD0: PROGRAM IMPORTS Basics, D0InputOutput, DeviceCleanup, PrincOpsUtils, RDC EXPORTS SA4000Face SHARES ProcessorFace--ProcessorID representation--, SA4000Face = BEGIN OPEN RDC, SA4000Face; CSB: TYPE = MACHINE DEPENDENT RECORD [ cylinder: ARRAY Drive OF CARDINAL, -- Contains the current cylinder address for each drive or -1 if the drive is to be recalibrated before the next command. state: CSBState]; CSBState: TYPE = MACHINE DEPENDENT RECORD [ next: IOCBshortPtr, -- Points to the next IOCB to be processed by the microcode. Points to the last IOCB if an error is reported by the microcode. Contains zero if the queue is empty. This is updated by Initiate and by the microcode. When the microcode finishes processing an IOCB, it replaces next with the next pointer from the IOCB. deferring: INTEGER, -- This is set to -1 by the microcode when it reports an error completion to the Head. The microcode will not process any more IOCBs until this is set to zero again by Poll when the error is reported to the client. In the case of a labelCheck that requires a fixup, the error is not really an error at all, and the client will be unaware of the label fixup. tail: IOCBshortPtr, -- Points to the last IOCB in the queue. Used only by Initiate. transferMask: WORD]; -- This interupt mask is used by the microcode to schedule Mesa processes at the completion of each IOCB. It is initialized by StartB. CSBPtr: TYPE = LONG POINTER TO CSB; DiskHandle: TYPE = MACHINE DEPENDENT RECORD [ -- representation of DeviceHandle zero: [0..16) _ 0, controller: D0InputOutput.ControllerNumber, drive: [0..256)]; Drive: TYPE = [0..drives); drives: CARDINAL = 4; -- maximum number of drives per controller rdcCommands: ARRAY Command OF WORD = [ 4110B, -- headerVerifyLabelVerify 4112B, -- headerVerifyLabelVerifyDataRead 4114B, -- headerVerifyLabelVerifyDataWrite 4111B, -- headerVerifyLabelVerifyDataVerify 4140B, -- headerVerifyLabelWrite 4144B, -- headerVerifyLabelWriteDataWrite 4120B, -- headerVerifyLabelRead 4122B, -- headerVerifyLabelReadDataRead 4124B, -- headerVerifyLabelReadDataWrite 4121B, -- headerVerifyLabelReadDataVerify 4510B, -- headerReadLabelVerify 4512B, -- headerReadLabelVerifyDataRead 4514B, -- headerReadLabelVerifyDataWrite 4511B, -- headerReadLabelVerifyDataVerify 4540B, -- headerReadLabelWrite 4544B, -- headerReadLabelWriteDataWrite 4520B, -- headerReadLabelRead 4522B, -- headerReadLabelReadDataRead 4524B, -- headerReadLabelReadDataWrite 4521B, -- headerReadLabelReadDataVerify 4200B, -- headerWrite 4110B]; -- headerWriteLabelWriteDataWrite (not implemented, so this is really "vv"!) labelRead: WORD = 0020B; -- bit of RDC command signifying label read p: RDC.Base = --Environment.first64K--LOOPHOLE[LONG[0]]; nil: RDC.Base RELATIVE POINTER = LOOPHOLE[0]; nullCylinder: CARDINAL = 177777B; -- used for initialization and recalibration nullDiskHandle: DiskHandle = [controller: D0InputOutput.nullControllerNumber, drive: 0]; -- GetNextDevice depends on this value rdcInProgress: WORD = 0; serviceLateRetries: CARDINAL = 1000; -- retries to be performed by the microcode rateErrorRetries: CARDINAL = 1000; -- retries to be performed by the microcode globalStateSize: PUBLIC CARDINAL _ 0; -- an IOCB for label fixup is not required for the D0 implementation. nullDeviceHandle: PUBLIC DeviceHandle _ Seal[nullDiskHandle]; operationSize: PUBLIC CARDINAL _ SIZE[IOCB]; totalErrors: CARDINAL _ 0; -- = total errors reported by Poll uCodeServiceLateRetries: CARDINAL _ 0; -- total serviceLate retries performed by the microcode uCodeRateErrorRetries: CARDINAL _ 0; -- total rateError retries performed by the microcode GetDeviceAttributes: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = BEGIN RETURN[ cylinders: 202, movingHeads: 8, -- actually depends on whether 4004 or 4008 fixedHeads: 0, -- actually depends on option sectorsPerTrack: 28] END; GetNextDevice: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [DeviceHandle] = BEGIN OPEN D0InputOutput, disk: LOOPHOLE[device, DiskHandle]; controller: ControllerNumber; SELECT disk FROM nullDiskHandle => BEGIN controller _ GetNextController[rdc, nullControllerNumber]; IF controller=nullControllerNumber THEN GOTO Null; -- no drives RETURN[Seal[[controller: controller, drive: 0]]] END; ENDCASE => GOTO Null EXITS Null => RETURN[nullDeviceHandle]; END; Initialize: PUBLIC PROCEDURE[t: WORD, globalState: GlobalStatePtr] = BEGIN OPEN D0InputOutput; csbState: CSBState = [ next: nil, deferring: 0, tail: nil, transferMask: t]; c: ControllerNumber _ nullControllerNumber; WHILE (c _ GetNextController[rdc, c])~=nullControllerNumber DO OPEN csb: LOOPHOLE[@IOPage[c], CSBPtr]; csb.state _ csbState; ENDLOOP; END; InitializeCleanup: PUBLIC PROCEDURE = BEGIN OPEN D0InputOutput; c: ControllerNumber _ nullControllerNumber; WHILE (c _ GetNextController[rdc, c])~=nullControllerNumber DO OPEN csb: LOOPHOLE[@IOPage[c], CSBPtr]; InitializeCleanupForController[@csb]; ENDLOOP; END; Initiate: PUBLIC PROCEDURE [operationPtr: OperationPtr] = BEGIN iocb: IOCBlongPtr = LOOPHOLE[operationPtr]; iocbShort: IOCBshortPtr = LOOPHOLE[PrincOpsUtils.LowHalf[operationPtr]]; csb: CSBPtr = IGetCSB[iocb.operation.device]; iocb.clientHeader _ iocb.operation.clientHeader; PackLabel[to: @iocb.clientLabel, from: iocb.operation.labelPtr]; iocb.operation.deviceStatus.a _ rdcInProgress; iocb.rdcCommand _ rdcCommands[iocb.operation.command]; iocb.next _ nil; iocb.serviceLateRetryCount _ serviceLateRetries; iocb.rateErrorRetryCount _ rateErrorRetries; IF csb.state.next = nil THEN -- microcode is currently not busy, so chain next IOCB onto head of list. csb.state.next _ iocbShort ELSE -- microcode is busy processing an IOCB, but it could report completion at any time. Chain new IOCB onto tail of queue. There is a possible race, which is handled in Poll. p[csb.state.tail].next _ iocbShort; csb.state.tail _ iocbShort; END; Poll: PUBLIC PROCEDURE [operationPtr: OperationPtr] RETURNS [status: Status] = BEGIN iocb: IOCBlongPtr = LOOPHOLE[operationPtr]; csb: CSBPtr = IGetCSB[iocb.operation.device]; controllerIdle: BOOLEAN = csb.state.next=nil OR csb.state.deferring<0; -- used to detect race noted in Initiate uCodeServiceLateRetries _ uCodeServiceLateRetries + (serviceLateRetries-iocb.serviceLateRetryCount); uCodeRateErrorRetries _ uCodeRateErrorRetries + (rateErrorRetries-iocb.rateErrorRetryCount); status _ DecodeStatus[iocb.operation.deviceStatus.a]; -- check status of specified IOCB IF status=inProgress THEN -- operation not completed (possibly not initiated) BEGIN IF controllerIdle THEN csb.state.next _ LOOPHOLE[PrincOpsUtils.LowHalf[operationPtr]]; RETURN END; IF status=goodCompletion THEN NULL -- operation successfully completed ELSE -- operation unsuccessfully completed BEGIN SELECT status FROM labelCheck => BEGIN IF MatchLabels[@iocb.clientLabel, @iocb.diskLabel] THEN -- not an error after all BEGIN iocb.operation.clientHeader _ iocb.clientHeader; iocb.clientLabel.bootChainLink _ iocb.diskLabel.bootChainLink; iocb.operation.deviceStatus.a _ rdcInProgress; csb.state.deferring _ 0; -- restart the controller RETURN[inProgress] END; END; wrongSector => SELECT TRUE FROM iocb.operation.clientHeader.cylinder~=iocb.operation.diskHeader.cylinder => status _ wrongCylinder; iocb.operation.clientHeader.head~=iocb.operation.diskHeader.head => status _ wrongHead; ENDCASE; -- wrongSector corresponds to headerError, so we don't need to change status. ENDCASE; -- other error totalErrors _ totalErrors+1; csb.state.next _ nil; -- zap all deferred requests csb.state.deferring _ 0; -- restart the controller END; iocb.operation.clientHeader _ iocb.clientHeader; UnpackLabel[to: iocb.operation.labelPtr, from: IF PrincOpsUtils.BITAND[iocb.rdcCommand, labelRead]~=0 THEN @iocb.diskLabel ELSE @iocb.clientLabel]; END; Recalibrate: PUBLIC PROCEDURE [device: DeviceHandle] = BEGIN GetCSB[device].cylinder[LOOPHOLE[device, DiskHandle].drive] _ nullCylinder END; Reset: PUBLIC PROCEDURE [device: DeviceHandle] = BEGIN END; DecodeStatus: PROCEDURE [r: UNSPECIFIED] RETURNS [s: Status] = -- decode RDC status word INLINE BEGIN RETURN[LOOPHOLE[LOOPHOLE[r, RDC.DeviceStatus].outcome]]; -- completion code in processor field matches Status codes up to wrongSector END; GetCSB: PROCEDURE[device: DeviceHandle] RETURNS [CSBPtr] = BEGIN RETURN[IGetCSB[device]] END; IGetCSB: PROCEDURE[device: DeviceHandle] RETURNS [CSBPtr] = INLINE BEGIN RETURN[LOOPHOLE[@D0InputOutput.IOPage[LOOPHOLE[device, DiskHandle].controller]]] END; InitializeCleanupForController: PROCEDURE [csb: CSBPtr] = BEGIN OPEN DeviceCleanup; item: Item; reason: Reason; csbState: CSBState; DO reason _ Await[@item]; SELECT reason FROM turnOff, kill => BEGIN UNTIL csb.state.next=nil OR csb.state.deferring<0 DO ENDLOOP; csbState _ csb.state; -- save state of CSB END; turnOn => csb.state _ csbState; -- restore CSB state ENDCASE ENDLOOP END; MatchLabels: PROCEDURE [p1, p2: LONG POINTER TO RDC.PackedLabel] RETURNS [BOOLEAN] = BEGIN MatchableLabel: TYPE = ARRAY [0..8) OF RECORD [UNSPECIFIED]; FOR i: CARDINAL IN [0..7) DO IF LOOPHOLE[p1^, MatchableLabel][i]~=LOOPHOLE[p2^, MatchableLabel][i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE] END; UniversalID: PUBLIC TYPE = --SystemInternal.UniversalID-- MACHINE DEPENDENT RECORD[ processor(0): ProcessorFace.ProcessorID, sequence(3): LONG CARDINAL]; PackLabel: PROCEDURE [to: LONG POINTER TO RDC.PackedLabel, from: LONG POINTER TO DiskFace.Label] = INLINE BEGIN OPEN fileID: LOOPHOLE[from.fileID, UniversalID], -- Mesa AR 4669 seq: LOOPHOLE[--from.--fileID.sequence, Basics.LongNumber], s: LOOPHOLE[seq.highbits, MACHINE DEPENDENT RECORD [ bits0Thru1 (0:0..1): CARDINAL[0..4), bit2 (0:2..2): CARDINAL[0..2), bits3Thru15 (0:3..15): CARDINAL[0..8192)]]; to^ _ [ sequence0Bit2: s.bit2, processorID0: --from.--fileID.processor.a, processorID1: --from.--fileID.processor.b, processorID2: --from.--fileID.processor.c, sequence1: Basics.LowHalf[--from.--fileID.sequence], sequence0Bits3Thru15: s.bits3Thru15, immutable: FALSE, temporary: FALSE, zeroSize: FALSE, filePageLo: from.filePage, type: from.attributes, bootChainLink: PackDiskAddress[LOOPHOLE[from.dontCare]]]; END; Seal: PROCEDURE [disk: DiskHandle] RETURNS [DeviceHandle] = BEGIN RETURN[LOOPHOLE[disk]] END; UnpackLabel: PROCEDURE [to: LONG POINTER TO DiskFace.Label, from: LONG POINTER TO RDC.PackedLabel] = INLINE BEGIN to^ _ [ fileID: LOOPHOLE[UniversalID[ processor: [a: from.processorID0, b: from.processorID1, c: from.processorID2], sequence: LOOPHOLE[Basics.LongNumber[num[ lowbits: from.sequence1, highbits: from.sequence0Bit2*8192+from.sequence0Bits3Thru15]]]]], filePage: from.filePageLo, attributes: from.type, dontCare: LOOPHOLE[UnpackDiskAddress[from.bootChainLink]]]; END; END. LOG Time: August 8, 1979 1:09 AM By: Redell Action: Add log to file from Jarvis Time: August 17, 1979 1:27 PM By: Gobbel Action: Pilot formatting Time: August 22, 1979 12:40 PM By: Gobbel Action: Bring up to date with revised device face Time: October 2, 1979 11:38 PM By: Redell Action: Further cleanup, esp for bootchains (partial label verification) Time: October 8, 1979 9:22 AM By: McJones Action: Restart controller bug, add hardwareState Time: October 10, 1979 3:53 PM By: McJones Action: INLINE procedures for speed; labelCheck is cue to call StartLabelFix Time: October 30, 1979 11:04 AM By: McJones AR2643: Label fix didn't reset iocb.label from user's label Time: November 1, 1979 1:20 PM By: Frandeen Action: Fix recalibrate after header error; bug in SynchronizeWithController Time: November 7, 1979 1:17 PM By: McJones AR2739: Label fix up confused by spurious labelCheck status returned by read label command on sector with missing (label?) synch byte Time: November 9, 1979 8:59 AM By: Frandeen Action: Implement runs of pages. Change interface to Poll. Change LabelFix procedures. Change interface to StartB since we no longer need an extra IOCB for label fixup. Time: December 13, 1979 11:29 AM By: Gobbel Action: Change name from SA4000D0Head to SA4000HeadD0 Time: January 30, 1980 12:48 PM By: McJones Action: Update to match new face; add StartChain logic Time: June 25, 1980 10:34 AM By: McJones Action: 48-bit processor ids Time: July 24, 1980 11:23 AM By: McJones Action: Label fixup must update iocb.operation.clientHeader Time: November 2, 1983 4:35 pm By: Birrell Action: conversion to 5.0 ¶SA4000HeadD0.mesa (last edited by: McJones on: July 24, 1980 11:23 AM) Things to do: 1) GetNextDevice should find all drives on a controller (does this need a microcode change?) 2) The semantics of aborting should be independent of controllers; e.g. aborting could work on all drives, or on an individual drive. (Currently it works on all drives of a controller, but it is hard to see how this can be specified in a controller-independent way.) Last Edited by: Birrell, November 3, 1983 10:32 am TYPE DEFINITIONS Controller status block. There is one CSB for each controller, with a maximum of four controllers. The CSB resides in a resident page of memory. D0InputOutput.IOPage is a LONG POINTER to an array of CSBs. The CSB is indexed by the controller number in the DiskHandle. This part of the CSB is defined separately so that it can be saved and restored by InitializeCleanup. CONSTANTS These are the RDC operation codes that correspond to the LabelDataOperations. For each operation, header verify is assumed. Each command includes the allow wake bit (4000). GLOBAL VARIABLES PROCEDURES MUST BE MODIFIED TO HANDLE FIXED HEADS, SINGLE-PLATTER DRIVES MUST BE MODIFIED TO HANDLE MULTIPLE DRIVES/CONTROLLER This procedure initiates an operation. The client passes an IOCB with some 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. The microcode wakes up at every sector to see if there is an IOCB waiting in the CSB queue. Initialize clientHeader, clientLabel, status, RDC command, and next fields. Design of RDC dictates position of clientHeader and (8 word) clientLabel fields in IOCB. The microcode performs error retries automatically for serviceLate and rateError because they occur frequently. Chain this IOCB onto CSB for processing by microcode. Update the error counts performed by the microcode. The number of retries in the IOCB will be decremented by one for each retry. We keep track of these for debugging purposes. If controller went idle before noticing this request, resubmit it (and following IOCBs). For all of the following completion codes, the microcode has reported an error. csb.next still points to the IOCB that caused the error. csb.deferring has been set to -1 by the microcode to prevent it from processing any more IOCBs until we set it back to zero. The RDC microcode has read the actual label from the disk into the diskLabel of the IOCB. The RDC verifies all 8 words of the label, but we are only interested in verifying the "significant" ones. If all the label words that we want to verify match the label words on the disk, we need only resubmit the IOCB again. First we update the clientHeader field in the operation to match the one in the IOCB which has been incremented by the microcode and we update the bootChainLink field in the operation label to match the corresponding field in the disk label. The code that was reported by the Controller is headerError. This means that the header read from the disk did not match the header sent by the client. It could be a wrongCylinder, wrongHeader, or wrongSector. WHAT SHOULD THIS DO? Private procedures All fields but bootChainLink are significant. Pilot code: (Really should treat filePageHi as significant when type IN PilotVolumeFileType.) Old Pilot code: vp: NULL]; IF from.type IN PilotFileTypes.PilotVFileType THEN to.vp _ volumeFile[filePageHi: from.filePageHi] ELSE to.vp _ normalFile[PackDiskAddress[LOOPHOLE[from.bootChainLink]]]; Old pilot code: bootChainLink: LOOPHOLE[LONG[0]]]; IF from.type IN PilotFileTypes.PilotVFileType THEN to.filePageHi _ from.filePageHi ELSE to.bootChainLink _ LOOPHOLE[UnpackDiskAddress[from.bootChainLink]]; Ê !˜JšœG™Gšœ ™ Jšœ\™\Jšœ‹™‹—J™2J™šÏk ˜ Jšœœ˜#JšœœJ˜]Jšœœ˜*Jšœ œ ˜Jšœœ˜!Jšœœœ ˜&Jšœ˜Jšœ ˜ —J˜šœ˜Jšœ6˜@Jšœ ˜JšœÏcœ˜@—J˜Jšœœœ ˜J™Jšœ™J˜š œœœ œœ˜&Jšœ‹™‹Jšœ œœœžy˜J˜—J˜š œ œœ œœ˜+Jšœe™eJšœž»˜ÐJšœ œžæ˜úJšœž?˜TJšœœž‡˜œ—J˜Jš œœœœœœ˜#J˜š œ œœ œœž!˜OJ˜J˜+J˜—J˜Jšœœ˜J™Jšœ ™ J˜Jšœœž*˜AJ˜šœ œ œœ˜&Jšœ¬™¬Jšœ ž˜#Jšœ ž"˜+Jšœ ž#˜,Jšœ ž$˜-Jšœ ž˜"Jšœ ž"˜+Jšœ ž˜!Jšœ ž ˜)Jšœ ž!˜*Jšœ ž"˜+Jšœ ž˜!Jšœ ž ˜)Jšœ ž!˜*Jšœ ž"˜+Jšœ ž˜ Jšœ ž ˜)Jšœ ž˜Jšœ ž˜'Jšœ ž˜(Jšœ ž ˜)Jšœ ž˜Jšœ žL˜V—J˜Jšœ œ ž+˜DJ˜Jšœœžœœ˜8J˜Jš œœœœœ˜-J˜Jšœœ ž,˜NJ˜JšœYž&˜J˜Jšœœ˜J˜Jšœœ ž+˜PJšœœ ž+˜NJ™Jšœ™J˜JšœœœžE˜kJ˜Jšœœ%˜=J˜Jš œœœœœ˜,J˜Jšœ œž"˜=Jšœœž7˜^Jšœœž5˜ZJ™Jšœ ™ J˜š Ïnœœ œœ7œ˜†Jšœ=™=Jš˜šœ˜J˜Jšœž+˜;Jšœž˜,J˜—Jšœ˜—J˜šŸ œœ œœ˜OJšœ5™5Jšœœœ˜=J˜šœ˜˜Jš˜J˜:Jšœ!œœž ˜?Jšœ*˜0Jšœ˜——Jšœœ˜Jšœ œ˜'Jšœ˜—J˜šŸ œœ œœ ˜DJšœœ˜˜J˜ J˜ J˜ J˜—J˜+šœ7˜>Jšœœ˜'J˜Jšœ˜—Jšœ˜—J˜šŸœœ œ˜%Jšœœ˜J˜+šœ7˜>Jšœœ˜'J˜%Jšœ˜—Jšœ˜—J˜šŸœœ œ˜9Jš˜JšœÄ™ÄJšœœ˜+Jšœœ&˜HJ˜.JšœL™LJšœX™XJ˜0J˜@J˜.J˜6J˜Jšœo™oJ˜0J˜,Jšœ5™5šœœžJ˜gJ˜—šœž­˜²J˜#—J˜Jšœ˜—J˜šŸœœ œœ˜NJš˜Jšœœ˜+J˜.Jšœœœž(˜oJšœ°™°J˜dJ˜\Jšœ6ž!˜Wšœœž3˜MJšœX™XJš˜Jšœœœ'˜]Jšœ˜—Jšœœœž#˜Fšœž%˜*Jš˜Jšœ…™…šœ˜˜Jš˜JšœÆ™Æšœ1œž˜QJš˜Jšœé™éJ˜0J˜>J˜.Jšœž˜2Jšœ ˜Jšœ˜—Jšœ˜—˜JšœÒ™Òšœœ˜J˜cJ˜W—JšœžM˜W——Jšœž˜J˜Jšœž˜2Jšœž˜2Jšœ˜—J˜0J˜(Jš œœœ œœ˜jJšœ˜—J˜šŸ œœ œ˜6Jš˜Jšœœ*˜JJšœ˜—J˜šŸœœ œ˜0Jš˜Jšœ™Jšœ˜—J˜Jšœ™J˜š Ÿ œ œ œœž˜XJšœ˜ Jš œœœœžL˜…Jšœ˜—J˜šŸœ œœ ˜:Jš˜Jšœ˜Jšœ˜—J˜šŸœ œœ ˜;Jšœ˜ Jšœœœ"˜PJšœ˜—J˜šŸœ œ˜9Jšœœ˜J˜ J˜J˜š˜J˜šœ˜˜Jš˜Jšœœœœ˜=Jšœž˜*Jšœ˜—Jšœ ž˜4—Jš˜—Jš˜Jšœ˜—J˜šŸ œ œ œœœœœœ˜TJšœ-™-Jšœ]™]Jš˜Jš œœœœœ œ˜<šœœœ˜Jš œœœœœœ˜Y—Jšœ˜Jšœœ˜ Jšœ˜—J˜š œ œœžœœ œœ˜SJšœ(˜(Jšœ œœ˜—J˜šŸ œ œœœœœœœœ˜bJšœœ˜Jšœœž˜;Jšœœž œ$˜;š œœœ œœ˜4Jšœœ˜$Jšœœ˜Jšœœ ˜+—˜J˜Jšœž œ˜*Jšœž œ˜*Jšœž œ˜*Jšœž œ˜4J˜$Jšœ œ˜Jšœ œ˜Jšœ œ˜J˜J˜Jšœœ˜9—šœ™Jšœœ™ šœ œ™2Jšœ/™/—Jšœ$œ™G—Jšœ˜—J˜šŸœ œœ˜;Jšœœœœ˜!—J˜šŸ œ œœœœœœœœ˜dJšœ˜ ˜Jšœœ ˜J˜Ošœ œ˜)J˜J˜A—J˜J˜Jšœ œ)˜;—šœ™Jšœœœ™"šœ œ™2J™—Jšœœ(™H—Jšœ˜—J˜Jšœ˜J˜š˜Jšœœ/˜LJšœœ$˜BJšœœ=˜\JšœœT˜sJšœœ>˜\Jšœœœ>˜xJšœœH˜hJšœœZ˜yJšœœ’˜±Jšœœ¡œ˜ÕJšœœA˜bJšœœC˜cJšœœ)˜FJšœœH˜eJšœD˜D—J˜—…—2MÙ