+/* Floppy.C Floppy Disk Device Driver for MMURTL */\r
+\r
+/*\r
+ MMURTL Operating System Source Code\r
+ Copyright 1991,1992,1993,1994 Richard A. Burgess\r
+ ALL RIGHTS RESERVED Version 1.0\r
+*/\r
+\r
+#define U32 unsigned long\r
+#define S32 long\r
+#define U16 unsigned int\r
+#define S16 int\r
+#define U8 unsigned char\r
+#define S8 char\r
+\r
+/* MMURTL OS PROTOTYPES */\r
+\r
+extern far SpawnTask(S8 *pEntry,\r
+ U32 dPriority,\r
+ U32 fDebug,\r
+ S8 *pStack,\r
+ U32 fOSCode);\r
+\r
+extern far U32 AllocExch(U32 *pExchRet);\r
+\r
+extern far U32 AllocDMAPage(U32 nPages, U8 **ppMemRet U32 *pPhyMemRet);\r
+\r
+extern far U32 InitDevDr(U32 dDevNum,\r
+ S8 *pDCBs,\r
+ U32 nDevices,\r
+ U32 dfReplace);\r
+\r
+extern far DmaSetUp(S8 *pPhyMem,\r
+ U32 sdMem, /* size */\r
+ U32 dChannel, /* channel 2 floppy */\r
+ U32 dType, /* 0=Verfify, 1=IN, 2=OUT */\r
+ U32 dMode); /* FDC uses 1 (single cycle) */\r
+\r
+extern far U32 UnMaskIRQ(U32 IRQNum);\r
+extern far U32 MaskIRQ(U32 IRQNum);\r
+extern far U32 SetIRQVector(U32 IRQNum, S8 *pIRQ);\r
+extern far U32 EndOfIRQ(U32 IRQNum);\r
+extern far U32 SendMsg(U32 Exch, U32 msg1, U32 msg2);\r
+extern far U32 ISendMsg(U32 Exch, U32 msg1, U32 msg2);\r
+extern far U32 WaitMsg(U32 Exch, S8 *pMsgRet);\r
+extern far U32 CheckMsg(U32 Exch, S8 *pMsgRet);\r
+extern far U32 GetTimerTick(U32 *pTickRet);\r
+extern far U32 Alarm(U32 Exch, U32 count);\r
+extern far U32 KillAlarm(U32 Exch);\r
+extern far U32 Sleep(U32 count);\r
+extern far void MicroDelay(U32 us15count);\r
+extern far void OutByte(U8 Byte, U16 wPort);\r
+extern far void OutWord(U16 Word, U16 wPort);\r
+extern far U8 InByte(U16 wPort);\r
+extern far U16 InWord(U16 wPort);\r
+extern far U8 ReadCMOS(U16 Address);\r
+extern far void CopyData(U8 *pSource, U8 *pDestination, U32 dBytes);\r
+\r
+\r
+/* THE FLOPPY INTERRUPT FUNCTION PROTOTYPE */\r
+\r
+static void interrupt fdisk_isr(void);\r
+\r
+/* PROTOTYPE FOR Floppy motor control task and associated stuff */\r
+\r
+static void fdmotor_task(void);\r
+static void fd_select(U32 drive);\r
+static void fd_motoroff(U32 drive);\r
+\r
+/* THE REST OF THE PROTOTYPES */\r
+\r
+U32 fdisk_setup(void);\r
+static U32 RdWrtVerf(U32 op);\r
+static U32 format_track(void);\r
+static U32 Set_Media(U32 drive, U32 type);\r
+static U32 FDC_reset(void);\r
+static U8 cmos_type (U8 drive_nr);\r
+static U32 send_fdc(U8 parm);\r
+static U8 GetParm(U8 index);\r
+static U32 wait_int (void);\r
+static U32 seek(void);\r
+static U32 recal(void);\r
+static U32 read_data(U8 *pDataRet);\r
+static U32 results(U32 expect);\r
+static void purge_fdc (void);\r
+static void wait_for_head(void);\r
+static U32 med_change(void);\r
+static U32 get_fdc_status(void);\r
+\r
+/* The following 3 calls are required in every MMURTL device driver */\r
+\r
+static U32 dev_op(U32 dDevice,\r
+ U32 dOpNum,\r
+ U32 dLBA,\r
+ U32 dnBlocks,\r
+ S8 *pData);\r
+\r
+static U32 dev_stat(U32 dDevice,\r
+ S8 * pStatRet,\r
+ U32 dStatusMax,\r
+ U32 *pdSatusRet);\r
+\r
+static U32 dev_init(U32 dDevNum,\r
+ S8 *pInitData,\r
+ U32 sdInitData);\r
+\r
+/* Near External for troubleshooting */\r
+\r
+extern long xprintf(char *fmt, ...);\r
+\r
+/* LOCAL DEFINITIONS */\r
+\r
+#define ok 0\r
+#define TRUE 1\r
+#define FALSE 0\r
+\r
+#define RATE_500 0x00\r
+#define RATE_300 0x01\r
+#define RATE_250 0x02\r
+#define RATE_1000 0x03\r
+#define INT_FLAG 0x80\r
+\r
+/* Error Codes to return */\r
+\r
+#define ErcNotInstalled 504\r
+#define ErcAddrMark 602\r
+#define ErcReadOnly 603\r
+#define ErcSectNotFound 604\r
+#define ErcNewMedia 605\r
+#define ErcNotMounted 606\r
+#define ErcCRC 607\r
+#define ErcBadFDC 608\r
+#define ErcBadSeek 609\r
+#define ErcFDCTimeOut 610\r
+#define ErcOverRun 611\r
+#define ErcBadLBA 612\r
+#define ErcDriveType 613\r
+#define ErcBadOp 614\r
+#define ErcBadRecal 615\r
+#define ErcSendFDC 616\r
+#define ErcResults 617\r
+#define ErcBadCmd 618\r
+#define ErcReadyLine 619\r
+\r
+/* Commands accepted by driver */\r
+\r
+#define CmdNull 0\r
+#define CmdRead 1\r
+#define CmdWrite 2\r
+#define CmdVerify 3\r
+#define CmdFmtBlk 4\r
+#define CmdFmtTrk 5\r
+#define CmdSeekTrk 6\r
+\r
+/* FDC port definitions */\r
+\r
+#define DOR_PORT 0x3f2\r
+#define MSR_PORT 0x3f4\r
+#define DATA_PORT 0x3f5\r
+#define DIR_PORT 0x3f7\r
+#define DRR_PORT 0x3f7\r
+\r
+/* FDC Return Status bit definitions */\r
+\r
+#define BUSY 0x10 /* was BIT4 */\r
+#define DSKCHANGE_BIT 0x80\r
+#define BIT7 0x80\r
+#define BIT6 0x40\r
+#define BIT5 0x20\r
+#define BIT4 0x10\r
+#define BIT3 0x08\r
+#define BIT2 0x04\r
+#define BIT1 0x02\r
+#define BIT0 0x01\r
+\r
+#define RQM 0x80\r
+#define DIO 0x40\r
+\r
+/* FDC commands */\r
+\r
+#define FDC_READ 0xe6\r
+#define FDC_WRITE 0xc5\r
+#define FDC_FORMAT 0x4d\r
+\r
+/* FDC DOR register bits */\r
+\r
+#define FD_MOTOR0 0x10\r
+#define FD_MOTOR1 0x20\r
+#define FD_INTS 0x08\r
+#define FD_RESET 0x04\r
+#define FD_DRV1SEL 0x01\r
+#define FD_MOTMASK 0xf0 /* mask to see motor bits */\r
+\r
+/* L O C A L C O N S T A N T S */\r
+\r
+/* The drive table contains parameters for each disk\r
+ type. The values are:\r
+ 0 - FDC SPECIFY Command byte 1\r
+ 1 - FDC SPECIFY Command byte 2\r
+ 2 - Unused\r
+ 3 - Bytes per Sector (coded 0=128, 1=256, 2=512, 3=1024);\r
+ 4 - number of sectors per track (Last sector)\r
+ 5 - Intersector Gap Size\r
+ 6 - Data Length (FFh = 512)\r
+ 7 - GAP 3 for Format Command\r
+ 8 - Fill Byte for Format command\r
+ 9 - Head settle time in milliseconds\r
+ 10 - Motor start time in milliseconds/10 (mult value by 10 to use)\r
+ 11 - max cylinder index (number of cyls - 1)\r
+ 12 - Xfer rate Command\r
+ 13 - Unused\r
+ 14 - Double Step Flag (e.g., 360K in a 1.2Mb drive)\r
+ 15 - Unused\r
+*/\r
+\r
+\r
+static U8 fdisk_table[5][16]= {\r
+\r
+ /* 0 = NO DRIVE - first two params set to allow FDC_reset */\r
+ {0x0af,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0},\r
+\r
+ /* 1 = 360 kb drive */\r
+ {0x0af, 2, 0, 2, 9, 0x2a, -1, 0x50, 0x0f6, 15, 8, 39, RATE_250, 0, 0, 0},\r
+\r
+ /* 2 = 1.2 mb drive */\r
+ {0xaf, 2, 0, 2, 15, 0x1b, -1, 0x54, 0x0f6, 15, 8, 79, RATE_500, 0, 0, 0},\r
+\r
+ /* 3 = 720 type drive */\r
+ {0x0af, 2, 0, 2, 9, 0x2a, -1, 0x50, 0x0f6, 15, 8, 79, RATE_250, 0, 0, 0},\r
+\r
+ /* 4 = 1.44 mb drive */\r
+ {0xaf, 2, 0, 2, 18, 0x1b, -1, 0x6c, 0x0f6, 15, 8, 79, RATE_500, 0, 0, 0},\r
+\r
+};\r
+\r
+static U32 MotorStk[100]; /* 400 byte stack for motor task */\r
+static U32 MotorStkTop;\r
+\r
+static U8 fd_fdc_command;\r
+static U8 fd_drive;\r
+static U8 fd_nr_sectors;\r
+static U8 fd_head;\r
+static U8 fd_sector;\r
+static U8 fd_track;\r
+static U8 seek_status;\r
+static U8 fwrite;\r
+static S8 *fd_pData;\r
+\r
+/* current fdisk_table[x] for drive 0 & 1 */\r
+\r
+static U8 type0\r
+static U8 type1\r
+\r
+/* Record for 64 byte status and init record */\r
+\r
+#define sStatus 64\r
+\r
+static struct statstruct{\r
+ U32 erc;\r
+ U32 blocks_done;\r
+ U32 BlocksMax;\r
+ U8 fNewMedia;\r
+ U8 type_now; /* current fdisk_table for drive selected */\r
+ U8 resvd1[2]; /* padding for DWord align */\r
+ U32 nCyl; /* total physical cylinders */\r
+ U32 nHead; /* total heads on device */\r
+ U32 nSectors; /* Sectors per track */\r
+ U32 nBPS; /* Number of bytes per sect */\r
+ U8 params[16]; /* begin device specific fields */\r
+ U8 STATUS[8]; /* status returned from FDC (for user status) */\r
+ U32 resvd3;\r
+ U32 resvd4; /* 64 bytes total */\r
+ };\r
+\r
+static struct statstruct fdstatus;\r
+static struct statstruct FDStatTmp;\r
+\r
+static U8 FDC_STATUS[8]; /* status returned from FDC */\r
+static U8 LAST_TRACK[3]; /* holds last track number */\r
+\r
+static struct dcbtype {\r
+ S8 Name[12];\r
+ S8 sbName;\r
+ S8 type;\r
+ S16 nBPB;\r
+ U32 last_erc;\r
+ U32 nBlocks;\r
+ S8 *pDevOp;\r
+ S8 *pDevInit;\r
+ S8 *pDevSt;\r
+ S8 fDevReent;\r
+ S8 fSingleUser;\r
+ S16 wJob;\r
+ U32 OS1;\r
+ U32 OS2;\r
+ U32 OS3;\r
+ U32 OS4;\r
+ U32 OS5;\r
+ U32 OS6;\r
+ };\r
+\r
+static struct dcbtype fdcb[2]; /* two floppy device control blcocks */\r
+\r
+\r
+/* Exch, msgs space, and vars for FD Motor task */\r
+static U8 dor_crnt; /* last value sent to DOR port */\r
+\r
+static U8 motor0_want; /* desired motor0 state, TRUE = want ON */\r
+static U8 motor1_want; /* desired motor1 state, TRUE = want ON */\r
+static U32 fd_tick; /* Set to tick everytime we select a floppy */\r
+static U32 fd_newtick; /* used to check tick time */\r
+\r
+/* Exch and msgs space for FD ISR */\r
+static U32 fd_exch;\r
+static U32 fd_msg[2];\r
+\r
+static U32 rgSectorMax[10] = {0, 720, 2400, 1440, 2880,\r
+ 0, 0, 0, 0, 0}; /* set max sectors */\r
+\r
+static U8 *sectbuf; /* sector buffer, 1 Page with AllocDMAPage */\r
+static U32 physectbuf; /* physical address of DMA buffer */\r
+\r
+/*======================================================*/\r
+/*=================== START OF CODE ====================*/\r
+/*======================================================*/\r
+\r
+static void enable_ints(void)\r
+{\r
+;\r
+#asm\r
+ STI\r
+#endasm\r
+}\r
+\r
+static void disable_ints(void)\r
+{\r
+;\r
+#asm\r
+ CLI\r
+#endasm\r
+}\r
+\r
+\r
+/********************************************************************\r
+ This small function becomes a thread (task) to control the motor.\r
+ The floppy motors are controlled serarately from the rest\r
+ of the floppy drive electronics. A single port controls both\r
+ floppies. After a floppy is used you don't want to shut it right\r
+ off or you can forget about any dreams of decent throughput.\r
+ This wakes up every 3 seconds and checks to see if there was\r
+ any floppy activity in the past 3 seconds. If not, we shut off\r
+ the motor(s).\r
+**********************************************************************/\r
+\r
+static void fdmotor_task(void)\r
+{\r
+ enable_ints();\r
+\r
+MotorLoop:\r
+\r
+ Sleep(300); /* 3 seconds */\r
+\r
+ GetTimerTick(&fd_newtick);\r
+\r
+ if ((fd_newtick - fd_tick) > 300) { /* not used in last 3 seconds */\r
+\r
+ if ((!motor0_want) && (dor_crnt & FD_MOTOR0))\r
+ { /* They want 0 off */\r
+ disable_ints();\r
+ dor_crnt &= ~FD_MOTOR0;\r
+ OutByte( dor_crnt, DOR_PORT);\r
+ enable_ints();\r
+ }\r
+\r
+ if ((!motor1_want) && (dor_crnt & FD_MOTOR1)) { /* They want 1 off */\r
+ disable_ints();\r
+ dor_crnt &= ~FD_MOTOR1;\r
+ OutByte( dor_crnt, DOR_PORT);\r
+ enable_ints();\r
+ }\r
+ }\r
+ goto MotorLoop;\r
+}\r
+\r
+/*******************************************\r
+ Set desired motor status flags and drive\r
+ select bit. If drive select bit has changed\r
+ this sends the current DOR value to select\r
+ the correct drive. If the motor bit we wanted\r
+ isn't on, we will also send the command.\r
+ We DO NOT mess with the other motor bit.\r
+*******************************************/\r
+\r
+static void fd_select(U32 drive)\r
+{\r
+U8 fsend;\r
+U8 fdelay;\r
+\r
+ GetTimerTick(&fd_tick); /* update last time selected */\r
+\r
+ fsend = FALSE;\r
+ fdelay = FALSE;\r
+\r
+ if (drive) { /* drive 1 */\r
+ if (!(dor_crnt & 0x01)) {\r
+ fsend = TRUE;\r
+ dor_crnt |= 0x01; /* select 1 */\r
+ }\r
+ if (!(dor_crnt & FD_MOTOR1)) {\r
+ fsend = TRUE;\r
+ fdelay = TRUE;\r
+ dor_crnt |= FD_MOTOR1; /* motor 1 on */\r
+ }\r
+ }\r
+ else {\r
+ if (dor_crnt & 0x01) {\r
+ fsend = TRUE;\r
+ dor_crnt &= 0xFE; /* select 0 (turn 1 off) */\r
+ }\r
+ if (!(dor_crnt & FD_MOTOR0)) {\r
+ fsend = TRUE;\r
+ fdelay = TRUE;\r
+ dor_crnt |= FD_MOTOR0; /* motor 0 on */\r
+ }\r
+ }\r
+\r
+ if (fsend) {\r
+\r
+ disable_ints();\r
+\r
+ OutByte( dor_crnt, DOR_PORT);\r
+\r
+ enable_ints();\r
+\r
+ if (fdelay)\r
+ Sleep(33); /* delay 1/3th second if not already on! */\r
+ }\r
+\r
+}\r
+\r
+\r
+/*******************************************\r
+ Sets motor_want bit for specified motor.\r
+*******************************************/\r
+\r
+static void fd_motoroff(U32 motor)\r
+{\r
+/* Set what we want so motor task will know */\r
+\r
+if (motor)\r
+ motor1_want = FALSE;\r
+else\r
+ motor0_want = FALSE;\r
+}\r
+\r
+/*********************************************************\r
+ This is called ONCE to initialize the Driver.\r
+*********************************************************/\r
+\r
+U32 fdisk_setup(void)\r
+{\r
+U32 erc;\r
+\r
+fdcb[0].Name[0] = 'F';\r
+fdcb[0].Name[1] = 'D';\r
+fdcb[0].Name[2] = '0';\r
+fdcb[0].sbName = 3;\r
+fdcb[0].type = 1;\r
+fdcb[0].fDevReent = 0; /* not reentrant */\r
+fdcb[0].fSingleUser = 0;\r
+\r
+fdcb[1].Name[0] = 'F';\r
+fdcb[1].Name[1] = 'D';\r
+fdcb[1].Name[2] = '1';\r
+fdcb[1].sbName = 3;\r
+fdcb[1].type = 1; /* this is set set to zero if not installed */\r
+fdcb[1].fDevReent = 0; /* not reentrant */\r
+fdcb[1].fSingleUser = 0;\r
+\r
+ dor_crnt |= (FD_INTS | FD_RESET); /* set ints & reset bits high */\r
+ motor0_want = FALSE;\r
+ motor1_want = FALSE;\r
+ seek_status = 0; /* clear seek status */\r
+ fdstatus.erc = 0; /* clear global disk error */\r
+\r
+ erc = AllocExch(&fd_exch); /* allocates exchanges for messaging */\r
+ if (erc)\r
+ return(erc);\r
+ erc = AllocDMAPage(1, §buf, &physectbuf);\r
+ if (erc)\r
+ return(erc);\r
+\r
+/* Used to test DMA memory call\r
+ xprintf("DMA Address: %08x, DMA Physical : %08x\r\n", sectbuf, physectbuf);\r
+ Sleep(200);\r
+*/\r
+\r
+ /* Set up the motor task */\r
+\r
+ SpawnTask( &fdmotor_task, 18, 0, &MotorStkTop, 1);\r
+\r
+ SetIRQVector(6, &fdisk_isr);\r
+ UnMaskIRQ(6); /* unmask IRQ 6 */\r
+\r
+ type0 = cmos_type(0);\r
+ if (type0)\r
+ {\r
+ fdcb[0].nBPB = 512;\r
+ fdcb[0].nBlocks = rgSectorMax[type0];\r
+ }\r
+\r
+ type1 = cmos_type(1);\r
+ if (type1)\r
+ {\r
+ fdcb[1].nBPB = 512;\r
+ fdcb[1].nBlocks = rgSectorMax[type1];\r
+ }\r
+\r
+/* reset the FDC */\r
+\r
+ if (fdstatus.erc= FDC_reset())\r
+ return (fdstatus.erc);\r
+\r
+/* now we attempt to select and recal both drives */\r
+\r
+ if (type0) {\r
+ fd_drive = 0; /* select drive 0 */\r
+ fd_select(0);\r
+ if (erc = recal())\r
+ {\r
+ erc = recal(); /* try twice */\r
+ }\r
+ fd_motoroff(0);\r
+ if (erc) return (fdstatus.erc = erc);\r
+ }\r
+\r
+ if (type1) {\r
+ fd_drive = 1; /* select drive 1 */\r
+ fd_select(1);\r
+ if (erc = recal())\r
+ erc = recal(); /* try twice */\r
+ if (erc) fdcb[1].type = 0;\r
+ fd_motoroff(1);\r
+ }\r
+\r
+ fdcb[0].pDevOp = &dev_op;\r
+ fdcb[0].pDevInit = &dev_init;\r
+ fdcb[0].pDevSt = &dev_stat;\r
+\r
+ fdcb[1].pDevOp = &dev_op;\r
+ fdcb[1].pDevInit = &dev_init;\r
+ fdcb[1].pDevSt = &dev_stat;\r
+\r
+ return(erc = InitDevDr(10, &fdcb, 2, 1));\r
+\r
+}\r
+\r
+/************************************************************\r
+ Reset the disk system\r
+*************************************************************/\r
+\r
+static U32 FDC_reset(void)\r
+{\r
+ U32 erc;\r
+\r
+ seek_status = 0; /* Set to Recal on next seek */\r
+\r
+ disable_ints();\r
+\r
+ OutByte(0x08, DOR_PORT); /* Drop reset signal on disk controller */\r
+ MicroDelay(75); /* Wait 1ms */\r
+ OutByte(0x0C, DOR_PORT); /* Raise reset line on disk controller */\r
+\r
+ enable_ints();\r
+\r
+ /* wait for interrupt and return if error (timeout...?) */\r
+\r
+ if (erc = wait_int())\r
+ return(erc);\r
+\r
+ /* Send sense command to current drive to see if reset took.\r
+ If this is the first system reset it defaults to drive 0,\r
+ Head 0.\r
+ */\r
+\r
+\r
+ if (erc = send_fdc(8)) return(erc);\r
+\r
+ /* check results */\r
+\r
+ if (erc = results(2)) return(erc);\r
+\r
+ if (((FDC_STATUS[0] & 0xc0) == 0xc0 ) ||\r
+ ((FDC_STATUS[0] & 0xc0) == 0)) {\r
+\r
+ /* Send Specify command to controller */\r
+ send_fdc(3);\r
+ send_fdc(GetParm(0));\r
+ send_fdc(GetParm(1));\r
+ return(ok);\r
+ }\r
+ return(ErcBadFDC);\r
+}\r
+\r
+/*************************************************************\r
+ The ISR is very simple.\r
+ It just waits for an interrupt then sends a message to the\r
+ exchange where the FD Driver will be waiting.\r
+ The call that caused an interrupt to be generated reads the status\r
+ codes from the controller to determine if the last status was OK.\r
+****************************************************************/\r
+static void interrupt fdisk_isr(void)\r
+{\r
+ ISendMsg(fd_exch, 0xfffffffd, 0xfffffffd);\r
+ EndOfIRQ(6);\r
+}\r
+\r
+/*=========================================================\r
+Return the drive type for a specified drive.\r
+---------------------------------------------------------*/\r
+\r
+static U8 cmos_type (U8 drive_nr)\r
+{\r
+ U8 drive_type;\r
+ drive_type = 0;\r
+ if (drive_nr)\r
+ drive_type = ReadCMOS(0x10) & 0x0f;\r
+ else\r
+ drive_type = (ReadCMOS(0x10) >> 4) & 0x0f;\r
+ return(drive_type);\r
+}\r
+\r
+/*========================================================\r
+Send a byte to the FDC.\r
+--------------------------------------------------------*/\r
+\r
+static U32 send_fdc(U8 parm)\r
+{\r
+U32 i;\r
+U8 b, bjunk;\r
+\r
+i = 100; /* try 100 times to send FDC command */\r
+do\r
+{\r
+ b = InByte(MSR_PORT); /* Get I/O status byte */\r
+ MicroDelay(100); /* 1.5 milliseconds between tries */\r
+ if (b & RQM) {\r
+ if (b & DIO) { /* He has something to send us... */\r
+ bjunk = InByte(DATA_PORT); /* Eat it! */\r
+ MicroDelay(100); /* 1.5 milliseconds between I/O */\r
+ }\r
+ else { /* OK to send to him */\r
+ OutByte(parm,DATA_PORT);\r
+ MicroDelay(100); /* 1.5 milliseconds between I/O */\r
+ return (ok); /* Sent it OK */\r
+ }\r
+ }\r
+ }\r
+ while (i--);\r
+ return (ErcSendFDC); /* return ERROR */\r
+}\r
+\r
+/*==========================================================\r
+Get the indexed value from the disk parameter table.\r
+-------------------------------------------------------*/\r
+\r
+static U8 GetParm(U8 index)\r
+{\r
+ return( fdisk_table [fdstatus.type_now][index]);\r
+}\r
+\r
+\r
+/******************************************\r
+Wait for the hardware interrupt to occur.\r
+Time-out and return if no interrupt.\r
+********************************************/\r
+\r
+static U32 wait_int(void)\r
+{\r
+U32 erc;\r
+\r
+/* Set alarm for 3 seconds */\r
+erc = Alarm(fd_exch, 300);\r
+\r
+erc = WaitMsg(fd_exch, fd_msg); /* Wait for message from ISR or Alarm */\r
+if (erc) {\r
+ KillAlarm(fd_exch);\r
+ return(erc); /* BAD NEWS FROM KERNEL!!! */\r
+ }\r
+\r
+if (fd_msg[0] != 0xfffffffd)\r
+{\r
+ return(fdstatus.erc = ErcFDCTimeOut);\r
+}\r
+else {\r
+ KillAlarm(fd_exch);\r
+ return(fdstatus.erc = ok);\r
+ }\r
+}\r
+\r
+\r
+/*******************************\r
+ Recalibrate the drive.\r
+********************************/\r
+\r
+static U32 recal(void)\r
+{\r
+U32 erc;\r
+\r
+ erc = send_fdc(7); /* recal command (2 bytes) */\r
+ if (!erc) erc = send_fdc(fd_drive); /* second byte is drive */\r
+\r
+ /* wait for int on recal */\r
+ if (!erc) erc = wait_int();\r
+ if (!erc) erc = send_fdc(8); /* Sense command */\r
+ if (!erc) erc = results(2); /* expect 2 bytes */\r
+ if (!erc) {\r
+ if (FDC_STATUS[0] & 0x20) /* Check seek-Ok bit */\r
+ {\r
+ if (FDC_STATUS[1]) return(ErcBadRecal); /* Was it track 0? */\r
+ return(ok);\r
+ }\r
+ else return (ErcBadSeek); /* Seek bit NOT set */\r
+ }\r
+ return(erc);\r
+}\r
+/*******************************************************\r
+ Move the head to the selected track.\r
+*********************************************************/\r
+\r
+static U32 seek(void)\r
+{\r
+U32 erc;\r
+\r
+ if ((seek_status & (1 << fd_drive)) == 0) /* need recal */\r
+ {\r
+\r
+ /* try 2 attemps at recalibrate, then error out */\r
+ if (recal())\r
+ if (erc = recal()) return (erc); /* try again */\r
+\r
+ seek_status |= (1 << fd_drive); /* recal was done */\r
+\r
+ LAST_TRACK[fd_drive] = 0; /* clear track number */\r
+\r
+ /* if we want track zero, then just wait for head and exit */\r
+\r
+ if (fd_track == 0) {\r
+ wait_for_head();\r
+ return (ok);\r
+ }\r
+ }\r
+\r
+ if (fdisk_table[fdstatus.type_now][14] != 0)\r
+ fd_track *= 2;\r
+ if (LAST_TRACK[fd_drive] == fd_track) /* already there */\r
+ return(ok);\r
+\r
+ /* update new position */\r
+ LAST_TRACK[fd_drive] = fd_track;\r
+\r
+ erc = send_fdc(0x0f); /*Seek Cmd */\r
+ if (!erc) erc = send_fdc((fd_head << 2) | fd_drive);\r
+ if (!erc) erc = send_fdc(fd_track);\r
+\r
+ /* wait for int on seek command */\r
+ if (!erc) erc = wait_int();\r
+ if (!erc) erc = send_fdc(8); /* Sense command */\r
+ if (!erc) erc = results(2); /* expect 2 bytes */\r
+ if (!erc)\r
+ if (!(FDC_STATUS[0] & 0x20)) /* Look for seek-Ok bit */\r
+ {\r
+ seek_status &= ~(1 << fd_drive); /* needs recal! */\r
+ erc = ErcBadSeek;\r
+ return (erc);\r
+ }\r
+ if (!erc)\r
+ wait_for_head();\r
+ return (ok);\r
+}\r
+\r
+/*=======================================================\r
+Read a single byte from the data port (for status).\r
+Returns TRUE if it got one.\r
+--------------------------------------------------------*/\r
+\r
+static U32 read_data(U8 *pDataRet)\r
+{\r
+U16 tries;\r
+U8 status;\r
+\r
+ /* try 100 times while Busy */\r
+\r
+ for (tries=0; tries<1000; tries++) {\r
+ status = InByte(MSR_PORT);\r
+ MicroDelay(100);\r
+ if (status & RQM) { /* RQM set */\r
+ if (status & DIO) { /* Direction IN if set */\r
+ *pDataRet = InByte(DATA_PORT); /* Get the data byte */\r
+ MicroDelay(100); /* digest it...*/\r
+ return(TRUE);\r
+ }\r
+ }\r
+ }\r
+ return(FALSE);\r
+}\r
+\r
+\r
+/*=======================================================\r
+Read anything from the controller following an interrupt.\r
+This may include up to seven bytes of status.\r
+--------------------------------------------------------*/\r
+\r
+static U32 results(U32 expect)\r
+{\r
+unsigned indx;\r
+U8 status;\r
+\r
+ indx = 0;\r
+ while (indx < expect) {\r
+ if (read_data(&status)) {\r
+ FDC_STATUS[indx++] = status; /* save status */\r
+ MicroDelay(100);\r
+ }\r
+ else return(ErcResults);\r
+ }\r
+ return(0);\r
+}\r
+\r
+\r
+\r
+/****************************************************\r
+ Purge the FDC of any status it is waiting to send.\r
+*****************************************************/\r
+\r
+static void purge_fdc (void)\r
+{\r
+ U8 b;\r
+\r
+ do {\r
+ b = InByte(MSR_PORT);\r
+ if (b & RQM) {\r
+ if (b & DIO) {\r
+ InByte(DATA_PORT); /* eat the byte */\r
+ MicroDelay(100); /* Breath (1.5ms) */\r
+ }\r
+ else return;\r
+ }\r
+ else return;\r
+ }\r
+ while (1);\r
+}\r
+\r
+/*======================================================\r
+Wait for the head to settle after a seek for write.\r
+MicroDelay is in 15 us increments, while settle value\r
+is in milliseconds, so we turn millies into micros and\r
+divide by 15 to get value for MicroDelay call.\r
+-------------------------------------------------------*/\r
+\r
+static void wait_for_head(void)\r
+{\r
+U32 wait;\r
+\r
+if (fwrite) {\r
+ wait = GetParm(9); /* Head settle for write in milliseconds */\r
+ wait = (wait * 1000) / 15;\r
+ MicroDelay(wait); /* delay in 15 us increments */\r
+ }\r
+}\r
+\r
+/*=========================================================\r
+ Checks for a media change for selected drive.\r
+ Returns:\r
+ 0, or ErcNewMedia\r
+-------------------------------------------------------*/\r
+\r
+static U32 med_change(void)\r
+{\r
+\r
+/* if no disk change indicated return OK */\r
+\r
+ if (InByte(DIR_PORT) & DSKCHANGE_BIT) {\r
+ fdstatus.fNewMedia = 1;\r
+ return (ErcNewMedia);\r
+ }\r
+ else {\r
+ fdstatus.fNewMedia = 0;\r
+ return (ok);\r
+ }\r
+}\r
+\r
+\r
+/***********************************************************\r
+ Wait until an operation is complete, then accept the status\r
+ from the controller.\r
+************************************************************/\r
+\r
+static U32 get_fdc_status(void)\r
+{\r
+U32 erc;\r
+\r
+ if (erc = wait_int()) return(erc);\r
+ if (erc = results(7)) return(erc);\r
+ if (!(FDC_STATUS[0] & 0xc0)) return(ok);\r
+ if ((FDC_STATUS[0] & 0xc0) == 0x80) return(ErcBadCmd);\r
+ if ((FDC_STATUS[0] & 0xc0) == 0xC0) return(ErcReadyLine);\r
+\r
+ /* If we got here, get controller error status */\r
+\r
+ if (FDC_STATUS[1] & BIT7)\r
+ erc = ErcSectNotFound;\r
+ else if (FDC_STATUS[1] & BIT5)\r
+ erc = ErcCRC;\r
+ else if (FDC_STATUS[1] & BIT4)\r
+ erc = ErcOverRun;\r
+ else if (FDC_STATUS[1] & BIT2)\r
+ erc = ErcSectNotFound;\r
+ else if (FDC_STATUS[1] & BIT1)\r
+ erc = ErcReadOnly;\r
+ else if (FDC_STATUS[1] & BIT0)\r
+ erc = ErcAddrMark;\r
+ else fdstatus.erc = ErcBadFDC;\r
+ return(erc);\r
+}\r
+\r
+\r
+/*************************************************************\r
+ This is called for Read, Write or Verify commmands. The only\r
+ differences in these 3 commands is the DMA type/direction and\r
+ a single byte FDC command.\r
+*************************************************************/\r
+\r
+static U32 RdWrtVerf(U32 op)\r
+{\r
+U32 erc;\r
+S8 dmatype; /* 0 = Verify, 1 = IN, 2 = Out */\r
+S8 retrys;\r
+U32 count;\r
+\r
+erc = 0;\r
+retrys = 5;\r
+count = fd_nr_sectors * 512;\r
+\r
+while ((fd_nr_sectors) && (!erc)) {\r
+\r
+ switch(op) {\r
+ case(CmdRead): /* Read */\r
+ dmatype = 1;\r
+ fd_fdc_command = FDC_READ;\r
+ break;\r
+ case(2): /* Write */\r
+ fwrite = 1;\r
+ dmatype = 2;\r
+ CopyData(fd_pData, sectbuf, 512);\r
+ fd_fdc_command = FDC_WRITE;\r
+ break;\r
+ case(3): /* Verify */\r
+ dmatype = 0;\r
+ fd_fdc_command = FDC_READ;\r
+ }\r
+\r
+ /* PhyAddress nBytes ch Type Mode */\r
+ erc = DmaSetUp(physectbuf, 512, 2, dmatype, 1 );\r
+\r
+ if (!erc) {\r
+ while(retrys--)\r
+ {\r
+ erc = 0;\r
+ if (!erc) erc = seek();\r
+ if (!erc) erc = send_fdc(fd_fdc_command);\r
+ if (!erc) erc = send_fdc(((fd_head <<2) & BIT2) | fd_drive);\r
+ if (!erc) erc = send_fdc(fd_track);\r
+ if (!erc) erc = send_fdc(fd_head);\r
+ if (!erc) erc = send_fdc(fd_sector);\r
+ if (!erc) erc = send_fdc(GetParm(3));\r
+ if (!erc) erc = send_fdc(GetParm(4));\r
+ if (!erc) erc = send_fdc(GetParm(5));\r
+ if (!erc) erc = send_fdc(GetParm(6));\r
+ if (!erc) erc = get_fdc_status();\r
+ if (!erc) break; /* exit loop with good operation */\r
+ MicroDelay(200); /* wait 3 milliseconds... */\r
+ } /* While statement for 5 retrys */\r
+ }\r
+\r
+ if (!erc) {\r
+ if (op==CmdRead)\r
+ CopyData(sectbuf, fd_pData, 512);\r
+ fdstatus.blocks_done++;\r
+ fd_pData+=512;\r
+ --fd_nr_sectors; /* One less sector */\r
+ ++fd_sector; /* Next sector please */\r
+ if (fd_sector > fdisk_table[fdstatus.type_now][4]) {\r
+ fd_sector = 1; /* back to sect one */\r
+ ++fd_head; /* next head please */\r
+ }\r
+ if (fd_head > 1) {\r
+ fd_head = 0; /* back to head 0 */\r
+ ++fd_track; /* next track please */\r
+ }\r
+ }\r
+} /* while */\r
+return(erc);\r
+}\r
+\r
+\r
+/*************************************************************\r
+ This formats the track beginning at the block address given\r
+ in dLBA. dLBA must always be a multiple of the number of\r
+ sectors per track minus 1 for the disk type (usually 9 or 15).\r
+*************************************************************/\r
+\r
+static U32 format_track(void)\r
+{\r
+U32 erc;\r
+\r
+ fd_fdc_command = FDC_FORMAT;\r
+ fwrite = 1; /* indicate write operation */\r
+\r
+ erc = DmaSetUp(fd_pData, 512, 2, 1, 1);\r
+ if (!erc) erc = send_fdc(3); /* specify command */\r
+ if (!erc) erc = send_fdc(GetParm(0));\r
+ if (!erc) erc = send_fdc(GetParm(1));\r
+ if (!erc) erc = seek();\r
+ if (!erc) erc = send_fdc(fd_fdc_command);\r
+ if (!erc) erc = send_fdc(((fd_head <<2) & BIT2) | fd_drive);\r
+ if (!erc) erc = send_fdc(GetParm(3));\r
+ if (!erc) erc = send_fdc(GetParm(4));\r
+ if (!erc) erc = send_fdc(GetParm(7));\r
+ if (!erc) erc = send_fdc(GetParm(8));\r
+ if (!erc) erc = get_fdc_status();\r
+ return(erc);\r
+}\r
+\r
+/******************************************************************************\r
+Now begins the PUBLIC routines that are interfaced to for all DEVICE DRIVERS\r
+*/\r
+\r
+/******************************************\r
+Called for all device operations. This\r
+assigns physical device from logical number\r
+that outside callers use. For Floppy, 5=0\r
+and 6=1. This will check to make sure a\r
+drive type is assigned then calc physical\r
+head, cly, sector from dLBA.\r
+*******************************************/\r
+\r
+static U32 dev_op(U32 dDevice,\r
+ U32 dOpNum,\r
+ U32 dLBA,\r
+ U32 dnBlocks,\r
+ S8 *pData)\r
+{\r
+U32 erc, j;\r
+\r
+ fd_pData = pData;\r
+ fwrite = 0;\r
+ fdstatus.blocks_done = 0; /* Reset values in Status record */\r
+ fdstatus.erc = 0;\r
+\r
+ /* Set internal drive number */\r
+\r
+ if (dDevice == 10)\r
+ fd_drive = 0;\r
+ else\r
+ fd_drive = 1;\r
+\r
+/* Check to see if we have a leftover interrupt message from last\r
+ command. If so then we eat it (do nothing)\r
+*/\r
+\r
+ erc = CheckMsg(fd_exch, fd_msg);\r
+\r
+ if (fd_drive==0){\r
+ if (!type0) return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type0;\r
+ }\r
+ else {\r
+ if (!type1) return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type1;\r
+ }\r
+\r
+ if (dLBA > fdstatus.BlocksMax) \r
+ {\r
+/* for troubleshooting\r
+ xprintf("dLBA: %d, BlocksMax: %d, DriveType: %d\r\n",\r
+ dLBA, fdstatus.BlocksMax, fdstatus.type_now);\r
+*/\r
+ return(fdstatus.erc = ErcBadLBA);\r
+ }\r
+\r
+ fd_select(fd_drive); /* turn motor on and select drive */\r
+\r
+ /* set the data rate register to value the drive */\r
+\r
+ OutByte(fdisk_table[fdstatus.type_now][12], DRR_PORT); /* Rate Cmd */\r
+\r
+ /* make sure any residual status is unloaded */\r
+\r
+ purge_fdc();\r
+\r
+ /* if a media change sensed, update status */\r
+\r
+/*\r
+\r
+ erc = med_change();\r
+ if (erc) {\r
+ erc = recal();\r
+ fd_motoroff(fd_drive);\r
+ if (erc) {\r
+ return(fdstatus.erc=erc);\r
+ }\r
+ else\r
+ return(fdstatus.erc=ErcNewMedia);\r
+ }\r
+\r
+*/\r
+\r
+ fdstatus.BlocksMax = rgSectorMax[fdstatus.type_now]; /* set max sectors */\r
+\r
+ fd_nr_sectors = dnBlocks;\r
+\r
+ /* calculate the cylinder, head and sector from LBA */\r
+ /* 3 = nHeads, 4 = sectors/track */\r
+\r
+ fd_track = dLBA / (GetParm(3) * GetParm(4));\r
+ j = dLBA % (GetParm(3) * GetParm(4));\r
+\r
+ /* We now know what cylinder, calc head and sector */\r
+\r
+ fd_head = j / GetParm(4);\r
+ fd_sector = j % GetParm(4) + 1; /* sector numbers start at 1 !!!! */\r
+\r
+/* for troubleshooting...\r
+ xprintf("\r\nDevice: %d, dLBA: %d, dOpNum: %d\r\n", dDevice, dLBA, dOpNum);\r
+*/\r
+\r
+ switch(dOpNum) {\r
+ case(CmdNull):\r
+ fdstatus.erc = ok; /* Null Command */\r
+ break;\r
+ case(CmdRead): /* Read */\r
+ case(CmdWrite): /* Write */\r
+ case(CmdVerify): /* Verify */\r
+ fdstatus.erc = RdWrtVerf(dOpNum);\r
+ break;\r
+ case(CmdFmtBlk): /* Format Block */\r
+ fdstatus.erc = ErcBadOp;\r
+ break;\r
+ case(CmdFmtTrk):\r
+ break; /* Format Track */\r
+ case(CmdSeekTrk): /* Seek Track */\r
+ break;\r
+ default:\r
+ fdstatus.erc = ErcBadOp;\r
+ break;\r
+ }\r
+\r
+ fd_motoroff(fd_drive);\r
+ return(fdstatus.erc);\r
+}\r
+\r
+\r
+/******************************************\r
+Called for indepth status report on ctrlr\r
+and drive specified. Returns 80 byte block\r
+of data including the drive parameter\r
+block (a 16 byte structure with all the\r
+timing and size params of the drive).\r
+This is called by the PUBLIC call DeviceStat!\r
+*******************************************/\r
+\r
+static U32 dev_stat(U32 dDevice,\r
+ S8 * pStatRet,\r
+ U32 dStatusMax,\r
+ U32 *pdStatusRet)\r
+{\r
+S32 j;\r
+\r
+ /* Set internal drive number */\r
+ if (dDevice == 10)\r
+ fd_drive=0;\r
+ else fd_drive = 1;\r
+\r
+ if (fd_drive==0){\r
+ if (type0==0)\r
+ return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type0;\r
+ fdstatus.nCyl = fdisk_table[type0][11]+1; /* total physical cyls */\r
+ fdstatus.nHead = 2; /* total heads on device */\r
+ fdstatus.nSectors= fdisk_table[type0][4]; /* Sectors per track */\r
+ fdstatus.nBPS = 512; /* Bytes per sect */\r
+ }\r
+ else {\r
+ if (type1==0) return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type1;\r
+ fdstatus.nCyl = fdisk_table[type1][11]+1; /* total physical cyls */\r
+ fdstatus.nHead = 2; /* total heads on device */\r
+ fdstatus.nSectors= fdisk_table[type1][4]; /* Sectors per track */\r
+ fdstatus.nBPS = 512; /* Bytes per sect */\r
+ }\r
+\r
+ /* set max sectors in status record */\r
+\r
+ fdstatus.BlocksMax = rgSectorMax[fdstatus.type_now];\r
+\r
+ /* copy in the 16 bytes of floppy specific data */\r
+\r
+ CopyData(&fdisk_table[fdstatus.type_now], &fdstatus.params, 16);\r
+\r
+ /* Update disk status bytes for status return */\r
+\r
+ CopyData(&FDC_STATUS, &fdstatus.STATUS, 8);\r
+\r
+ if (dStatusMax < sStatus) j = dStatusMax;\r
+ else j = sStatus;\r
+\r
+ CopyData(&fdstatus, pStatRet, j); /* copy the status data */\r
+\r
+ *pdStatusRet = j; /* give em the size returned */\r
+\r
+return(0);\r
+}\r
+\r
+/******************************************\r
+Called to reset the floppy disk controller\r
+and to set the TYPE. The type is one byte\r
+of the 64 byte status record passed to\r
+dev_init (offset 13). See the fdstatus struct.\r
+This should be called once for each floppy\r
+before it is used the first time after\r
+the driver is loaded, or after a fatal\r
+error is received (timeout etc.).\r
+This is called by the PUBLIC call DeviceInit!\r
+*******************************************/\r
+\r
+static S32 dev_init(U32 dDevice,\r
+ S8 *pInitData,\r
+ U32 sdInitData)\r
+\r
+{\r
+U32 i;\r
+\r
+ /* Read the init status block in */\r
+\r
+ if (sdInitData > sStatus) i = sStatus; /* no more than 64 bytes! */\r
+ else i = sdInitData;\r
+\r
+ CopyData(pInitData, &FDStatTmp, i); /* copy in their init data */\r
+\r
+ if (dDevice == 10)\r
+ fd_drive=0; /* Set internal drive number */\r
+ else\r
+ fd_drive = 1;\r
+\r
+ if (fd_drive==0){ /* set up for drive 0 */\r
+ type0 = FDStatTmp.type_now;\r
+ if (type0==0) return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type0;\r
+ }\r
+ else {\r
+ type1 = FDStatTmp.type_now;\r
+ if (type1==0) return(fdstatus.erc = ErcDriveType);\r
+ fdstatus.type_now = type1;\r
+ }\r
+ fd_select(fd_drive);\r
+ fdstatus.erc = FDC_reset();\r
+ fd_motoroff(fd_drive);\r
+ return(fdstatus.erc);\r
+}\r
+\r
+/*=========== THE END =========================================*/\r