* * Last editted by Jim Frandeen, August 29, 1980 1:21 PM Change DoInt to NotifyInterrupt
* * Last editted by AOF, August 11, 1980 11:35 PM
TITLE[IFDC];
* Features of this version:
*
* August 11, 1980 11:37 PM
*
Fixes buffer address overrun when fetching from beyond mapped buffer.
*
Includes OUTPUT instruction interlocks.
*
*
This micro code is to support the IFDC (aka D0FDC, ASD FDC).
*
That controller handles only IBM style format in single density, single sided.
*
The FDC chip on board is an Intel 8271. The drive is an SA800/801.
*
*
This version of microcode supports chained Iocbs that are accessed via a
*
Csb that is located in the IOpage. All Iocbs are to be allocated from the
*
first64K of virtual memory and must be quad-word aligned.
*
*
Each Iocb represents one FDC operation. It will contain at least a single
*
FDC command word to be written to the controller’s LdFDCcmd (FDldCmd) register.
*
It may also contain [0..5) additional register loads that if present will be
*
written to the LdFDCcmd (FDldCmd) register as parameters. All register loading
*
is performed during the command phase.
*
After the required register loading is completed in the command phase,
*
the chip will enter the execution phase which may proceed with
*
CPU DMA simulation required. The execution phase will terminate due to
*
operation being completed or an error.
*
After the command phase, the chip enters the result phase. The
*
microcode will collect the controller status, standard result (determining its
*
pertenance is not a function of the microcode) and the current drive status.
*
All are stored in the Iocb’s ending status field, and an interrupt generated.
*
*
This code buffers data transfers at two (2) bytes and uses PFETCH1/PSTORE1
*
to access main memory at a rate of once every 64 microseconds (250Kbit/sec).
*
*
An idle (logically) device will generate no wakeups. When software wants
*
the microcode to go from an idle state to a processing state, it must first
*
link one or more Iocbs to the CSB, then output LdWakeAllowcmdWakeAllow.
*
That should wake the microcode almost immediately. When the micrcode completes its
*
processing it will reset all wake allow functions.
*
* Parameters and Constants
SET[NotifyPage,0];
ONPAGE[FDPage];
SETTASK[0];
*
* device constants
*
* IFDC Output Address Decodes
SET[FDrstCntrllr,0];* If Odata[00:1] will activate RstAttn,
* which will reset OdataPEF’, OFaultF’
* and IndxWUF’. Odata[15:1] will activate
* CntrllrRst which is equivalent to
* MasterRst
SET[FDwakeAllow,1];* Will load WakeAllowF if Odata[15:1] hi
* IndxWAF if Odata[14:1] high
* FDCcmdWAF if Odata[13:1] high
* EnFMAmodeF if Odata[12:1] high
SET[FDinitStrb,3];* Initialize FDCstrb’
SET[FDldWndw,4];* Loads the HiInitCount with the contents
* of Odata[08:4] and LowInitCount with
* the contents of Odata[12:4]
SET[FDldFnct,5];* Sets the signals FDCread’, FDCdack,
* FDCA1 and FDCA0 from Odata[00:4]
SET[FDldCmd,6];* Loads the output data registers with
* Odata[00:16] and set the signals
* FDCread’, FDCdack, FDCA1 and FDCA0
* from Odata[00:4]
SET[FDldData,7];* Loads the output data
* registers with Odata[00:16].
* IFDC Input Address Decodes
SET[FDrdCntrllrID,0];* Idata[00:16] ← 005403B
SET[FDrdCntrllr,2];* Read controller status into Idata[00:16]
SET[FDrdData,3];* Enables data stored in the input data
* buffer available in Idata[00:16]
* Local constants for micro code use
* Definition of the Floppy Command Status Block (FDcsb.)
* This segment is 4 words long, base is modulo 4, and is in the first 64K
* of the I/O page.
SET[FDcsb.iocb,0];* 16 bit POINTER to current IOCB
* NOTE: Environment.first64K is assumed
SET[FDcsb.notify,3];* notify mask word
* Definition of Floppy I/O Command Block (FDiocb.)
* This block is 16 words long, base is modulo 4.
SET[FDiocb.link0,0];* RELATIVE POINTER to successor IOCB
SET[FDiocb.link1,1];* ...cont...
SET[FDiocb.ba0,4];* Iocb.ba (LONG POINTER)
SET[FDiocb.ba1,5];* ...cont...
SET[FDiocb.bl,6];* buffer length in 16 bit words
SET[FDiocb.result,7];* Iocb.result[0:5] ← Standard Result
* Iocb.result[5:7] ← Drive Status
* Iocb.result[12:4] ← Controller Status
SET[FDiocb.wake,10];* Wake function used during execution phase
* bits [12:4] are loaded into ldWakeAllow
* bits [4:4] are displacement to first load word
* bits [0:4] are the function dispatch
* NOTE: Only permitted values are:
* 0 => nop
* 4 => seekTrack
* 10 => readDMA
* 14 => writeDMA
**********************************************
*
* R- register declarations
*
**********************************************
RV[FDrm.csbInit,0]* Where INIT will store my CSB address
RV[FDrm.saveAPC,1]* register to hold caller’s return word
RV[FDrm.csb0,2];* SA850 CSB address displacement
RV[FDrm.csb1,3];* (...cont...)←000B
RV[FDrm.iocb0,4];* current active Iocb
RV[FDrm.iocb1,5];* (...cont...)←000B
RV[FDrm.notify,7];* NAKED NOTIFY mask word
RV[FDrm.ba0,14];* buffer address (even)
RV[FDrm.ba1,15];* buffer address (odd)
RV[FDrm.bl,16];* length of buffer in words
RV[FDrm.result,17];* ending Status
RV[FDrm.wake,10];* WakeAllow function code
RV[FDrm.load,11];* function to issue to FDC
RV[FDrm.input,12];* word read from chip
RV[FDrm.output,13];* word to write to chip
*Define constants used with FDC
MC[FDa0, 010000];* chip addressing line A0
MC[FDnoWakeAllow,0];* LdWakeAllow ← noWakeAllow
MC[FDcmdWakeAllow,5];* LdWakeAllow ← commandWakeAllow
MC[FDdataWakeAllow,11];* LdWakeAllow ← dataWakeAllow
MC[FDreadResultReg,010000];* CS’←Rd’←0, A1,,A0←1 (read standard result)
MC[FDreadDriveReg0,154];* LdFDCcmd ← read drive status (part 0)
MC[FDreadDriveReg1,100000];* LdFDCcmd ← read drive status (part 1)
MC[FDreadDMA,070000];* ldFDCfnct← to enable DMA read
MC[FDwriteDMA,170000];* ldFDCfnct← to enable DMA write
MC[FDrstAttn,100000];* RstCntrllr←to reset controller faults
MC[FDcntrllrRst,000001];* RstCntrllr←to master reset the controller
MC[FDreadStatus,0];* ldFDCcmd←to read chip status
MC[FDcmdBusy,200];* Chip command is busy
MC[FDcmdFull,100];* Shouldn’t be if loading a parameter?????
MC[FDiReq,010];* Interrupt required (error)
MC[FDresultFull,020];* Chip result register is full
MC[FDparmBusy,040];* Chip parameter register is full
MC[FDparmBusyORCmdFull,140];* Union of the two
MC[FDdelayCount,10];* number of tasks before reading chip data
*
* Define base addresses for dispatch tables and the program base.
*
* IMRESERVE[10,0,230];
* used to offset code in page
SET[FDfunctionType,ADD[LSHIFT[FDpage,10],FDoffset,20]];
* function type dispatch
FDstart:
*~
* This code is to initialize and make ready to accept wakeups.
* This code is executed only once.
*~
AT[FDinit],FDrm.csb1←(ZERO);* clear out MSW of CSB address
T←(FDrm.csbInit);* load displacement of CSB
FDrm.csb0←(T),CALL[FDreturnX];* store CSB address & set TPC
FDidleEP:
*
* This is where we pick up from an idle time wake. We are assured of having at
* least one (1) Iocb chained to the CSB. Current wake function is
cmdWakeAllow.
*
* This wake is already gone, but will come back ~1.5usecs after we TASK and
* persist in doing that until
cmdWakeAllow is turned off.
*
* Fetch the current information from the CSB.
*
PFETCH4[FDrm.csb0,FDrm.iocb0,FDcsb.iocb],CALL[FDreturn];
FDsetUp:
*
* Reset attention on the controller before using.
*
FDrm.output←(FDrstAttn);
OUTPUT[FDrm.output,FDrstCntrllr];
LU←(FDrm.iocb0);* any IOCB?
GOTO[FDstop,ALU=0],FDrm.wake←(FDcmdWakeAllow);
OUTPUT[FDrm.wake,FDwakeAllow];
FDrm.wake←(FDrm.wake),CALL[FDreturn];
*
* Fetch data segment from current Iocb.
*
PFETCH4[FDrm.iocb0,FDrm.ba0,FDiocb.ba0],CALL[FDreturn];
*
* Now fetch the execution phase wake allow word. This word has the command
* type dispatch and the offset within the IOCB for the next word to load in the
* controller’s
ldFDCcmd register. If the offset is zero, no there are no more words
* to load.
*
PFETCH1[FDrm.iocb0,FDrm.wake,FDiocb.wake],CALL[FDreturn];
T←LDF[FDrm.wake,4,4];
FDcmdPhase:
*
* T now contains the displacement within the IOCB for the next word to fetch
* and load into the controller/chip. If it is zero (and the ALU knows that), then
* go to the exection phase. Else fetch the single word into FDrm.load.
*
GOTO[FDexecPhase,ALU=0];* no (more) register loads
PFETCH1[FDrm.iocb0,FDrm.load];
FDtestChip:
*
* See if the chip is capable of accepting the word we are about to OUTPUT to
* it. If this is a write to the command register, then the chip may not be busy
* (cmdBusy#0 => busy). If this is a parameter (A0 will be high) then cmdBusy
* must be high, but parmBusy must be low. (It is not clear, but assumed that CmdFull
* must also be low before writing new parameter.)
*
* Issue command to read the chip’s status
*
FDrm.output←(FDreadStatus),CALL[FDreadChip];
*
* If this is the command, then addressing line A0 will be low, else assume a parm.
*
LU←(FDrm.load) AND (FDa0);
GOTO[.+3,ALU=0],LU←(FDrm.input) AND (FDcmdBusy);
GOTO[FDresultPhase,ALU=0];
LU←(FDrm.input) AND (FDparmBusyORCmdFull);
GOTO[FDtestChip,ALU#0];
OUTPUT[FDrm.load,FDldCmd];
T←(LDF[FDrm.load,4,4]),GOTO[FDcmdPhase];
FDexecPhase:
*
*
Write the WakeAllow specified in the command’s IOCB.
*
Fix the LONG POINTER buffer address.
*
Determine what type of function it was. Iocb.wake[0:4] contains a
*
function dispatch field.
*
OUTPUT[FDrm.wake,FDwakeAllow];
T←(LSH[FDrm.ba1,10]);
FDrm.ba1←(FDrm.ba1)+(T)+1,IOSTROBE;
DISPATCH[FDrm.wake,0,4],FDrm.wake←(FDrm.wake);
DISP[FDresultPhase];
FDresultPhase:
*
* CASE:
Iocb.wake[0:4]=0
*
This command is a NOP (just gets result).
*
* The controller status is a four bit field that will be stored in bit positions 12:4.
*
AT[FDfunctionType,0],INPUT[FDrm.result,FDrdCntrllr];
CALL[FDreturn];
T←FDrm.result←(FDrm.result) AND (17c);
FDstdResult:
*
* An operation has completed that may return a
Standard Result (bits 10:5 are significant).
* That result will be solicited from the chip and posted in
IOCB.eS[0:5].
*
FDrm.output←(FDreadStatus),CALL[FDreadChip];
LU←(FDrm.input) AND (FDresultFull);
GOTO[FDdriveStatus,ALU=0];
FDrm.output←(FDreadResultReg),CALL[FDreadChip];
FDrm.input←(LSH[FDrm.input,12]);
T←(FDrm.input) AND (170000c);
FDrm.result←(FDrm.result) OR (T);
FDdriveStatus:
*
* Now set to read the drive status register by requesting the chip to move it to
* the
result register, then read a standard result.
*
FDrm.output←(FDreadDriveReg0);
FDrm.output←(FDrm.output) OR (FDreadDriveReg1),CALL[FDwriteCmd];
NOP;
FDimmediateWait:
*
* wait for "immediate" results from the chip to be placed in the result register.
*
FDrm.output←(FDreadStatus),CALL[FDreadChip];
LU←(FDrm.input) AND (FDcmdBusy);
GOTO[FDimmediateWait,ALU#0];
FDimmediateRdy:
*
* Save the ending status in T as a 5 bit field. Prepare to merge it into the
* ending status in bit positions 0:5. Get the drive status and treat it as
* as a 7 bit field that will be merged into the ending status in bits 5:7.
*
FDrm.output←(FDreadResultReg);
CALL[FDreadChip];
FDrm.input←(FDrm.input) AND (177c);
T←(LSH[FDrm.input,4]);
FDrm.result←(FDrm.result) OR (T);
FDstoreStatus:
*
* Store the function ending status (incl ba, bl, and eS). Then chain to the
* next IOCB in the list. Notify the Mesa world that the operation has completed.
*
PSTORE4[FDrm.iocb0,FDrm.ba0,FDiocb.ba0];
FDrm.ba0←(FDrm.ba0);
PFETCH1[FDrm.iocb0,FDrm.iocb0,FDiocb.link0];
LU←(FDrm.iocb0);
PSTORE1[FDrm.csb0,FDrm.iocb0,FDcsb.iocb];
LOADPAGEEXTERNAL[NotifyInterruptPage];
T←(FDrm.notify),CALLEXTERNAL[NotifyInterruptLoc];*Mask in T, allow task
*
* Go back to beginning of loop to test for more IOCBs top process.
*
GOTO[FDsetUp];
FDstop:
*
* Time to idle the controller. Set
noWakeAllow, then task waiting for the Head to
* queue something up and reset to
cmdWakeAllow.
* NOTE: An idle controller generates no wakes.
*
FDrm.output←(FDnoWakeAllow);
OUTPUT[FDrm.output,FDwakeAllow],CALL[FDreturn];
GOTO[FDidleEP];
FDseekTrack:
*
* CASE:
Iocb.wake[0:4]=10
*
Seek specified track. This function will be requested by the Head only,
*
never by the actual client. It will most likely be a seek track 0. The operation
*
will complete with an INT wake, which has been set be the IOCB code. All
*
we have to do is task and wait for it to happen.
*
AT[FDfunctionType,6],FDrm.output←(FDcmdWakeAllow);
CALL[FDreturn];
OUTPUT[FDrm.output,FDwakeAllow];
FDrm.output←(FDrm.output),GOTO[FDresultPhase];
FDdmaRead:
*
* CASE:
Iocb.wake[0:4]=10
*
Read data from diskette (DMA mode). Impies multiple data wakes.
*
Also implies that we no longer have cmdWakeAllow. We must set the DMA
*
mode and fetch the buffer address and length from main memory before the
*
first word is assembled in the data regs.
*
AT[FDfunctionType,10], FDrm.output←(FDreadDMA);
OUTPUT[FDrm.output,FDldFnct],CALL[FDreturn];
FDread:
*
* Loop here until we get IOATTEN which indicates the operation errored or completed.
*
NOP;* wait for IOATTEN to solidify
GOTO[FDfinishDMA,IOATTEN],INPUT[FDrm.input, FDrdData];

*
* store a single word (16 bits) if the client’s buffer is long enough, then update
* the address. The wake has already been cleared.
* NOTE: DMA buffer length error iff final length # 0.
* Don’t store work if buffer length is <= zero.
*
LU←(FDrm.bl)-1;
SKIP[ALU>=0],LU←(FDrm.input);
GOTO[FDupdateBA];
PSTORE1[FDrm.ba0,FDrm.input,0],GOTO[FDupdateBA];
FDdmaWrite:
*
* CASE:
Iocb.wake[0:4]=14
*
Write data to diskette (DMA mode). Impies multiple data wakes.
*
Also implies that we no longer have cmdWakeAllow. We must set the DMA
*
mode and fetch the buffer address and length from main memory before the
*
first word is assembled in the data regs.
*
AT[FDfunctionType,14],FDrm.output←(FDwriteDMA);
OUTPUT[FDrm.output,FDldFnct],CALL[FDwrite];
FDwrite:
*
* Loop here until we get IOATTEN which indicates the operation errored or completed.
* Fetch data from memory only if buffer still exists.
* NOTE: DMA buffer length error iff final length # 0.
* Don’t fetch from memory if buffer legth is <= 0.
*
LU←(FDrm.bl)-1;
SKIP[ALU<0];
PFETCH1[FDrm.ba0,FDrm.output,0];
SKIP[NOATTEN];
FDrm.bl←(FDrm.bl)+1,GOTO[FDfinishDMA];
OUTPUT[FDrm.output,FDldData],GOTO[FDupdateBA];
FDupdateBA:
*
* Update the buffer address by one word. Check for overflow on the 16 bit
* even register of the address and do what’s necessary if it overflows.
* NOTE: The return will lose control until the next
dataWake.
*
FDrm.bl←(FDrm.bl)-1;
FDrm.ba0←(FDrm.ba0)+1;
SKIP[NOOVF];
FDrm.ba1←(FDrm.ba1)+(400c)+1;
FDrm.output←(FDrm.output),RETURN;
FDfinishDMA:
*
* Operation is complete (i.e., we have IOattention). Set command wake allow and
* go off to process the ending status.
*
FDrm.wake←(FDcmdWakeAllow),GOTO[FDwriteWake];
FDwriteWake:
*
* This is the end of a function. (FDrm.wake) contains a new
WakeAllow to be
* written to
LdWakeAllow. After that, post the ending status and ....
*
OUTPUT[FDrm.wake,FDwakeAllow];
FDrm.wake←(FDrm.wake),CALL[FDreturn];
GOTO[FDresultPhase],IOSTROBE;
FDwriteCmd:
*
* Entry here assumes value is already in FDrm.output
*
OUTPUT[FDrm.output,FDldCmd],GOTO[FDreturn];
FDreadChip:
*
* Use command stored in FDrm.output to write to the
ldFDCcmd register,
* then read the result back in via the
rdFDCdata register and store the
* result in FDrm.input.
*
* Delay appropriately.
*
FDrm.input←(T),USECTASK;* save current T
T←APC&APCTASK;* and save current APC in T
FDrm.saveAPC←(T);* save in r-reg
OUTPUT[FDrm.output,FDldCmd];
T←(FDrm.input);* restore caller’s T
FDrm.input←(FDdelayCount),CALL[.+1];
FDrm.output←(FDrm.output);
GOTO[FDreturnX,R>=0],FDrm.input←(FDrm.input)-1;
NOP;
INPUT[FDrm.input,FDrdData];
APC&APCTASK←(FDrm.saveAPC),GOTO[FDreturnX];
FDreturn:
FDrm.output←(FDrm.output);* register interlock
FDreturnX:
RETURN;* tasking return
:END[IFDC];