DIRECTORY DeviceCleanup USING [Await, Item, Reason], DiskFace USING [Label], DoradoInputOutput USING [IOAddress, DMuxAddr, Output, ResetDisk, RWMufMan], PrincOpsUtils USING [BITAND, BITOR, LowHalf], SA4000Face; DiskHeadDorado: PROGRAM IMPORTS DeviceCleanup, DoradoInputOutput, PrincOpsUtils EXPORTS SA4000Face SHARES SA4000Face = BEGIN OPEN SA4000Face; CSBPtr: TYPE = LONG POINTER TO CSB; CSB: TYPE = MACHINE DEPENDENT RECORD [ head (0): IOCBQueueHead, interruptMask (1): WORD, drive (2): Drive, -- drive currently selected cylinder (3): CARDINAL, -- negative => need to restore disk tail (4): IOCBLongPtr]; IOCBQueueHead: TYPE = MACHINE DEPENDENT RECORD [ SELECT OVERLAID * FROM untagged => [iocb: IOCBShortPtr], tagged => [highBits: [0..77777B], tag: {idle, active}], ENDCASE]; nilIOCBQueueHead: IOCBQueueHead = [untagged[iocb: nilIOCBShortPtr]]; IOCBShortPtr: TYPE = SA4000Face.Base RELATIVE POINTER TO IOCB; IOCBLongPtr: TYPE = LONG POINTER TO IOCB; IOCB: TYPE = MACHINE DEPENDENT RECORD [ next (0B): IOCBShortPtr, seal (1B): CARDINAL, drive (2B): Drive, pageCount (3B): CARDINAL, command (4B): DiskCommand, diskAddress (5B): DiskAddress, diskHeader (7B): DiskAddress, headerPtr (11B): LONG POINTER TO DiskAddress, headerECC (13B): LONG CARDINAL, headerStatus (15B): DiskStatus, labelPtr (16B): LONG POINTER TO DiskFace.Label, labelECC (20B): LONG CARDINAL, labelStatus (22B): DiskStatus, dataPtr (23B): LONG POINTER, dataECC (25B): LONG CARDINAL, dataStatus (27B): DiskStatus, diskLabel (30B): DiskFace.Label]; nilIOCBShortPtr: IOCBShortPtr = LOOPHOLE[0]; DoradoOperationPtr: TYPE = LONG POINTER TO DoradoOperation; DoradoOperation: TYPE = MACHINE DEPENDENT RECORD [ operation (0): Operation, iocb (15B): IOCB]; DiskStatus: TYPE = MACHINE DEPENDENT RECORD [ seekInc (0: 0..0): BOOLEAN, headOvfl (0: 1..1): BOOLEAN, devCheck (0: 2..2): BOOLEAN, notSelected (0: 3..3): BOOLEAN, notOnLine (0: 4..4): BOOLEAN, notReady (0: 5..5): BOOLEAN, sectorOvfl (0: 6..6): BOOLEAN, fifoUnderflow (0: 7..7): BOOLEAN, fifoOverflow (0: 8..8): BOOLEAN, checkErr (0: 9..9): BOOLEAN, readOnly (0: 10..10): BOOLEAN, cylOffset (0: 11..11): BOOLEAN, iobParityErr (0: 12..12): BOOLEAN, fifoParityErr (0: 13..13): BOOLEAN, eccErr (0: 14..14): BOOLEAN, sectorSearchErr (0: 15..15): BOOLEAN]; nullDiskStatus: DiskStatus = LOOPHOLE[0]; errorStatusMask: DiskStatus = [ seekInc: TRUE, headOvfl: TRUE, devCheck: TRUE, notSelected: TRUE, notOnLine: TRUE, notReady: TRUE, sectorOvfl: TRUE, fifoUnderflow: TRUE, fifoOverflow: TRUE, checkErr: TRUE, iobParityErr: TRUE, fifoParityErr: TRUE, eccErr: TRUE, sectorSearchErr: TRUE, readOnly: FALSE, cylOffset: FALSE]; sealValid: CARDINAL = 125377B; sealNil: CARDINAL = 0; cylinderRestore: CARDINAL = 177777B; cylinderUnknown: CARDINAL = 77777B; diskControl: DoradoInputOutput.IOAddress = 10B; DiskCommand: TYPE = MACHINE DEPENDENT RECORD [ incrementDataPtr (0: 0..0): BOOLEAN _ FALSE, -- in IOCB only; not defined in hardware unused1 (0: 1..4): Filler, clearEnableRun (0: 5..5): BOOLEAN _ FALSE, debugMode (0: 6..6): BOOLEAN _ FALSE, blockTilIndex (0: 7..7): BOOLEAN _ FALSE, header (0: 8..9): Action _ none, label (0: 10..11): Action _ none, data (0: 12..13): Action _ none, unused2 (0: 14..15): Action _ none]; Action: TYPE = MACHINE DEPENDENT {none, write, check, read}; diskMuff: DoradoInputOutput.IOAddress = 11B; MuffCommand: TYPE = MACHINE DEPENDENT RECORD [ unused (0: 0..1): Filler, clearCompareErr (0: 2..2): BOOLEAN _ FALSE, setChecksumErr (0: 3..3): BOOLEAN _ FALSE, clearIndexTW (0: 4..4): BOOLEAN _ FALSE, clearSectorTW (0: 5..5): BOOLEAN _ FALSE, clearSeekTagTW (0: 6..6): BOOLEAN _ FALSE, clearErrors (0: 7..7): BOOLEAN _ FALSE, muffAddr (0: 8..15): MufflerAddress _ tempSense]; MuffInput: TYPE = MACHINE DEPENDENT RECORD [ unused (0: 0..14): Filler, bit (0: 15..15): BOOLEAN]; diskTag: DoradoInputOutput.IOAddress = 14B; TagCommand: TYPE = MACHINE DEPENDENT RECORD [ driveTag (0: 0..0): BOOLEAN _ FALSE, cylinderTag (0: 1..1): BOOLEAN _ FALSE, headTag (0: 2..2): BOOLEAN _ FALSE, controlTag (0: 3..3): BOOLEAN _ FALSE, bus (0: 4..15): SELECT OVERLAID * FROM drive => [ unused (0: 4..5): Filler, subSectorCount (0: 6..9): [0..17B] _ 0, -- sub-sectors/sector - 1 loadSubSector (0: 10..10): BOOLEAN _ FALSE, select (0: 11..11): BOOLEAN _ FALSE, driveNumber (0: 12..15): Drive], cylinder => [ cylinder (0: 4..15): [0..7777B]], head => [ unused (0: 4..7): Filler, offset (0: 8..8): BOOLEAN _ FALSE, offsetDirection (0: 9..9): {out(0), in(1)} _ out, headNumber (0: 10..15): [0..77B]], control => [ syncPattern (0: 4..4): {s201, s001} _ s201, -- s001 is Alto-compatible unused (0: 5..5): Filler, strobeLate (0: 6..6): BOOLEAN _ FALSE, strobeEarly (0: 7..7): BOOLEAN _ FALSE, write (0: 8..8): BOOLEAN _ FALSE, read (0: 9..9): BOOLEAN _ FALSE, addressMark (0: 10..10): BOOLEAN _ FALSE, headReset (0: 11..11): BOOLEAN _ FALSE, deviceCheckReset (0: 12..12): BOOLEAN _ FALSE, headSelect (0: 13..13): BOOLEAN _ FALSE, rezero (0: 14..14): BOOLEAN _ FALSE, headAdvance (0: 15..15): BOOLEAN _ FALSE], ENDCASE]; MufflerAddress: TYPE = MACHINE DEPENDENT { tempSense(0), indexTW(1), sectorTW(2), seekTagTW(3), rdFifoTW(4), wrFifoTW(5), readData(6), writeData(7), enableRun(10B), debugMode(11B), notRdOnlyBlock(12B), notWriteBlock(13B), notCheckBlock(14B), active(15B), select0(16B), select1(17B), seekInc(20B), headOvfl(21B), devCheck(22B), notSelected(23B), notOnLine(24B), notReady(25B), sectorOvfl(26B), fifoUnderflow(27B), fifoOverflow(30B), readDataErr(31B), readOnly(32B), cylOffset(33B), iobParityErr(34B), fifoParityErr(35B), writeError(36B), readError(37B), (177B)}; diskDMuxAddr: [0..7777B] = 2000B; -- DMux address of first disk muffler Filler: TYPE = [0..1] _ 0; DiskHandle: TYPE = MACHINE DEPENDENT RECORD [ unused (0: 0..7): Filler, drive (0: 8..15): Drive]; nullDiskHandle: DiskHandle = LOOPHOLE [177777B]; Drive: TYPE = [0..drives); drives: CARDINAL = 16; -- maximum number of drives per controller Model: TYPE = {nonexistent, system, t80, t300}; modelCylinders: ARRAY Model OF CARDINAL = [ nonexistent: 0, system: 815*5, t80: 815, t300: 815]; modelHeads: ARRAY Model OF CARDINAL = [ nonexistent: 0, system: 1, t80: 5, t300: 19]; modelSectors: ARRAY Model OF CARDINAL = [ nonexistent: 0, system: 28, t80: 28, t300: 28]; globalStateSize: PUBLIC CARDINAL _ 0; nullDeviceHandle: PUBLIC DeviceHandle _ DeviceFromDiskHandle[nullDiskHandle]; operationSize: PUBLIC CARDINAL _ SIZE[DoradoOperation]; csb: CSBPtr = LOOPHOLE[LONG[177520B]]; totalErrors: CARDINAL _ 0; -- = total errors reported by Poll modelTable: ARRAY [0..drives) OF Model _ ALL [nonexistent]; GetDeviceAttributes: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [cylinders, movingHeads, fixedHeads, sectorsPerTrack: CARDINAL] = BEGIN diskHandle: DiskHandle = DiskFromDeviceHandle[device]; model: Model = modelTable[diskHandle.drive]; RETURN [ cylinders: modelCylinders[model], movingHeads: modelHeads[model], fixedHeads: 0, sectorsPerTrack: modelSectors[model]]; END; GetNextDevice: PUBLIC PROCEDURE [device: DeviceHandle] RETURNS [DeviceHandle] = BEGIN disk: DiskHandle = DiskFromDeviceHandle[device]; IF disk=nullDiskHandle THEN -- drive 0 presumed to exist always RETURN [DeviceFromDiskHandle[[drive: 0]]]; FOR drive: Drive IN (disk.drive..drives) DO IF modelTable[drive]#nonexistent THEN RETURN [DeviceFromDiskHandle[[drive: drive]]]; ENDLOOP; RETURN [nullDeviceHandle]; END; Initialize: PUBLIC PROCEDURE [t: WORD, globalState: GlobalStatePtr] = BEGIN OPEN DoradoInputOutput; ManualStrobe: PROCEDURE [tag, data: TagCommand] = BEGIN Output[data, diskTag]; Output[PrincOpsUtils.BITOR[tag, data], diskTag]; Output[data, diskTag]; END; AutoStrobe: PROCEDURE [data: TagCommand] = BEGIN Output[data, diskTag]; THROUGH [0..10) DO NULL; ENDLOOP; END; ReadMuffler: PROCEDURE [addr: MufflerAddress] RETURNS [status: BOOLEAN] = BEGIN RETURN [ DoradoInputOutput.RWMufMan[ [useDMD: FALSE, dMuxAddr: DMuxFromMufAddr[addr]]].dMuxData#0]; END; ClassifyDrive: PROCEDURE [drive: Drive] RETURNS [model: Model] = BEGIN ManualStrobe[tag: [driveTag: TRUE, bus: drive[driveNumber: 0]], data: [bus: drive[select: TRUE, driveNumber: drive]]]; IF ReadMuffler[notSelected] THEN RETURN [nonexistent] ELSE BEGIN AutoStrobe[[controlTag: TRUE, bus: control[deviceCheckReset: TRUE]]]; AutoStrobe[[headTag: TRUE, bus: head[headNumber: modelHeads[t80]]]]; model _ IF ReadMuffler[headOvfl] THEN t80 ELSE t300; AutoStrobe[[controlTag: TRUE, bus: control[deviceCheckReset: TRUE]]]; END; END; lastDrive: Drive; ResetDisk[disable]; -- disable disk task so it won't run and confuse matters csb^ _ [ head: nilIOCBQueueHead, interruptMask: t, drive: 0, cylinder: cylinderUnknown, tail: NIL]; modelTable[0] _ system; -- drive 0 presumed to exist always lastDrive _ (IF ClassifyDrive[LAST[Drive]]=nonexistent THEN LAST[Drive]-1 ELSE 3); FOR drive: Drive IN [1..lastDrive] DO modelTable[drive] _ ClassifyDrive[drive]; ENDLOOP; ResetDisk[normal]; -- put disk microcode back in normal state END; InitializeCleanup: PUBLIC PROCEDURE = BEGIN OPEN DeviceCleanup; item: Item; reason: Reason; savedCSB: CSB; DO reason _ Await[@item]; SELECT reason FROM turnOff, kill => BEGIN UNTIL csb.head.tag = idle DO ENDLOOP; savedCSB _ csb^; csb.head.iocb _ nilIOCBShortPtr; END; turnOn => BEGIN csb^ _ savedCSB; csb.cylinder _ cylinderUnknown; END; ENDCASE ENDLOOP END; Initiate: PUBLIC PROCEDURE [operationPtr: OperationPtr] = BEGIN doradoOperationPtr: DoradoOperationPtr = LOOPHOLE[operationPtr]; iocb: IOCBLongPtr = @doradoOperationPtr.iocb; iocbShort: IOCBShortPtr = LOOPHOLE[PrincOpsUtils.LowHalf[iocb]]; diskCommand: DiskCommand _ SELECT operationPtr.command FROM vv => [incrementDataPtr: FALSE, header: check, label: check, data: none], vvr => [incrementDataPtr: FALSE, header: check, label: check, data: read], vvw => [incrementDataPtr: FALSE, header: check, label: check, data: write], vvv => [incrementDataPtr: FALSE, header: check, label: check, data: check], vw => [incrementDataPtr: FALSE, header: check, label: write, data: none], vww => [incrementDataPtr: FALSE, header: check, label: write, data: write], vr => [incrementDataPtr: FALSE, header: check, label: read, data: none], vrr => [incrementDataPtr: FALSE, header: check, label: read, data: read], vrw => [incrementDataPtr: FALSE, header: check, label: read, data: write], vrv => [incrementDataPtr: FALSE, header: check, label: read, data: check], rv => [incrementDataPtr: FALSE, header: read, label: check, data: none], rvr => [incrementDataPtr: FALSE, header: read, label: check, data: read], rvw => [incrementDataPtr: FALSE, header: read, label: check, data: write], rvv => [incrementDataPtr: FALSE, header: read, label: check, data: check], rw => [incrementDataPtr: FALSE, header: read, label: write, data: none], rww => [incrementDataPtr: FALSE, header: read, label: write, data: write], rr => [incrementDataPtr: FALSE, header: read, label: read, data: none], rrr => [incrementDataPtr: FALSE, header: read, label: read, data: read], rrw => [incrementDataPtr: FALSE, header: read, label: read, data: write], rrv => [incrementDataPtr: FALSE, header: read, label: read, data: check], w => [incrementDataPtr: FALSE, header: write, label: none, data: none], ENDCASE => ERROR; diskCommand.incrementDataPtr _ operationPtr.incrementDataPtr; iocb^ _ [ next: nilIOCBShortPtr, seal: sealValid, drive: DiskFromDeviceHandle[operationPtr.device].drive, pageCount: operationPtr.pageCount, command: diskCommand, diskAddress: operationPtr.clientHeader, diskHeader: operationPtr.clientHeader, headerPtr: @iocb.diskHeader, headerECC: 0, headerStatus: nullDiskStatus, labelPtr: IF diskCommand.label=read THEN operationPtr.labelPtr ELSE @iocb.diskLabel, labelECC: 0, labelStatus: nullDiskStatus, dataPtr: operationPtr.dataPtr, dataECC: 0, dataStatus: nullDiskStatus, diskLabel: operationPtr.labelPtr^]; IF csb.head.iocb # nilIOCBShortPtr THEN csb.tail.next _ iocbShort; IF csb.head.iocb = nilIOCBShortPtr AND iocb.seal = sealValid THEN csb.head.iocb _ iocbShort; csb.tail _ iocb; END; Poll: PUBLIC PROCEDURE [operationPtr: OperationPtr] RETURNS [status: Status] = BEGIN doradoOperationPtr: DoradoOperationPtr = LOOPHOLE[operationPtr]; iocb: IOCBLongPtr = @doradoOperationPtr.iocb; iocbShort: IOCBShortPtr = LOOPHOLE[PrincOpsUtils.LowHalf[iocb]]; status _ IF iocb.seal=sealValid THEN inProgress ELSE goodCompletion; operationPtr.clientHeader _ iocb.diskAddress; operationPtr.pageCount _ iocb.pageCount; IF status#inProgress THEN BEGIN IF iocb.pageCount=0 THEN BEGIN IF iocb.diskAddress.head = modelHeads[modelTable[iocb.drive]] THEN BEGIN iocb.diskAddress.head _ 0; iocb.diskAddress.cylinder _ iocb.diskAddress.cylinder+1; END END ELSE BEGIN combinedStatus: DiskStatus = PrincOpsUtils.BITOR[iocb.headerStatus, PrincOpsUtils.BITOR[iocb.labelStatus, iocb.dataStatus]]; SELECT TRUE FROM combinedStatus.notSelected OR combinedStatus.notOnLine OR combinedStatus.notReady => status _ notReady; combinedStatus.seekInc => status _ seekTimeout; combinedStatus.sectorSearchErr => status _ sectorTimeout; combinedStatus.headOvfl => IF iocb.diskAddress.head = modelHeads[modelTable[iocb.drive]] THEN BEGIN iocb.diskAddress.head _ 0; iocb.diskAddress.cylinder _ iocb.diskAddress.cylinder+1; GOTO restart; END ELSE status _ hardwareError; iocb.headerStatus.checkErr => status _ SELECT TRUE FROM iocb.diskHeader.cylinder#iocb.diskAddress.cylinder => wrongCylinder, iocb.diskHeader.head#iocb.diskAddress.head => wrongHead, ENDCASE => wrongSector; PrincOpsUtils.BITAND[iocb.headerStatus, errorStatusMask]#0 => status _ hardwareError; iocb.labelStatus.sectorOvfl OR iocb.labelStatus.eccErr => status _ labelError; iocb.labelStatus.checkErr => status _ labelCheck; PrincOpsUtils.BITAND[iocb.labelStatus, errorStatusMask]#0 => status _ hardwareError; iocb.dataStatus.sectorOvfl OR iocb.dataStatus.eccErr => status _ dataError; ENDCASE => status _ hardwareError; csb.head.iocb _ nilIOCBShortPtr; totalErrors _ totalErrors+1; EXITS restart => BEGIN iocb.headerStatus _ iocb.labelStatus _ iocb.dataStatus _ nullDiskStatus; iocb.seal _ sealValid; csb.head.iocb _ iocbShort; status _ inProgress; END; END; IF iocb.command.label#read THEN operationPtr.labelPtr^ _ iocb.diskLabel; operationPtr.dataPtr _ iocb.dataPtr; operationPtr.diskHeader _ iocb.diskHeader; END; END; Recalibrate: PUBLIC PROCEDURE [device: DeviceHandle] = BEGIN csb.cylinder _ cylinderRestore; END; Reset: PUBLIC PROCEDURE [device: DeviceHandle] = BEGIN END; DiskFromDeviceHandle: PROCEDURE [device: DeviceHandle] RETURNS [DiskHandle] = INLINE BEGIN RETURN[LOOPHOLE[device]]; END; DeviceFromDiskHandle: PROCEDURE [disk: DiskHandle] RETURNS [DeviceHandle] = INLINE BEGIN RETURN[LOOPHOLE[disk]]; END; DMuxFromMufAddr: PROCEDURE [mufAdr: MufflerAddress] RETURNS [DoradoInputOutput.DMuxAddr] = INLINE BEGIN RETURN[diskDMuxAddr+LOOPHOLE[mufAdr, CARDINAL]]; 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: December 6, 1980 2:56 PM Taft Convert for Dorado Time: February 13, 1983 1:24 pm Taft Add support for multiple drives and T-300s Time: May 5, 1983 2:49 pm By: Andrew Birrell Action: convert to new Cedar nucleus FCedar Nucleus: Dorado implementation of SA4000Face DiskHeadDorado.mesa Last Edited by: Taft, February 24, 1983 5:26 pm Last Edited by: Andrew Birrell, May 5, 1983 2:48 pm Data structures shared with disk microcode Remainder of CSB is not used by the microcode IOCB must be odd word aligned Guarantee required odd word alignment, since Operation is 16-word aligned These bits constitute errors: These are just status bits that do not necessarily constitute errors: Disk hardware definitions Output to DiskControl register, and used as diskCommand in IOCBs Output to DiskMuff register Input from DiskMuff register Output to DiskTag register Muffler addresses for status bits (DskEth-relative) others of interest only to Midas Data structures private to the software The "system" drive is drive 0, which is always assumed to be a T-80 that is addressed in a funny way. t80 and t300 refer to drives other than drive 0, which are addressed in the normal way. Public variables an IOCB for label fixup is not required for the Dorado implementation. Private variables Public procedures Assumes Initialize is called before GetDeviceAttributes Assumes Initialize is called before GetNextDevice Assumes that the disk hardware and microcode are quiescent No need to wait: drive is advertised to return status within 200 ns of its being selected. Need to wait at least 1.2 microseconds for strobe sequence to complete, plus an unspecified amount of time (probably less than a microsecond) for the drive to return up-to-date status. Reads through system DMux rather than through DskEth muffler interface, because it's impossible for the disk task to dismiss wakeups without clobbering the muffler address. Select the drive. If it doesn't become selected then it doesn't exist. Try to select a head which does not exist on a T80. This causes a headOvfl error on a T80, not on a T300 Drives 0-2 are connected directly to the controller, and drive 3 also if no multiplexor is present. If a multiplexor is present, it is connected in place of drive 3. Drive 17B is presumed not to exist. Try selecting drive 17B. If this succeeds then what really happened is that no multiplexor is present and drive 3 got selected instead. If this fails then either there is a multiplexor or there is no multiplexor and no drive 3 is present; it is safe to test each of drives 4-16 since in this case they will select only if there is a multiplexor. Chain this IOCB onto CSB for processing by microcode. Capture in-progress/done state here and use it for all subsequent decisions. If the command finishes during the code below, we will still report inProgress to the client, who will Poll again and get the real ending state. Copy things back to client that he might want to monitor while the command is still in progress. Command has completed: successfully if pageCount=0, unsuccessfully otherwise. Successful transfer happened to end at a cylinder boundary. Increment disk address to next cylinder. Error occurred. Must carefully examine DiskStatus for each block to determine what happened and to decide what Status to report to the client. First check for problems that might have caused the operation to malfunction and generated other errors as secondary effects. Check for head overflow: means transfer crossed a cylinder boundary. Look for header problems: Header check error: must discriminate possible causes Would like to distinguish other header errors (e.g., header checksum), but there is no Status value with which to report them!! Look for label problems: Look for data problems: Microcode has been deferring ever since the error occurred. Now zap all deferred operations and allow new ones to be Initiated. Copy updated information back to client. We already did clientHeader and pageCount. Note: if reading label, it was read directly to client so don't clobber it here. WHAT SHOULD THIS DO? Private procedures ÊY˜JšÏc2™2Jšœ™Jšœ/™/Jšœ3™3J˜šÏk ˜ Jšœžœ˜*Jšœ žœ ˜Jšœžœ4˜KJšœžœžœžœ ˜-J˜ J˜—šœž˜Jšžœ0˜7Jšžœ ˜Jšžœ ˜—J˜Jšžœžœ ˜J˜Jšœ*™*J˜Jš œžœžœžœžœžœ˜#š žœžœžœž œžœ˜&J˜Jšœžœ˜Jšœ˜-Jšœžœ#˜;Jšœ.™.J˜J˜—š œžœžœž œžœ˜0šžœžœž˜J˜!J˜7Jšžœ˜ J˜——J˜DJ˜Jšœ™Jš œžœžœžœžœžœ˜>Jš œ žœžœžœžœžœ˜)š žœžœžœž œžœ˜'J˜Jšœ žœ˜J˜Jšœžœ˜J˜J˜J˜Jšœžœžœžœ ˜-Jšœžœžœ˜J˜Jšœžœžœžœ˜/Jšœžœžœ˜J˜Jšœžœžœ˜Jšœžœžœ˜J˜J˜!J˜—Jšœ žœ˜,J˜Jš œžœžœžœžœ˜;š œžœžœž œžœ˜2J˜JšœI™IJšœ žœ˜J˜—š œ žœžœž œžœ˜-Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜!Jšœžœ˜ Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜"Jšœžœ˜#Jšœžœ˜Jšœžœ˜&J˜—Jšœžœ˜)˜Jšœ™Jš œ žœ žœ žœžœ žœ˜RJš œ žœžœžœžœ˜JJš œ žœžœžœ žœ˜FJšœžœ˜JšœE™EJšœ žœ žœ˜#J˜—Jšœ žœ ˜Jšœ žœ˜J˜Jšœžœ ˜$Jšœžœ ˜#J˜J˜Jšœ™J˜Jšœ@™@J˜/š œ žœžœž œžœ˜.Jšœžœžœ(˜UJ˜Jšœžœžœ˜*Jšœžœžœ˜%Jšœžœžœ˜)J˜ J˜!J˜ J˜$—Jšœžœžœž œ˜——Jšžœ˜—šŸ œž œžœ˜@Jšž˜JšœG™Gšœžœ˜?Jšœžœ˜6—Jšžœžœžœ˜5šžœž˜ Jšœ3™3Jšœ4™4Jšœžœ!žœ˜EJšœžœ+˜DJšœžœžœžœ˜4Jšœžœ!žœ˜EJšžœ˜—Jšžœ˜—J˜Jšœ8˜L˜J˜3Jšœ!žœ˜&—Jšœ#˜˜\Jšœžœžœ>˜xJšœžœH˜hJšœžœZ˜yJšœžœ’˜±Jšœžœ¡žœ˜ÕJšœžœA˜bJšœžœC˜cJšœžœ)˜FJšœžœH˜eJšœžœ˜9JšœKžœ˜QJ˜SJ˜J˜J˜—…—Ab¡