//+++2004-08-31 // Copyright (C) 2004 Mike Rieker, Beverly, MA USA // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //---2004-08-31 /************************************************************************/ /* */ /* 82078 floppy disk controller driver */ /* */ /* Creates devices named floppy.cn */ /* */ /* c = controller: p for primary, s for secondary */ /* n = unitnumber: 0 or 1 */ /* */ /************************************************************************/ #include "ozone.h" #include "oz_dev_disk.h" #include "oz_dev_timer.h" #include "oz_io_disk.h" #include "oz_knl_devio.h" #include "oz_knl_event.h" #include "oz_knl_hw.h" #include "oz_knl_kmalloc.h" #include "oz_knl_misc.h" #include "oz_knl_procmode.h" #include "oz_knl_sdata.h" #include "oz_knl_section.h" #include "oz_knl_spte.h" #include "oz_knl_status.h" #include "oz_knl_thread.h" #include "oz_knl_userjob.h" #include "oz_sys_dateconv.h" #include "oz_sys_xprintf.h" #define NAME "floppy" #define L2DISK_BLOCK_SIZE (devex -> sizecode + 7) #define DISK_BLOCK_SIZE (128 << devex -> sizecode) #define MAXSIZECODE (sizeof sectorspertrack / sizeof sectorspertrack[0]) #define TRACKSPERCYLINDER (2) #define CYLINDERS (80) #define GPL_1 (0x1B) #define GPL_2 (0x54) #define IRQ_LEVEL 6 // all controllers share this IRQ #define INT_TIMEOUT 5 // number of seconds to wait for an interrupt #define DRIVE_RDY_TIME 3 // within 3Sec after recalibrate, drive should be ready #define MSR_RQM_TIMEOUT 50000 // within 50mS after interrupt, RQM should be set #define MOTOR_TIMER_SECONDS 2 // let motor run for 2Sec after last using it #define MAX_READ_RETRIES 8 // retry reads 8 times #define LOCKDMA() while (oz_knl_event_set (dmapage_event, 0) == 0) oz_knl_event_waitone (dmapage_event) #define UNLKDMA() oz_knl_event_set (dmapage_event, 1) typedef struct Controller Controller; typedef struct Devex Devex; typedef struct Iopex Iopex; /* Registers */ #define SRB (1) /* R/W - Status Register B */ #define SRB_IDLE (0x01) #define SRB_PD (0x02) #define SRB_IDLEMSK (0x04) #define DOR (2) /* R/W - Digital Output Register */ #define DOR_DRIVESEL (0x01) #define DOR_RESET_ (0x04) #define DOR_DMAGATE (0x08) #define DOR_MOTEN0 (0x10) #define DOR_MOTEN1 (0x20) #define DOR_DRIVE0 (0x1c) /* Composite value to enable drive 0 */ #define DOR_DRIVE1 (0x2d) /* Composite value to enable drive 1 */ #define TDR (3) /* R/W - Tape Drive Register */ #define MSR (4) /* R-O - Main Status Register */ #define MSR_DRV0BUSY (0x01) /* Drive 0 is busy doing a seek */ #define MSR_DRV1BUSY (0x02) /* Drive 1 is busy doing a seek */ #define MSR_CB (0x10) /* 0: No command in progress; 1: Command in progress */ #define MSR_NONDMA (0x20) /* 0: Result data can be read; 1: Disk data can be read */ #define MSR_DIO (0x40) /* 0: Host can write data; 1: Host can read data */ #define MSR_RQM (0x80) /* 0: Host cannot transfer data; 1: Host can transfer data */ #define DSR (4) /* W-O - Data Rate Select Register */ #define FIFO (5) /* R/W - Data Register */ #define DIR (7) /* R-O - Digital Input Register */ #define DIR_DSKCHG_ (0x80) /* 0: Disk has not been changed; 1: Disk has been changed */ #define CCR (7) /* W-O - Configuration Control Register */ #define ST3_WP (0x40) /* R-O - write protected */ #define ST3_TRK0 (0x10) /* R-O - track zero */ /* DMA registers (channel 2) */ #define DMA_ADDR2 (0x04) /* W-O - start address */ /* R-O - current address */ #define DMA_COUNT2 (0x05) /* W-O - start count */ /* R-O - remaining count */ #define DMA_CTRL (0x08) /* W-O - control */ /* <0> = 0 : mem-to-mem disabled */ /* 1 : mem-to-mem enabled */ /* <1> = 0 : ch 0 addr hold disabled */ /* 1 : ch 0 addr hold enabled */ /* <2> = 0 : controller enabled */ /* 1 : controller disabled */ /* <3> = 0 : normal timing */ /* 1 : compressed timing */ /* <4> = 0 : fixed priority */ /* 1 : rotating priority */ /* <5> = 0 : late write selection */ /* 1 : extended write selection */ /* <6> = 0 : DRQn sense asserted HI */ /* 1 : DRQn sense asserted LO */ /* <7> = 0 : DAKn sense asserted LO */ /* 1 : DAKn sense asserted HI */ #define DMA_MODE (0x0B) /* W-0 - dma mode */ /* <0:1> = channel select */ /* <2:3> = 00 : verify */ /* 01 : device-to-memory */ /* 10 : memory-to-device */ /* 11 : reserved */ /* <4> = 0 : no auto-init */ /* 1 : auto-init */ /* <5> = 0 : address increment */ /* 1 : address decrement */ /* <6:7> = 00 : demand transfer mode */ /* 01 : single transfer mode */ /* 10 : block transfer mode */ /* 11 : cascade transfer mode */ #define DMA_CBP (0x0C) /* writing to this causes the DMAC to reset the hi/lo byte sequencing */ #define DMA_MCLR (0x0D) /* writing to this does a hardware reset of the DMAC */ #define DMA_ACT (0x0E) /* writing to this activates all four DMA channels */ #define DMA_MASK (0x0F) /* mask bit for each four channels (when set, inhibits operation of the channel) */ #define DMA_PAGE2 (0x81) /* R/W - address page (64K page) */ #define DMA_DEV2MEM (0x04) #define DMA_MEM2DEV (0x08) /* One of these per controller */ struct Controller { Controller *next; /* next controller in list */ OZ_Event *event; /* associated event */ OZ_Timer *inttimer; /* interrupt timer */ const char *ctrlname; /* controller name ("primary", "secondary") */ Devex *devexs; /* list of devices on this controller */ Iopex *iopexqh; /* list of I/O requests waiting for thread */ Iopex **iopexqt; Iopex *iopexip; /* the I/O request currently in progress */ volatile int waitingforint; /* 0=not (no longer) waiting for interrupt; 1=waiting for interrupt; -1=timedout */ uWord ioportbase; /* io port base address, 0x03F0 or 0x0370 */ }; /* Device extension structure */ struct Devex { Devex *next; /* next device on controller */ OZ_Timer *motor_timer; /* motor timer */ OZ_Datebin motoroff; /* when the motor is to be turned off (or 0 if it is off) */ Controller *controller; /* the controller it is connected to */ OZ_Devunit *devunit; /* device unit struct pointer */ const char *unitname; /* unit name string (for error messages) */ OZ_Dbn startblock; /* starting block of the track in trackbuf */ uLong validblocks; /* number of blocks in trackbuf that are valid */ uLong dirtybeg; /* offset of first block in trackbuf that is dirty */ uLong dirtyend; /* offset past last block in trackbuf that is dirty */ uByte *trackbuffer; /* virtual address of track buffer */ uLong trackbuf_pageoffs; /* offset of first byte of track buffer in first physical page */ OZ_Mempage trackbuf_phypages[8]; /* array of track buffer physical page numbers */ uByte sizecode; /* 0=128, 1=256, 2=512, ... 7=16k */ uByte drivesel; /* drive select: 0=drive 0; 1=drive 1 */ uByte datarate; /* datarate for the media currently in drive */ char failed; /* 0: initialization in progress */ /* 1: failed to initialize, delete in progress */ /* -1: init completed successfully, drive is online */ char volvalid; /* volume is valid */ char writable; /* volume is writable */ }; /* I/O operation extension */ struct Iopex { Iopex *next; /* next request in queue */ OZ_Ioop *ioop; /* corresponding ioop */ uLong (*be) (Iopex *iopex); /* backend routine */ Devex *devex; /* device that it is for */ int aborted; /* tagged for abort by floppy_abort routine */ int writing; /* 0=reading; 1=writing */ int writethru; /* 0=normal; 1=writethru */ uLong size; /* size of buffer */ const OZ_Mempage *phypages; /* points to array of physical page numbers */ uLong byteoffs; /* byte offset in first physical page */ OZ_Dbn slbn; /* starting logical block number */ }; /* Function table */ static int floppy_shutdown (OZ_Devunit *devunit, void *devexv); static int floppy_clonedel (OZ_Devunit *devunit, void *devexv, int cloned); static void floppy_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode); static uLong floppy_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap); static const OZ_Devfunc floppy_functable = { sizeof (Devex), 0, sizeof (Iopex), 0, floppy_shutdown, NULL, floppy_clonedel, NULL, NULL, floppy_abort, floppy_start, NULL }; /* Internal static data */ // 128, 256, 512, 1024, 2048, 4096, 8192 corresponding bytes per sector static const uByte sectorspertrack[8] = { 34, 26, 18, 11, 6, 3, 1 }; // indexed using devex->sizecode #define MAX_TRACK_SIZE (2048*6) // what blocksize*sectorspertrack size gives the max data bytes on a track static Controller *controllers; static int initialized = 0; static OZ_Devclass *devclass; static OZ_Devdriver *devdriver; static OZ_Event *dmapage_event; /* dma page's access event flag */ static OZ_Smplock *smplock_fd; /* floppy drive's irq smplock */ static uByte dmapage_buff[MAX_TRACK_SIZE*2]; /* dma buffer, phyaddr = virtaddr */ static uByte *dmapage_vadr; /* dma page's virtual address = dma page's physical address */ static OZ_Phyaddr dmapage_padr; /* dma page's physical address = dma page's virtual address */ static uLong dmapage_size; /* number of bytes available in dmapage_phypage */ static OZ_Hw486_irq_many fd_irq_many; /* interrupt handler link block */ /* Internal routines */ static void config (uWord ioportbase, const char *ctrlname); static uLong floppy_thread (void *controllerv); static void setdrivefailed (Devex *devex); static void setdrivestatus (Devex *devex, const char *status); static uLong be_setvolvalid (Iopex *iopex); static uLong be_writeblocks (Iopex *iopex); static uLong be_readblocks (Iopex *iopex); static uLong be_format (Iopex *iopex); static int mediaonline (Iopex *iopex); static uLong flushtrackbuffer (Devex *devex); static uLong filltrackbuffer (Devex *devex); static int resetctrlr (Controller *controller, uByte datarate); static uLong resetctrlr_check (void *ioportbasev); static int recalibrate (Devex *devex); static void driveidlewait (Devex *devex); static uLong write_blocks (uLong size, const uByte *buff, uLong slbn, uLong *wlen, Devex *devex); static uLong read_blocks (uLong size, uByte *buff, uLong slbn, uLong *rlen, Devex *devex); static uLong chkvolvalid (Devex *devex); static void motor_timer_start (Devex *devex); static void motor_timer_expired (void *devexv, OZ_Timer *timer); static int send_command (int size, uByte *buff, Controller *controller, int interrupts); static void int_timeout (void *controllerv, OZ_Timer *timer); static uLong recv_results (int size, uByte *buff, uWord port, Devex *devex, uByte command); static int command_done (uWord port, uByte command); static uLong command_done_check (void *portv); static uByte wait_for_rqm (uWord port); static uLong check_for_rqm (void *rqmprmv); static uByte wait_for_rqmdio (uWord port); static uLong check_for_rqmdio (void *rqmprmv); static void print_rw_err (const char *rw, Devex *devex, uByte *command, uByte *results, uLong len); static uLong dmapage_setup (int dmadir, uLong transferlen, Devex *devex, uLong byteoffs, OZ_Mempage *phypages, OZ_Dbn slbn); static int fd_interrupt (void *dummy, OZ_Mchargs *mchargs); /************************************************************************/ /* */ /* Boot-time initialization routine */ /* */ /************************************************************************/ void oz_dev_floppy_init () { uLong pm, sts; if (initialized) return; oz_knl_printk ("oz_dev_floppy_init\n"); initialized = 1; /* Make sure the dmapage_buff meets the restrictions of the DMA controller. The stupid DMA */ /* controller can't cross 64K boundaries and it can't address anything above 0xFFFFFF (16M). */ dmapage_size = sizeof dmapage_buff; dmapage_vadr = dmapage_buff; if (((OZ_Pointer)dmapage_vadr & -65536) != ((((OZ_Pointer)dmapage_vadr) + dmapage_size - 1) & -65536)) { dmapage_size /= 2; if (((OZ_Pointer)dmapage_vadr & -65536) != ((((OZ_Pointer)dmapage_vadr) + dmapage_size - 1) & -65536)) { dmapage_vadr = (uByte *)((((OZ_Pointer)dmapage_vadr) + 65535) & -65536); if (((OZ_Pointer)dmapage_vadr & -65536) != ((((OZ_Pointer)dmapage_vadr) + dmapage_size - 1) & -65536)) { oz_crash ("oz_dev_floppy_init: dmapage_vadr %p size %x still crosses 64K boundary", dmapage_vadr, dmapage_size); } } oz_knl_printk ("oz_dev_floppy_init: dmapage_buff size %x at %p crosses 64K boundary\n", sizeof dmapage_buff, dmapage_buff); oz_knl_printk ("oz_dev_floppy_init: - using size %x at %p instead\n", dmapage_size, dmapage_vadr); } sts = oz_knl_misc_sva2pax (dmapage_vadr, &dmapage_padr, &pm, dmapage_size); if (sts < dmapage_size) { oz_knl_printk ("oz_dev_floppy: dmapage_buff %p only %u bytes out of %u\n", dmapage_vadr, sts, dmapage_size); return; } dmapage_padr = (dmapage_padr << OZ_HW_L2PAGESIZE) + pm; if ((dmapage_padr + dmapage_size) > 16*1024*1024) { oz_knl_printk ("oz_dev_floppy: dmapage_buff 0x%x is beyond 16Meg limit\n", dmapage_padr, dmapage_size); return; } /* Allocate an event flag for the (potentially) two threads to serialize access to the one-and-only DMA controller */ sts = oz_knl_event_create (10, "floppy dma", NULL, &dmapage_event); if (sts != OZ_SUCCESS) oz_crash ("oz_dev_floppy: error %u creating dmpage_event", sts); UNLKDMA (); /* Create device driver database entries */ devclass = oz_knl_devclass_create (OZ_IO_DISK_CLASSNAME, OZ_IO_DISK_BASE, OZ_IO_DISK_MASK, "floppy_486"); devdriver = oz_knl_devdriver_create (devclass, "floppy_486"); fd_irq_many.entry = fd_interrupt; fd_irq_many.param = NULL; fd_irq_many.descr = NAME; smplock_fd = oz_hw486_irq_many_add (IRQ_LEVEL, &fd_irq_many); /* Configure controllers */ controllers = NULL; config (0x03F0, "primary"); config (0x0370, "secondary"); } /* This routine sets up the devunit structs immediately then forks a thread to perform the actual probing and */ /* initialization. Meanwhile, the main thread is free to continue on. If anything tries to queue I/O to the */ /* floppy devices before they have been configured, the requests will wait until the configuration is complete. */ /* If the configuration finds that the controller and/or drive does not exist, it will abort any queued requests. */ static void config (uWord ioportbase, const char *ctrlname) { char threadname[OZ_THREAD_NAMESIZE], unitname[OZ_DEVUNIT_NAMESIZE]; Controller *controller; Devex *devex, **ldevex; int drivesel; OZ_Devunit *devunit; OZ_Thread *thread; uLong sts; /* Create and initialize controller struct */ controller = OZ_KNL_NPPMALLOC (sizeof *controller); memset (controller, 0, sizeof *controller); controller -> ioportbase = ioportbase; controller -> ctrlname = ctrlname; controller -> iopexqt = &(controller -> iopexqh); controller -> inttimer = oz_knl_timer_alloc (); /* Create the devices now in case someone wants to queue requests before we sense if the drive is there or not. */ /* This will happen if we are booting from floppy and the floppy will be mounted right away. In that case, the */ /* mount will wait for the initialization to complete. I hate waiting for boot routines to grind on floppies. */ ldevex = &(controller -> devexs); for (drivesel = 0; drivesel < 2; drivesel ++) { /* Create the device unit struct */ oz_sys_sprintf (sizeof unitname, unitname, NAME ".%c%u", ctrlname[0], drivesel); devunit = oz_knl_devunit_create (devdriver, unitname, "", &floppy_functable, 0, oz_s_secattr_sysdev); if (devunit == NULL) continue; /* Fill in the device unit extension info */ devex = oz_knl_devunit_ex (devunit); devex -> failed = 0; devex -> devunit = devunit; devex -> motor_timer = oz_knl_timer_alloc (); devex -> motoroff = 0; devex -> controller = controller; devex -> sizecode = 0; devex -> drivesel = drivesel; devex -> volvalid = 0; devex -> unitname = oz_knl_devunit_devname (devunit); /* Link it to list of devices on the controller */ *ldevex = devex; ldevex = &(devex -> next); /* Set its status */ setdrivestatus (devex, "probing"); /* Set it up to automount a filesystem (theoretically, it can do partitions, too) */ oz_knl_devunit_autogen (devunit, oz_dev_disk_auto, NULL); } *ldevex = NULL; /* Start the kernel thread to process the initialization and then the I/O requests */ oz_sys_sprintf (sizeof threadname, threadname, NAME " %s", ctrlname); sts = oz_knl_event_create (sizeof threadname, threadname, NULL, &(controller -> event)); if (sts != OZ_SUCCESS) oz_knl_printk ("oz_dev_floppy config: error %u creating event %s\n", sts, ctrlname); else { sts = oz_knl_thread_create (oz_s_systemproc, oz_knl_user_getmaxbasepri (NULL), NULL, NULL, NULL, 0, floppy_thread, controller, OZ_ASTMODE_INHIBIT, sizeof threadname, threadname, NULL, &thread); if (sts != OZ_SUCCESS) oz_knl_printk ("oz_dev_floppy config: error %u creating thread %s\n", sts, ctrlname); else { oz_knl_thread_orphan (thread); oz_knl_thread_increfc (thread, -1); } } } /************************************************************************/ /* */ /* This routine is called when the system is being shut down. It can */ /* be assumed that there is no floppy I/O going as disks have been */ /* dismounted at this point. */ /* */ /************************************************************************/ static int floppy_shutdown (OZ_Devunit *devunit, void *devexv) { /* Turn both motors off and reset controller */ oz_hw486_outb (0, ((Devex *)devexv) -> controller -> ioportbase + DOR); return (1); } /************************************************************************/ /* */ /* The last reference was removed from the device and now the system */ /* wants to know if it should be deleted */ /* */ /* Input: */ /* */ /* devunit/devexv = device to be deleted */ /* cloned = as passed to oz_knl_devunit_create */ /* smplevel = dv */ /* */ /* Output: */ /* */ /* floppy_clonedel = 1 : ok to delete the device */ /* */ /* Note: */ /* */ /* This routine is used to delete a device that failed to */ /* initialize at boot time. It should only get called with */ /* 'failed=1' after the setdevicefailed routine decrements the */ /* reference count. */ /* */ /* It should never be called for good devices, as the driver */ /* keeps its original reference count out on the devunit. */ /* */ /************************************************************************/ static int floppy_clonedel (OZ_Devunit *devunit, void *devexv, int cloned) { Controller *controller; Devex *devex, **ldevex, *xdevex; devex = devexv; if (devex -> failed <= 0) oz_crash ("oz_dev_floppy_486 clonedel: devex %p -> failed %d", devex, devex -> failed); controller = devex -> controller; // unlink it from controller for (ldevex = &(controller -> devexs); (xdevex = *ldevex) != devex; ldevex = &(xdevex -> next)) {} *ldevex = xdevex -> next; return (1); // it's ok to delete it } /************************************************************************/ /* */ /* Abort I/O request in progress */ /* */ /************************************************************************/ static void floppy_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode) { Controller *controller; Devex *devex; Iopex *iopex, **liopex, *xiopex; uLong fd; devex = devexv; xiopex = NULL; // haven't found anything to abort yet controller = devex -> controller; // point to controller struct /* Remove any matching requests from controller queue */ fd = oz_hw_smplock_wait (smplock_fd); // lock its queue for (liopex = &(controller -> iopexqh); (iopex = *liopex) != NULL;) { // scan the queue if (oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) { // see if this is something we should abort *liopex = iopex -> next; // if so, unlink from queue iopex -> next = xiopex; // link to stuff to abort xiopex = iopex; } else { liopex = &(iopex -> next); // leave it alone } } controller -> iopexqt = liopex; // set up possibly new tail pointer iopex = controller -> iopexip; // maybe abort the request in progress if ((iopex != NULL) && oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) iopex -> aborted = 1; oz_hw_smplock_clr (smplock_fd, fd); // unlock the queue /* Now that we're back at softint level, abort all the requests we found */ while ((iopex = xiopex) != NULL) { xiopex = iopex -> next; oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, NULL, NULL); } } /************************************************************************/ /* */ /* Start performing a disk i/o function */ /* */ /************************************************************************/ static uLong floppy_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap) { Controller *controller; Devex *devex; Iopex *iopex; uLong fd, sts; devex = devexv; iopex = iopexv; iopex -> aborted = 0; /* Process individual functions */ switch (funcode) { /* Set volume valid bit one way or the other */ case OZ_IO_DISK_SETVOLVALID: { OZ_IO_disk_setvolvalid disk_setvolvalid; movc4 (as, ap, sizeof disk_setvolvalid, &disk_setvolvalid); iopex -> writing = disk_setvolvalid.valid; iopex -> be = be_setvolvalid; break; } /* Write blocks to the disk */ case OZ_IO_DISK_WRITEBLOCKS: { OZ_IO_disk_writeblocks disk_writeblocks; movc4 (as, ap, sizeof disk_writeblocks, &disk_writeblocks); sts = oz_knl_ioop_lockr (ioop, disk_writeblocks.size, disk_writeblocks.buff, &(iopex -> phypages), NULL, &(iopex -> byteoffs)); if (sts != OZ_SUCCESS) return (sts); iopex -> size = disk_writeblocks.size; iopex -> slbn = disk_writeblocks.slbn; iopex -> writethru = disk_writeblocks.writethru; iopex -> be = be_writeblocks; break; } /* Read blocks from the disk */ case OZ_IO_DISK_READBLOCKS: { OZ_IO_disk_readblocks disk_readblocks; movc4 (as, ap, sizeof disk_readblocks, &disk_readblocks); sts = oz_knl_ioop_lockw (ioop, disk_readblocks.size, disk_readblocks.buff, &(iopex -> phypages), NULL, &(iopex -> byteoffs)); if (sts != OZ_SUCCESS) return (sts); iopex -> size = disk_readblocks.size; iopex -> slbn = disk_readblocks.slbn; iopex -> be = be_readblocks; break; } /* Write pages to the disk */ case OZ_IO_DISK_WRITEPAGES: { OZ_IO_disk_writepages disk_writepages; if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); movc4 (as, ap, sizeof disk_writepages, &disk_writepages); iopex -> phypages = disk_writepages.pages; iopex -> byteoffs = disk_writepages.offset; iopex -> size = disk_writepages.size; iopex -> slbn = disk_writepages.slbn; iopex -> writethru = disk_writepages.writethru; iopex -> be = be_writeblocks; break; } /* Read pages from the disk */ case OZ_IO_DISK_READPAGES: { OZ_IO_disk_readpages disk_readpages; if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); movc4 (as, ap, sizeof disk_readpages, &disk_readpages); iopex -> phypages = disk_readpages.pages; iopex -> byteoffs = disk_readpages.offset; iopex -> size = disk_readpages.size; iopex -> slbn = disk_readpages.slbn; iopex -> be = be_readblocks; break; } /* Get info part 1 */ case OZ_IO_DISK_GETINFO1: { OZ_IO_disk_getinfo1 disk_getinfo1; if (!OZ_HW_WRITABLE (as, ap, procmode)) return (OZ_ACCVIO); memset (&disk_getinfo1, 0, sizeof disk_getinfo1); /* zero fill to clear params we don't care and/or know about */ if (devex -> volvalid) { disk_getinfo1.blocksize = DISK_BLOCK_SIZE; /* return what we know about */ disk_getinfo1.secpertrk = sectorspertrack[devex->sizecode]; disk_getinfo1.trkpercyl = TRACKSPERCYLINDER; disk_getinfo1.cylinders = CYLINDERS; disk_getinfo1.totalblocks = disk_getinfo1.secpertrk * disk_getinfo1.trkpercyl * disk_getinfo1.cylinders; } if (as <= sizeof disk_getinfo1) { memcpy (ap, &(disk_getinfo1), as); } else { memcpy (ap, &disk_getinfo1, sizeof disk_getinfo1); memset (((uByte *)ap) + sizeof disk_getinfo1, 0, as - sizeof disk_getinfo1); } return (OZ_SUCCESS); } /* Format -- param string 'sectorsize[,gaplength]' */ case OZ_IO_DISK_FORMAT: { char paramstr[16]; int usedup, usedup2; OZ_IO_disk_format disk_format; uLong blocksize, gaplength, sizecode; movc4 (as, ap, sizeof disk_format, &disk_format); sts = oz_knl_section_ugetz (procmode, sizeof paramstr, disk_format.paramstr, paramstr, NULL); if (sts != OZ_SUCCESS) return (sts); // param string is not addressible blocksize = oz_hw_atoi (paramstr, &usedup); // get sector size from param string for (sizecode = 0; sizecode < MAXSIZECODE; sizecode ++) { // try to find it in the table devex -> sizecode = sizecode; if (DISK_BLOCK_SIZE == blocksize) break; } if (sizecode == MAXSIZECODE) return (OZ_BADPARAM); // if not found, return error status gaplength = GPL_2; // set up default gap length if (paramstr[usedup] == ',') { // see if gap length specified gaplength = oz_hw_atoi (paramstr + (++ usedup), &usedup2); // ok, decode it if (gaplength > 0xFF) return (OZ_BADPARAM); // make sure it is not too big usedup += usedup2; // increment to where terminator is } if (paramstr[usedup] != 0) return (OZ_BADPARAM); // check for no junk left in param string iopex -> size = sizecode; // save the size code here iopex -> writing = 1; // we are writing to floppy iopex -> phypages = NULL; // we will use the dmapage_buff iopex -> byteoffs = gaplength; // save the gap length here iopex -> be = be_format; // queue the formatting routine break; } /* Who knows what */ default: { return (OZ_BADIOFUNC); } } /* Queue request to kernel thread and wake it if this is the first one on queue */ iopex -> ioop = ioop; iopex -> devex = devex; iopex -> next = NULL; // this shall be last in the queue controller = devex -> controller; // point to controller struct sts = OZ_DEVOFFLINE; // assume the device has failed to init fd = oz_hw_smplock_wait (smplock_fd); // lock its queue if (devex -> failed <= 0) { *(controller -> iopexqt) = iopex; // link new request on end controller -> iopexqt = &(iopex -> next); sts = OZ_STARTED; // it has been successfully queued } oz_hw_smplock_clr (smplock_fd, fd); // unlock the queue if (controller -> iopexqh == iopex) { // wake if this is first in queue oz_knl_event_set (controller -> event, 1); } return (sts); } /************************************************************************/ /* */ /* This kernel thread processes requests for the floppy. There is */ /* one kernel thread per controller. */ /* */ /************************************************************************/ static uLong floppy_thread (void *controllerv) { char c, *q, unitdesc[OZ_DEVUNIT_DESCSIZE], unitname[OZ_DEVUNIT_NAMESIZE]; Controller *controller; Devex *devex, **dp; int foundone, si; Iopex *iopex; OZ_Datebin now; OZ_Devunit *devunit; OZ_Event *event; uByte dor, drivesel; uLong dv, fd, sts; uWord ioportbase; controller = controllerv; ioportbase = controller -> ioportbase; si = oz_hw_cpu_setsoftint (0); /* Link it so interrupt routine can see it */ fd = oz_hw_smplock_wait (smplock_fd); controller -> next = controllers; controllers = controller; oz_hw_smplock_clr (smplock_fd, fd); /* See if the controller is even there. If not, delete the devices and exit. */ oz_knl_printk ("oz_dev_floppy: resetting %s floppy controller (port %X)\n", controller -> ctrlname, ioportbase); if (!resetctrlr (controller, 0)) { oz_knl_printk ("oz_dev_floppy: %s controller didn't come ready\n", controller -> ctrlname); while (1) { dv = oz_hw_smplock_wait (&oz_s_smplock_dv); for (devex = controller -> devexs; devex != NULL; devex = devex -> next) if (devex -> failed <= 0) break; oz_hw_smplock_clr (&oz_s_smplock_dv, dv); if (devex == NULL) break; setdrivefailed (devex); } oz_hw_cpu_setsoftint (si); return (OZ_SUCCESS); } /* Loop through each possible device to see if it is there by doing a recalibrate */ foundone = 0; while (1) { dv = oz_hw_smplock_wait (&oz_s_smplock_dv); // find one we haven't processed yet for (devex = controller -> devexs; devex != NULL; devex = devex -> next) if (devex -> failed == 0) break; oz_hw_smplock_clr (&oz_s_smplock_dv, dv); if (devex == NULL) break; // we've done them all, exit loop oz_knl_printk ("oz_dev_floppy: recalibrating %s\n", devex -> unitname); // announce we are going to check it if (!recalibrate (devex)) setdrivefailed (devex); // check it, if failed, delete drive else { devex -> failed = -1; // it's ok, mark it online setdrivestatus (devex, "online"); oz_knl_printk ("oz_dev_floppy: %s available\n", devex -> unitname); // print a message foundone = 1; // don't exit the kernel thread } oz_hw486_outb (DOR_RESET_, ioportbase + DOR); // anyway, turn the motors all off } /* Init completed, good or bad */ /* If there aren't any drives, exit */ if (!foundone) { oz_knl_printk ("oz_dev_floppy: no drives attached to %s controller\n", controller -> ctrlname); oz_hw_cpu_setsoftint (si); return (OZ_SUCCESS); } /* Process I/O requests for those drives forever */ while (1) { /* See if there are any requests to process */ fd = oz_hw_smplock_wait (smplock_fd); iopex = controller -> iopexqh; if (iopex != NULL) { controller -> iopexqh = iopex -> next; if (iopex -> next == NULL) controller -> iopexqt = &(controller -> iopexqh); controller -> iopexip = iopex; } oz_hw_smplock_clr (smplock_fd, fd); /* If not, wait for something to do */ if (iopex == NULL) { /* Meanwhile, maybe some motors need turning off */ /* Do this here instead of in timer ast so it gets synchronized */ now = oz_hw_tod_getnow (); for (devex = controller -> devexs; devex != NULL; devex = devex -> next) { if ((devex -> motoroff != 0) && (now >= devex -> motoroff)) { if (devex -> volvalid) flushtrackbuffer (devex); // make sure any pending writes are flushed first dor = oz_hw486_inb (ioportbase + DOR); // now turn the motor off dor &= ~ (DOR_MOTEN0 << devex -> drivesel); oz_hw486_outb (dor, ioportbase + DOR); } } /* Now we can wait */ if (controller -> iopexqh == NULL) { // flushtrackbuffer may have cleared event flag // just after a new request was queued so test again oz_knl_event_waitone (controller -> event); oz_knl_event_set (controller -> event, 0); } } /* Otherwise, process request */ else { oz_knl_process_setcur (oz_knl_ioop_getprocess (iopex -> ioop)); // switch processes to access parameters and buffers sts = (*(iopex -> be)) (iopex); // perform the I/O request controller -> iopexip = NULL; // don't let floppy_abort see it now oz_knl_iodone (iopex -> ioop, sts, NULL, NULL, NULL); // post completion oz_knl_process_setcur (oz_s_systemproc); // switch back to neutral territory } } } /* The drive failed initialization tests, mark it as failed then abort any I/O requests that may have queued */ /* Upon return, the devex pointer is no longer valid, and the controller->devexs list might be modified */ static void setdrivefailed (Devex *devex) { Controller *controller; Iopex *iopex, **liopex; OZ_Devunit *devunit; uLong fd; /* Set the status string to note that it has failed to initialize */ setdrivestatus (devex, "init failed"); /* Abort any pending I/O requests */ controller = devex -> controller; check: fd = oz_hw_smplock_wait (smplock_fd); // lock this controller's i/o request queue devex -> failed = 1; // mark it as failed so no new requests will queue for (liopex = &(controller -> iopexqh); (iopex = *liopex) != NULL; liopex = &(iopex -> next)) { if (iopex -> devex != devex) continue; // skip request if it's not for this drive *liopex = iopex -> next; // unlink the request if (*liopex == NULL) controller -> iopexqt = liopex; oz_hw_smplock_clr (smplock_fd, fd); // release lock and post request oz_knl_iodone (iopex -> ioop, OZ_DEVOFFLINE, NULL, NULL, NULL); goto check; // re-scan list for more } oz_hw_smplock_clr (smplock_fd, fd); // none in there, release lock /* Call the clonedel routine to actually delete the devunit/devex structs when all channels are deassigned */ oz_knl_devunit_increfc (devex -> devunit, -1); // dec ref count, if/when it goes zero, call clonedel } /* Set the drive status string in the unit description */ static void setdrivestatus (Devex *devex, const char *status) { char unitdesc[OZ_DEVUNIT_DESCSIZE]; Controller *controller; controller = devex -> controller; oz_sys_sprintf (sizeof unitdesc, unitdesc, "(port %X) %s", controller -> ioportbase, status); oz_knl_devunit_rename (devex -> devunit, NULL, unitdesc); } /************************************************************************/ /* */ /* Set volume valid I/O request */ /* */ /* Input: */ /* */ /* iopex -> writing = 0 : turn volume offline, flush track buffer */ /* 1 : turn volume online (if media is loaded) */ /* */ /************************************************************************/ static uLong be_setvolvalid (Iopex *iopex) { Devex *devex; uByte dor; uLong sts; devex = iopex -> devex; sts = OZ_SUCCESS; if (iopex -> writing) { // see if turning it online devex -> volvalid = mediaonline (iopex); // ... see if media is loaded if (!(devex -> volvalid)) sts = OZ_VOLNOTVALID; // ... and maybe return error code } else if (devex -> volvalid) { // turning offline, see if currently online sts = flushtrackbuffer (devex); // if online, flush track buffer OZ_KNL_NPPFREE (devex -> trackbuffer); // ... then free track buffer devex -> trackbuffer = NULL; devex -> volvalid = 0; // ... and mark volume offline dor = oz_hw486_inb (devex -> controller -> ioportbase + DOR); // ... and turn the motor off dor &= ~ (DOR_MOTEN0 << devex -> drivesel); oz_hw486_outb (dor, devex -> controller -> ioportbase + DOR); } return (sts); } /************************************************************************/ /* */ /* Write blocks of data to floppy disk */ /* */ /* Input: */ /* */ /* size = number of bytes to write */ /* phypages/byteoffs = address of bytes to write */ /* slbn = starting logical block number */ /* devex = device extension identifying floppy drive to write to */ /* */ /* smplock = softint */ /* */ /* Output: */ /* */ /* write_blocks = OZ_SUCCES : successful write */ /* else : error status */ /* data written to floppy */ /* */ /************************************************************************/ static uLong be_writeblocks (Iopex *iopex) { uByte command[9], cylinder, msr, results[7], secintrk, sector, track; const uByte *p; Devex *devex; OZ_Dbn startblock; uLong direct, secpertrk, sts, writelen; uWord port; devex = iopex -> devex; /* Check volume valid bit */ sts = chkvolvalid (devex); if (sts != OZ_SUCCESS) return (sts); /* Check write-protect switch */ if (!(devex -> writable)) return (OZ_WRITELOCKED); /* Turn mo-toron (if not already) */ port = devex -> controller -> ioportbase; msr = oz_hw486_inb (port + DOR); oz_hw486_outb ((msr & 0xFC) | DOR_RESET_ | DOR_DMAGATE | devex -> drivesel | (DOR_MOTEN0 << devex -> drivesel), port + DOR); /* Start writing */ secpertrk = sectorspertrack[devex->sizecode]; while (iopex -> size > 0) { /* If it doesn't start within current track buffer, flush current track buffer and start a new one */ secintrk = iopex -> slbn % secpertrk; // get sector within track we are doing startblock = iopex -> slbn - secintrk; // get block at beginning of track we are doing if (startblock != devex -> startblock) { // see if it is in the track buffer sts = flushtrackbuffer (devex); // if not, flush the track that's in there if (sts != OZ_SUCCESS) goto rtn; devex -> startblock = startblock; // then say this track is in there now devex -> validblocks = 0; // ... but none of it is valid devex -> dirtybeg = secpertrk; // ... and none of it is dirty devex -> dirtyend = 0; } if (secintrk > devex -> validblocks) { // see if there is a gap after what's valid and where we're starting sts = filltrackbuffer (devex); // if so, read the rest of the track from the floppy to fill the gap if (sts != OZ_SUCCESS) goto rtn; } /* Copy data into track buffer and make sure the dirty limits contain it */ writelen = (secpertrk - secintrk) << L2DISK_BLOCK_SIZE; // get how many bytes are left in the track from where we start if (writelen > iopex -> size) writelen = iopex -> size; // ... but don't do more than the caller gave us to write oz_hw_phys_movetovirt (writelen, devex -> trackbuffer + (secintrk << L2DISK_BLOCK_SIZE), iopex -> phypages, iopex -> byteoffs); if (devex -> dirtybeg > secintrk) devex -> dirtybeg = secintrk; // maybe move beg of dirty stuff back secintrk += writelen >> L2DISK_BLOCK_SIZE; // get block number after where we wrote if (devex -> dirtyend < secintrk) devex -> dirtyend = secintrk; // maybe move end of dirty stuff down if (devex -> validblocks < secintrk) devex -> validblocks = secintrk; // maybe move end of valid stuff down /* Increment and continue */ iopex -> size -= writelen; // this fewer bytes to do iopex -> slbn += writelen >> L2DISK_BLOCK_SIZE; // start at this block number next time iopex -> byteoffs += writelen; // start at this offset in buffer next time /* Maybe it is being aborted */ sts = OZ_ABORTED; if (iopex -> aborted) goto rtn; } /* If writethru mode, flush the track buffer out to the floppy */ sts = OZ_SUCCESS; if (iopex -> writethru) sts = flushtrackbuffer (devex); rtn: motor_timer_start (devex); return (sts); } /************************************************************************/ /* */ /* Read blocks of data from floppy disk */ /* */ /* Input: */ /* */ /* size = number of bytes to read */ /* slbn = starting logical block number */ /* devex = device extension identifying floppy drive to read from */ /* */ /* smplock = softint */ /* */ /* Output: */ /* */ /* read_blocks = OZ_SUCCES : successful read */ /* else : error status */ /* phypages/byteoffs = filled with data read from floppy */ /* */ /************************************************************************/ static uLong be_readblocks (Iopex *iopex) { uByte command[9], cylinder, endintrk, msr, results[7], secintrk, sector, track; Devex *devex; int retries; OZ_Dbn startblock; uLong direct, readlen, secpertrk, sts; uWord port; devex = iopex -> devex; /* Check volume valid bit */ sts = chkvolvalid (devex); if (sts != OZ_SUCCESS) return (sts); /* Turn motor on (if not already) */ port = devex -> controller -> ioportbase; msr = oz_hw486_inb (port + DOR); oz_hw486_outb ((msr & 0xFC) | DOR_RESET_ | DOR_DMAGATE | devex -> drivesel | (DOR_MOTEN0 << devex -> drivesel), port + DOR); /* Start reading */ secpertrk = sectorspertrack[devex->sizecode]; while (iopex -> size > 0) { /* If it doesn't start within current track buffer, flush current track buffer and start a new one */ secintrk = iopex -> slbn % secpertrk; // get sector within track we are doing startblock = iopex -> slbn - secintrk; // get block at beginning of track we are doing if (startblock != devex -> startblock) { // see if it is in the track buffer sts = flushtrackbuffer (devex); // if not, flush the track that's in there if (sts != OZ_SUCCESS) goto rtn; devex -> startblock = startblock; // then say this track is in there now devex -> validblocks = 0; // ... but none of it is valid devex -> dirtybeg = secintrk; // ... and none of it is dirty devex -> dirtyend = 0; } readlen = (secpertrk - secintrk) << L2DISK_BLOCK_SIZE; // this is now many bytes to end of track if (readlen > iopex -> size) readlen = iopex -> size; // if more than we want, read just enough endintrk = secintrk + (readlen >> L2DISK_BLOCK_SIZE); if (endintrk > devex -> validblocks) { // see if there is a gap after what's valid and where we end reading sts = filltrackbuffer (devex); // if so, read the rest of the track from the floppy to fill the gap if (sts != OZ_SUCCESS) goto rtn; } /* Copy from track buffer, increment and continue */ oz_hw_phys_movefromvirt (readlen, devex -> trackbuffer + (secintrk << L2DISK_BLOCK_SIZE), iopex -> phypages, iopex -> byteoffs); iopex -> size -= readlen; // decrement what we have left to do iopex -> slbn += readlen >> L2DISK_BLOCK_SIZE; // increment block number iopex -> byteoffs += readlen; // increment where we put what's left /* Maybe it is being aborted */ sts = OZ_ABORTED; if (iopex -> aborted) goto rtn; } sts = OZ_SUCCESS; rtn: motor_timer_start (devex); return (sts); } /************************************************************************/ /* */ /* Format a floppy */ /* */ /* Input: */ /* */ /* size = size code (0=128, 1=256, 2=512, ...) */ /* devex = device extension identifying floppy drive to write to */ /* */ /* smplock = softint */ /* */ /* Output: */ /* */ /* be_format = OZ_SUCCES : successful */ /* else : error status */ /* */ /************************************************************************/ static uLong be_format (Iopex *iopex) { Controller *controller; Devex *devex; int i; OZ_Datebin when; uByte command[6], cylinder, gaplength, msr, *p, results[7], sector, track; uLong secpertrk, sts, writelen; uWord port; devex = iopex -> devex; controller = devex -> controller; /* Turn mo-toron (if not already) */ port = controller -> ioportbase; msr = oz_hw486_inb (port + DOR); oz_hw486_outb ((msr & 0xFC) | DOR_RESET_ | DOR_DMAGATE | devex -> drivesel | (DOR_MOTEN0 << devex -> drivesel), port + DOR); /* Start formatting */ devex -> sizecode = iopex -> size; gaplength = iopex -> byteoffs; iopex -> byteoffs = 0; secpertrk = sectorspertrack[devex->sizecode]; writelen = secpertrk * 4; oz_knl_printk ("oz_dev_floppy_486: formatting %s, sector size %u, gap length %u\n", devex -> unitname, DISK_BLOCK_SIZE, gaplength); oz_hw486_outb (0, port + CCR); // set data rate for (cylinder = 0; cylinder < CYLINDERS; cylinder ++) { oz_knl_printk ("\roz_dev_floppy_486: formatting %s cylinder %2u ... ", devex -> unitname, cylinder); /* Seek to the cylinder to be formatted */ sts = OZ_IOFAILED; if (cylinder == 0) { command[0] = 0x07; // command = RECALIBRATE command[1] = devex -> drivesel; // drive select bits if (!send_command (2, command, controller, 1)) goto rtn; } else { command[0] = 0x0F; // command = SEEK command[1] = devex -> drivesel; // drive select bits command[2] = cylinder; // cylinder number if (!send_command (3, command, controller, 1)) goto rtn; } command[0] = 0x08; // command = sense interrupt status if (!send_command (1, command, controller, 0)) goto rtn; sts = recv_results (2, results, port, devex, 0x08); // read seek results if (sts != OZ_SUCCESS) goto rtn; if ((results[0] & 0xE0) != 0x20) { oz_knl_printk ("\noz_dev_floppy: %s seek to cyl %u did not complete, st0 %X\n", devex -> unitname, cylinder, results[0]); sts = OZ_IOFAILED; goto rtn; } if (results[1] != cylinder) { oz_knl_printk ("\noz_dev_floppy: %s seek went to cylinder %u instead of %u\n", devex -> unitname, results[1], cylinder); sts = OZ_IOFAILED; goto rtn; } /* Wait a settling time */ when = oz_hw_tod_getnow (); // get what time it is now when += OZ_TIMER_RESOLUTION / 2; // get what time it will be in 1/2 sec do { controller -> waitingforint = 0; // we're just waiting for interrupt timeout timer oz_knl_timer_insert (controller -> inttimer, when, int_timeout, controller); // put timer in queue while (controller -> waitingforint >= 0) { // wait for it to time out oz_knl_event_waitone (controller -> event); oz_knl_event_set (controller -> event, 0); } } while (oz_hw_tod_getnow () < when); // repeat if it woke too soon /* Do each track in the cylinder */ for (track = 0; track < TRACKSPERCYLINDER; track ++) { /* Put formatting data in temp DMA buffer and set up dma registers */ /* phypages = NULL so dmapage_setup routine knows to use dmapage_buff */ oz_knl_printk ("f"); LOCKDMA (); dmapage_setup (DMA_MEM2DEV, secpertrk * 4, devex, 0, NULL, 0); p = dmapage_vadr; for (sector = 1; sector <= secpertrk; sector ++) { *(p ++) = cylinder; *(p ++) = track; *(p ++) = sector; *(p ++) = devex -> sizecode; } /* Send a format track command to the controller */ command[0] = 0x4D; // command = MFM, FORMAT TRACK command[1] = (track << 2) + devex -> drivesel; // drive select and head select bits command[2] = devex -> sizecode; // 00=128 bytes per sector, 01=256 bytes per sector, ... command[3] = secpertrk; // number of sectors in the track command[4] = gaplength; command[5] = 0x00; // initial data fill byte value if (!send_command (6, command, controller, 1)) { UNLKDMA (); sts = OZ_IOFAILED; goto rtn; } /* Receive the results back */ sts = recv_results (7, results, port, devex, 0x4D); UNLKDMA (); if (sts != OZ_SUCCESS) goto rtn; if ((results[0] & 0xC0) != 0x00) { oz_knl_printk ("\n"); print_rw_err ("format", devex, command, results, writelen); sts = OZ_IOFAILED; goto rtn; } /* Now write all zeroes to the track */ oz_knl_printk ("w"); LOCKDMA (); dmapage_setup (DMA_MEM2DEV, secpertrk * DISK_BLOCK_SIZE, devex, 0, NULL, 0); memset (dmapage_vadr, 0x69, secpertrk * DISK_BLOCK_SIZE); command[0] = 0x45; /* command = MFM, WRITE */ command[1] = (track << 2) + devex -> drivesel; /* drive select and head select bits */ command[2] = cylinder; /* cylinder number (starting at zero) */ command[3] = track; /* track number (head select, 0 or 1) */ command[4] = 1; /* sector number (starting at one) */ command[5] = devex -> sizecode; /* 00=128 bytes per sector, 01=256 bytes per sector, ... */ command[6] = secpertrk; /* last sector on track (inclusive) */ command[7] = GPL_1; command[8] = 0xFF; /* data transfer length = maximum */ sts = OZ_IOFAILED; if (!send_command (9, command, devex -> controller, 1)) { UNLKDMA (); goto rtn; } sts = recv_results (7, results, devex -> controller -> ioportbase, devex, 0x45); UNLKDMA (); if (sts != OZ_SUCCESS) goto rtn; if ((results[0] & 0xC0) != 0x00) { oz_knl_printk ("\n"); print_rw_err ("write", devex, command, results, writelen); sts = OZ_IOFAILED; goto rtn; } /* Now read the whole track back */ oz_knl_printk ("r"); LOCKDMA (); dmapage_setup (DMA_DEV2MEM, secpertrk * DISK_BLOCK_SIZE, devex, 0, NULL, 0); command[0] = 0x42; /* command = MFM, READ TRACK */ command[1] = (track << 2) + devex -> drivesel; /* drive select and head select bits */ command[2] = cylinder; /* cylinder number (starting at zero) */ command[3] = track; /* track number (head select, 0 or 1) */ command[4] = 1; /* sector number (starting at one) */ command[5] = devex -> sizecode; /* 00=128 bytes per sector, 01=256 bytes per sector, ... */ command[6] = secpertrk; /* last sector on track (inclusive) */ command[7] = GPL_1; command[8] = 0xFF; /* data transfer length = maximum */ sts = OZ_IOFAILED; if (!send_command (9, command, devex -> controller, 1)) { UNLKDMA (); goto rtn; } sts = recv_results (7, results, devex -> controller -> ioportbase, devex, 0x42); UNLKDMA (); if (sts != OZ_SUCCESS) goto rtn; if ((results[0] & 0xC0) != 0x00) { oz_knl_printk ("\n"); print_rw_err ("read", devex, command, results, writelen); sts = OZ_IOFAILED; goto rtn; } /* Maybe it is being aborted */ sts = OZ_ABORTED; if (iopex -> aborted) goto rtn; } } command[0] = 0x07; // command = RECALIBRATE command[1] = devex -> drivesel; // drive select bits send_command (2, command, controller, 1); // retract the heads to cyl 0 sts = OZ_SUCCESS; // we're done rtn: if (sts == OZ_SUCCESS) oz_knl_printk ("\noz_dev_floppy_486: format %s successful\n", devex -> unitname); else oz_knl_printk ("\noz_dev_floppy_486: error %u formatting %s\n", sts, devex -> unitname); motor_timer_start (devex); return (sts); } /************************************************************************/ /* */ /* See if a volume is loaded in floppy drive and reset media changed */ /* flag */ /* */ /* Input: */ /* */ /* iopex = I/O requesting media online check */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* mediaonline = 0 : media not online */ /* 1 : media is online */ /* */ /************************************************************************/ static int mediaonline (Iopex *iopex) { Controller *controller; Devex *devex; int rc, retries; uByte command[3], dir, results[7]; uLong byteoffs, pageoffs, sts, tracksize; uWord ioportbase; devex = iopex -> devex; controller = devex -> controller; ioportbase = controller -> ioportbase; if (devex -> trackbuffer != NULL) { OZ_KNL_NPPFREE (devex -> trackbuffer); devex -> trackbuffer = NULL; } rc = 0; /* assume we will fail to find media online */ for (devex -> datarate = 0; devex -> datarate < 4; devex -> datarate ++) { if (iopex -> aborted) goto done; if (!resetctrlr (controller, devex -> datarate)) { /* reset controller */ oz_knl_printk ("oz_dev_floppy mediaonline: %s controller failed to reset\n", controller -> ctrlname); goto done; } if (iopex -> aborted) goto done; if (!recalibrate (devex)) goto done; /* try to recalibrate drive */ retries = 0; /* DSKCHG flag reset retry counter */ retry: if (iopex -> aborted) goto done; command[0] = 0x0F; /* command = seek */ command[1] = devex -> drivesel; /* drive select and head select bits */ command[2] = retries + 1; /* cylinder number (starting at zero) */ if (!send_command (3, command, controller, 1)) goto done; if (!command_done (ioportbase, 0x0F)) goto done; driveidlewait (devex); /* wait for drive to not be busy */ if (iopex -> aborted) goto done; command[0] = 0x08; /* send sense interrupt command */ if (!send_command (1, command, controller, 0)) goto done; if (recv_results (2, results, ioportbase, devex, 0x08) != OZ_SUCCESS) goto done; if (results[0] & 0xC0) { oz_knl_printk ("oz_dev_floppy: %s recal error st0=%x, pcn=%x\n", devex -> unitname, results[0], results[1]); goto done; } /* if it came ready in there, media is online */ dir = oz_hw486_inb (ioportbase + DIR); /* make sure the DIR_DSKCHG_ bit is clear */ if (dir & DIR_DSKCHG_) { if (++ retries < 3) goto retry; /* if DIR_DSKCHG_ bit is set, seek again to clear it */ oz_knl_printk ("oz_dev_floppy: %s media offline\n", devex -> unitname); goto done; } if (iopex -> aborted) goto done; devex -> writable = 0; /* assume it is not write enabled */ command[0] = 0x04; /* 'sense drive status' command */ command[1] = devex -> drivesel; /* drive select and head select bits */ if (!send_command (2, command, controller, 0)) goto done; /* sense the drive's status */ sts = recv_results (1, results, ioportbase, devex, 0x04); /* read the results back */ if (sts != OZ_SUCCESS) goto done; if (!(results[0] & ST3_WP)) devex -> writable = 1; /* it is write enabled */ if (iopex -> aborted) goto done; command[0] = 0x4A; /* read ID to see if data rate it correct and to get block size code */ command[1] = devex -> drivesel; if (!send_command (2, command, controller, 1)) goto done; sts = recv_results (7, results, ioportbase, devex, 0x4A); if (sts != OZ_SUCCESS) goto done; if ((results[0] & 0xC3) == devex -> drivesel) { /* check the results */ oz_knl_printk ("oz_dev_floppy: %s datarate %u seems to work\n", devex -> unitname, devex -> datarate); devex -> sizecode = results[6]; if (results[6] >= MAXSIZECODE) oz_knl_printk ("oz_dev_floppy: %s size code %u too big\n", devex -> unitname, results[6]); else if (L2DISK_BLOCK_SIZE > OZ_HW_L2PAGESIZE) oz_knl_printk ("oz_dev_floppy: %s block size %u greater than page size\n", devex -> unitname, DISK_BLOCK_SIZE); else { rc = devex -> datarate + 1; /* if everything worked, media is online */ break; } } else { oz_knl_printk ("oz_dev_floppy: %s failed datarate %u, st0=%x, st1=%x, st2=%x\n", devex -> unitname, devex -> datarate, results[0], results[1], results[2]); } } /* If working datarate was found, allocate a track buffer */ if (rc > 0) { devex -> datarate = rc - 1; oz_knl_printk ("oz_dev_floppy: %s using datarate %u\n", devex -> unitname, devex -> datarate); oz_knl_printk ("oz_dev_floppy: %s block size %u\n", devex -> unitname, DISK_BLOCK_SIZE); tracksize = sectorspertrack[devex->sizecode] << L2DISK_BLOCK_SIZE; devex -> trackbuffer = OZ_KNL_NPPMALLOC (tracksize); /* malloc a track buffer */ rc = 0; /* get its physical page array for DMAing */ for (byteoffs = 0; byteoffs < tracksize; byteoffs += sts) { if (rc > sizeof devex -> trackbuf_phypages / sizeof devex -> trackbuf_phypages[0]) { oz_crash ("oz_dev_floppy mediaonline: trackbuffer %p spans too many pages %d", devex -> trackbuffer, rc); } sts = oz_knl_misc_sva2pa (devex -> trackbuffer + byteoffs, devex -> trackbuf_phypages + rc, &pageoffs); if (rc == 0) devex -> trackbuf_pageoffs = pageoffs; else if (pageoffs != 0) { oz_crash ("oz_dev_floppy mediaonline: trackbuffer %p pageoffs[%d] = %X", devex -> trackbuffer, rc, pageoffs); } rc ++; } devex -> startblock = 0; /* say track buffer is empty */ devex -> validblocks = 0; devex -> dirtybeg = sectorspertrack[devex->sizecode]; devex -> dirtyend = 0; rc = 1; oz_knl_printk ("oz_dev_floppy: %s media %s\n", devex -> unitname, devex -> writable ? "read/write" : "read-only"); } done: motor_timer_start (devex); /* restart motor timer */ return (rc); /* return resultant status */ } /************************************************************************/ /* */ /* Flush dirty blocks from current track buffer to the floppy */ /* */ /* Input: */ /* */ /* devex -> startblock = starting block number of the track */ /* devex -> dirtybeg = starting block within the track that's */ /* dirty (zero based) */ /* devex -> dirtyend = ending block (exclusive) that's dirty */ /* devex -> trackbuffer = virtual address of track buffer */ /* */ /* Output: */ /* */ /* flushtrackbuffer = OZ_SUCCESS : successful */ /* else : write error status */ /* devex -> dirtybeg = devex -> dirtyend */ /* */ /************************************************************************/ static uLong flushtrackbuffer (Devex *devex) { uByte command[9], cylinder, results[7], secpertrk, sector, track; uLong direct, sts, writelen; /* Calculate cylinder, track and sector that we will start writing */ secpertrk = sectorspertrack[devex->sizecode]; track = devex -> startblock / secpertrk; cylinder = track / TRACKSPERCYLINDER; track %= TRACKSPERCYLINDER; sector = devex -> dirtybeg; /* Repeat as long as there are dirty blocks to process */ while (devex -> dirtyend > sector) { /* Determine how much to write = to end of dirty blocks or end of dma buffer */ writelen = (devex -> dirtyend - sector) << L2DISK_BLOCK_SIZE; if (writelen > dmapage_size) writelen = dmapage_size; /* Set up DMA registers, copy data to temp dma buffer if track buffer not directly accessible */ LOCKDMA (); direct = dmapage_setup (DMA_MEM2DEV, writelen, devex, devex -> trackbuf_pageoffs + (sector << L2DISK_BLOCK_SIZE), devex -> trackbuf_phypages, devex -> startblock + sector); if (direct != 0) writelen = direct; else memcpy (dmapage_vadr, devex -> trackbuffer + (sector << L2DISK_BLOCK_SIZE), writelen); /* Send a write command to the controller */ command[0] = 0xC5; /* command = MULTITRACK, MFM, WRITE */ command[1] = (track << 2) + devex -> drivesel; /* drive select and head select bits */ command[2] = cylinder; /* cylinder number (starting at zero) */ command[3] = track; /* track number (head select, 0 or 1) */ command[4] = sector + 1; /* sector number (starting at one) */ command[5] = devex -> sizecode; /* 00=128 bytes per sector, 01=256 bytes per sector, ... */ command[6] = secpertrk; /* last sector on track (inclusive) */ command[7] = GPL_1; command[8] = 0xFF; /* data transfer length = maximum */ if (!send_command (9, command, devex -> controller, 1)) { UNLKDMA (); return (OZ_IOFAILED); } /* Receive the results back */ sts = recv_results (7, results, devex -> controller -> ioportbase, devex, 0xC5); UNLKDMA (); if (sts != OZ_SUCCESS) return (sts); if ((results[0] & 0xC0) != 0x00) { print_rw_err ("write", devex, command, results, writelen); return (OZ_IOFAILED); } /* Maybe there are more sectors in the track that are dirty */ sector += writelen >> L2DISK_BLOCK_SIZE; } devex -> dirtybeg = sector; return (OZ_SUCCESS); } /************************************************************************/ /* */ /* Fill track buffer by reading track from floppy */ /* */ /* Input: */ /* */ /* devex -> startblock = starting block number of the track */ /* devex -> validblocks = how many blocks in track are already valid /* devex -> trackbuffer = virtual address of track buffer */ /* */ /* Output: */ /* */ /* filltrackbuffer = OZ_SUCCESS : successful */ /* else : read error status */ /* devex -> validblocks = the whole track */ /* */ /************************************************************************/ static uLong filltrackbuffer (Devex *devex) { int retries; uByte command[9], cylinder, results[7], secpertrk, sector, track; uLong direct, readlen, sts; /* Calculate cylinder, track and sector that we will start reading */ secpertrk = sectorspertrack[devex->sizecode]; track = devex -> startblock / secpertrk; cylinder = track / TRACKSPERCYLINDER; track %= TRACKSPERCYLINDER; sector = devex -> validblocks; /* Keep reading until we have the whole track */ while (sector < secpertrk) { /* Determine how much to read = to end of track or end of dma buffer */ readlen = (secpertrk - sector) << L2DISK_BLOCK_SIZE; if (readlen > dmapage_size) readlen = dmapage_size; /* Set up DMA registers to transfer directly to track buffer */ retries = 3; retry: LOCKDMA (); direct = dmapage_setup (DMA_DEV2MEM, readlen, devex, devex -> trackbuf_pageoffs + (sector << L2DISK_BLOCK_SIZE), devex -> trackbuf_phypages, devex -> startblock + sector); if ((direct != 0) && (direct < readlen)) readlen = direct; /* Send a read command to the controller */ command[0] = 0xE6; // command = MULTITRACK, MFM, SKIP_DELETED, READ command[1] = (track << 2) + devex -> drivesel; // drive select and head select bits command[2] = cylinder; // cylinder number (starting at zero) command[3] = track; // track number (head select, 0 or 1) command[4] = sector + 1; // sector number (starting at one) command[5] = devex -> sizecode; // 00=128 bytes per sector, 01=256 bytes per sector, ... command[6] = secpertrk; // last sector on track (inclusive) command[7] = GPL_1; command[8] = 0xFF; // data transfer length = maximum if (!send_command (9, command, devex -> controller, 1)) { // send command, wait for interrupt UNLKDMA (); return (OZ_IOFAILED); } /* Receive the results back */ sts = recv_results (7, results, devex -> controller -> ioportbase, devex, 0xE6); UNLKDMA (); if (sts != OZ_SUCCESS) { readlen = 512; if (-- retries > 0) goto retry; return (sts); } if ((results[0] & 0xC0) != 0x00) { // check for any error print_rw_err ("read", devex, command, results, readlen); readlen = 512; // on retry, just do one block if (-- retries > 0) goto retry; return (OZ_IOFAILED); } /* If data is in temp dma buffer, copy to track buffer */ if (direct == 0) memcpy (devex -> trackbuffer + (sector << L2DISK_BLOCK_SIZE), dmapage_vadr, readlen); /* Maybe there are more sectors in the track */ sector += readlen >> L2DISK_BLOCK_SIZE; } devex -> validblocks = sector; return (OZ_SUCCESS); } /************************************************************************/ /* */ /* Reset the controller then enable it */ /* */ /* Input: */ /* */ /* controller = controller to be reset */ /* datarate = what to program the data rate to */ /* */ /* Output: */ /* */ /* resetctrlr = 0 : controller never came ready */ /* 1 : controller is ready */ /* */ /************************************************************************/ static int resetctrlr (Controller *controller, uByte datarate) { uByte command[4]; uWord ioportbase; ioportbase = controller -> ioportbase; /* Reset it */ oz_hw486_outb (0, ioportbase + DOR); /* make reset line active */ oz_hw_stl_microwait (2000, NULL, NULL); /* wait at least 2mS */ oz_hw486_outb (DOR_RESET_ | DOR_DMAGATE, ioportbase + DOR); /* release reset line */ /* Wait for it to come ready */ if (!oz_hw_stl_microwait (200000, resetctrlr_check, &ioportbase)) { /* wait up to 200mS for it to come ready */ oz_knl_printk ("oz_dev_floppy resetctrlr: %s didn't come ready, msr %2.2X\n", controller -> ctrlname, oz_hw486_inb (ioportbase + MSR)); return (0); /* never came ready, fail */ } /* Configure it */ oz_hw486_outb (datarate, ioportbase + CCR); /* set data rate */ #if 000 // screws up bochs command[0] = 0x12; /* PERPENDICULAR command code */ command[1] = 0; /* disable perpendicular mode */ if (!send_command (2, command, controller, 0)) return (0); if (!command_done (ioportbase, 0x12)) return (0); #endif command[0] = 0x13; /* CONFIGURE command code */ command[1] = 0; command[2] = 0x4B; /* - enable implied seek, enable fifo, fifo threshold=12 (0b+1) */ command[3] = 0; if (!send_command (4, command, controller, 0)) return (0); if (!command_done (ioportbase, 0x13)) return (0); command[0] = 0x03; /* SPECIFY command code */ command[1] = 0; command[2] = 0; /* - dma mode */ if (!send_command (3, command, controller, 0)) return (0); if (!command_done (ioportbase, 0x03)) return (0); return (1); } static uLong resetctrlr_check (void *ioportbasev) { uByte msr; uWord ioportbase; ioportbase = *(uWord *)ioportbasev; msr = oz_hw486_inb (ioportbase + MSR); // read the master status register return ((msr & (MSR_RQM | MSR_CB)) == MSR_RQM); // success if it is ready to accept a command } /************************************************************************/ /* */ /* Recalibrate floppy */ /* */ /* Input: */ /* */ /* devex = drive to recalibrate */ /* */ /* Output: */ /* */ /* recalibrate = 0 : failed */ /* 1 : successful */ /* */ /************************************************************************/ static int recalibrate (Devex *devex) { Controller *controller; uByte command[2], dor, results[2]; uWord ioportbase; controller = devex -> controller; ioportbase = controller -> ioportbase; dor = oz_hw486_inb (ioportbase + DOR) & 0xFC; /* turn on its motor */ dor |= (DOR_MOTEN0 << devex -> drivesel) | devex -> drivesel | DOR_RESET_ | DOR_DMAGATE; oz_hw486_outb (dor, ioportbase + DOR); command[0] = 0x07; /* send recalibrate command to drive */ command[1] = devex -> drivesel; if (!send_command (2, command, controller, 1)) return (0); driveidlewait (devex); /* wait for drive to not be busy */ command[0] = 0x08; /* send sense interrupt command */ if (!send_command (1, command, controller, 0)) return (0); if (recv_results (2, results, ioportbase, NULL, 0x08) != OZ_SUCCESS) return (0); if (results[0] & 0xC0) { oz_knl_printk ("oz_dev_floppy: %s recal error st0=%x, pcn=%x\n", devex -> unitname, results[0], results[1]); return (0); } command[0] = 0x04; /* send sense drive status command */ command[1] = devex -> drivesel; if (!send_command (2, command, controller, 0)) return (0); if (recv_results (1, results, ioportbase, NULL, 0x04) != OZ_SUCCESS) return (0); if (!(results[0] & ST3_TRK0)) { oz_knl_printk ("oz_dev_floppy: %s not at track 0 after recal (st3=%x)\n", devex -> unitname, results[0]); return (0); } return (1); } /************************************************************************/ /* */ /* Wait up to 'DRIVE_RDY_TIME' seconds for drive busy bit to turn off */ /* */ /* This routine waits 50mS before checking the bit for the first time */ /* and it waits 50mS after seeing the bit off before it returns. */ /* */ /************************************************************************/ static void driveidlewait (Devex *devex) { Controller *controller; int i, j; OZ_Datebin when; uByte msr; controller = devex -> controller; j = 1; // haven't seen it idle yet for (i = 20 * DRIVE_RDY_TIME; -- i >= 0;) { controller -> waitingforint = 0; // we want to wait for the timeout when = oz_hw_tod_getnow (); // get current time when += OZ_TIMER_RESOLUTION / 20; // calc when timeout should happen oz_knl_timer_insert (controller -> inttimer, when, int_timeout, controller); // start the timer going while (controller -> waitingforint >= 0) { // wait for 50mS oz_knl_event_waitone (controller -> event); oz_knl_event_set (controller -> event, 0); } msr = oz_hw486_inb (controller -> ioportbase + MSR); // check the drive's busy bit if (!(msr & (1 << devex -> drivesel)) && (-- j < 0)) break; // if not busy twice, we're done } } /************************************************************************/ /* */ /* Check the 'media changed' status bit and set datarate */ /* */ /* Input: */ /* */ /* devex = device extension pointer */ /* */ /* smplock = softint, lock set */ /* */ /* Output: */ /* */ /* chkvolvalid = OZ_SUCCESS : disk not changed */ /* OZ_VOLNOTVALID : disk was changed */ /* */ /************************************************************************/ static uLong chkvolvalid (Devex *devex) { uByte dir; if (devex -> volvalid) { // maybe it was invalidated by software dir = oz_hw486_inb (devex -> controller -> ioportbase + DIR); // now check to see if hardware invalidated it if (dir & DIR_DSKCHG_) { oz_knl_printk ("oz_dev_floppy: %s media changed\n", devex -> unitname); // if so, print a message devex -> volvalid = 0; // ... and software invalidate it, too } else { oz_hw486_outb (devex -> datarate, devex -> controller -> ioportbase + CCR); // ok, set the datarate for this drive } } return (devex -> volvalid ? OZ_SUCCESS : OZ_VOLNOTVALID); } /************************************************************************/ /* */ /* Restart motor timer for a drive */ /* */ /* Input: */ /* */ /* devex = pointer to device to start the timer for */ /* smplock = softint */ /* */ /************************************************************************/ static void motor_timer_start (Devex *devex) { OZ_Datebin now; now = oz_hw_tod_getnow (); devex -> motoroff = now + (MOTOR_TIMER_SECONDS * OZ_TIMER_RESOLUTION); oz_knl_timer_insert (devex -> motor_timer, devex -> motoroff, motor_timer_expired, devex); } /************************************************************************/ /* */ /* This routine is called back (at softint level) by the timer when */ /* the motor timer expires */ /* */ /************************************************************************/ static void motor_timer_expired (void *devexv, OZ_Timer *timer) { Devex *devex; OZ_Datebin now; devex = devexv; now = oz_hw_tod_getnow (); if (now >= devex -> motoroff) oz_knl_event_set (devex -> controller -> event, 1); else oz_knl_timer_insert (timer, devex -> motoroff, motor_timer_expired, devex); } /************************************************************************/ /* */ /* Send a command to the floppy disk controller and wait for */ /* corresponding interrupt (if any) */ /* */ /* Input: */ /* */ /* size = number of command bytes to send */ /* buff = address of command bytes to send */ /* controller = the controller to send the command to */ /* interrupts = 0 : the command does not produce an interrupt */ /* 1 : wait for the command to interrupt */ /* */ /* Output: */ /* */ /* send_command = 0: error sending command */ /* 1: success */ /* command sent to controller */ /* */ /************************************************************************/ static int send_command (int size, uByte *buff, Controller *controller, int interrupts) { int i; OZ_Datebin when; uByte msr; uWord port; port = controller -> ioportbase; /* Send command to the controller, byte by byte */ /* While sending the command, RQM should be set (indicating the controller is ready to accept command bytes or send result bytes) */ /* DIO should be clear (indicating the controller is accepting command bytes now) */ /* CB should be clear for the first byte (indicating the controller is ready for the first byte of a command) */ /* then it should be set until we read the last result byte from it */ for (i = 0; i < size; i ++) { msr = wait_for_rqm (port); if (!(msr & MSR_RQM)) return (0); if ((msr & MSR_DIO) || ((i > 0) && !(msr & MSR_CB))) { oz_knl_printk ("oz_dev_floppy: %s premature termination of %2.2X command after %d bytes, msr %X\n", controller -> ctrlname, buff[0], i, msr); return (0); } if (i == size - 1) controller -> waitingforint = interrupts; oz_hw486_outb (buff[i], port + FIFO); } /* If the command produces an interrupt, wait for the interrupt. The interrupt routine will clear */ /* 'waitingforint' only when it sees msr set. The timeout routine sets watingforint = -1. */ if (controller -> waitingforint > 0) { when = oz_hw_tod_getnow (); // get current time when += INT_TIMEOUT * OZ_TIMER_RESOLUTION; // calc when timeout should happen oz_knl_timer_insert (controller -> inttimer, when, int_timeout, controller); // start the timer going while (controller -> waitingforint > 0) { // wait for interrupt or timeout oz_knl_event_waitone (controller -> event); oz_knl_event_set (controller -> event, 0); } if (!oz_knl_timer_remove (controller -> inttimer)) { // remove timer from queue while (controller -> waitingforint >= 0) { // couldn't, wait for timeout oz_knl_event_waitone (controller -> event); oz_knl_event_set (controller -> event, 0); } } } return (1); } /* This routine is called at softint level when we have waited too long for an interrupt */ static void int_timeout (void *controllerv, OZ_Timer *timer) { Controller *controller; controller = controllerv; controller -> waitingforint = -1; // say we have executed oz_knl_event_set (controller -> event, 1); // wake in case it is still waiting } /************************************************************************/ /* */ /* Receive results from the floppy disk controller */ /* */ /* Input: */ /* */ /* size = number of command bytes to send */ /* buff = address of command bytes to send */ /* port = I/O port base (0x3F0 or 0x370) */ /* devex = device extension data pointer (or NULL if none) */ /* */ /* Output: */ /* */ /* recv_results = OZ_SUCCESS : success */ /* else : error receiving results */ /* */ /************************************************************************/ static uLong recv_results (int size, uByte *buff, uWord port, Devex *devex, uByte command) { uByte msr; int dtc, i; dtc = 0; for (i = 0; i < size;) { msr = wait_for_rqmdio (port); if (!(msr & MSR_RQM)) goto volnotvalid; if (msr & MSR_NONDMA) { if (dtc == 0) oz_knl_printk ("oz_dev_floppy: %s trying to read results but still in data transfer mode on port %x\n", devex -> controller -> ctrlname, port); oz_hw486_inb (port + FIFO); dtc ++; continue; } if (!(msr & MSR_DIO)) goto premature; if ((i < size - 1) && !(msr & MSR_CB)) goto busyclear; buff[i++] = oz_hw486_inb (port + FIFO); } if (dtc != 0) { oz_knl_printk ("oz_dev_floppy: %s skipped %d data bytes before results\n", devex -> controller -> ctrlname, dtc); } return (OZ_SUCCESS); volnotvalid: if (devex != NULL) devex -> volvalid = 0; return (OZ_VOLNOTVALID); premature: oz_knl_printk ("oz_dev_floppy: %s premature termination of command %2.2X results after %d of %d bytes, msr %2.2X\n", devex -> controller -> ctrlname, command, i, size, msr); return (OZ_IOFAILED); busyclear: oz_knl_printk ("oz_dev_floppy: %s command %2.2X busy bit did not stay set after %d of %d results bytes, msr %2.2X\n", devex -> controller -> ctrlname, command, i, size, msr); return (OZ_IOFAILED); } /************************************************************************/ /* */ /* Wait for the command to be done */ /* */ /* Input: */ /* */ /* port = I/O port base (0x3F0 or 0x370) */ /* */ /* Output: */ /* */ /* command done = 0 : timed out */ /* else : success */ /* */ /************************************************************************/ static int command_done (uWord port, uByte command) { uLong rc; rc = oz_hw_stl_microwait (50000, command_done_check, &port); /* give it up to 50mS to complete */ if (!rc) oz_knl_printk ("oz_dev_floppy command_done: %X command %2.2X timed out, msr %2.2X\n", port, command, oz_hw486_inb (port + MSR)); return (rc); } static uLong command_done_check (void *portv) { uByte msr; msr = oz_hw486_inb (*(uWord *)portv + MSR); return (!(msr & MSR_CB)); } /************************************************************************/ /* */ /* Wait for Master Status Register's RQM bit to be set */ /* */ /* Input: */ /* */ /* port = I/O port base number (0x3F0 or 0x370) */ /* */ /* Output: */ /* */ /* wait_for_rqm = resultant msr contents */ /* if MSR_RQM is set, it succeeded */ /* otherwise, it timed out */ /* */ /************************************************************************/ typedef struct { uWord port; uByte msr; } Rqmprm; static uByte wait_for_rqm (uWord port) { Rqmprm rqmprm; rqmprm.port = port; if (!oz_hw_stl_microwait (MSR_RQM_TIMEOUT, check_for_rqm, &rqmprm)) { oz_knl_printk ("oz_dev_floppy: timed out waiting for MSR_RQM on port %x (msr=%x)\n", port, rqmprm.msr); } return (rqmprm.msr); } static uLong check_for_rqm (void *rqmprmv) { uByte msr; ((Rqmprm *)rqmprmv) -> msr = msr = oz_hw486_inb (((Rqmprm *)rqmprmv) -> port + MSR); return (msr & MSR_RQM); } static uByte wait_for_rqmdio (uWord port) { Rqmprm rqmprm; rqmprm.port = port; if (!oz_hw_stl_microwait (MSR_RQM_TIMEOUT, check_for_rqmdio, &rqmprm)) { oz_knl_printk ("oz_dev_floppy: timed out waiting for MSR_RQM and _DIO on port %x (msr=%x)\n", port, rqmprm.msr); } return (rqmprm.msr); } static uLong check_for_rqmdio (void *rqmprmv) { uByte msr; ((Rqmprm *)rqmprmv) -> msr = msr = oz_hw486_inb (((Rqmprm *)rqmprmv) -> port + MSR); return ((msr & (MSR_RQM | MSR_DIO)) == (MSR_RQM | MSR_DIO)); } /************************************************************************/ /* */ /* Print error status bits */ /* */ /* Input: */ /* */ /* rw = "read" or "write" */ /* devex = device that had error */ /* command = read/write command buffer */ /* results = read/write results buffer */ /* len = length of attempted transfer */ /* */ /* Output: */ /* */ /* error message printed on console */ /* */ /************************************************************************/ static void print_rw_err (const char *rw, Devex *devex, uByte *command, uByte *results, uLong len) { static const char *term[4] = { "normal", "abnormal", "invalid", "polling" }; oz_knl_printk ("oz_dev_floppy: %s %s error len %u, st0 %x, st1 %x, st2 %x\n", devex -> unitname, rw, len, results[0], results[1], results[2]); oz_knl_printk ("oz_dev_floppy: - %s termination%s%s%s%s%s%s%s%s%s%s%s%s\n", term[results[0]>>6], results[0] & 0x10 ? ", recal fail" : "", results[1] & 0x80 ? ", end of cyl" : "", results[1] & 0x20 ? ", data error" : "", results[1] & 0x10 ? ", overrun" : "", results[1] & 0x04 ? ", sector not found" : "", results[1] & 0x02 ? ", write protect" : "", results[1] & 0x01 ? ", missing AM" : "", results[2] & 0x40 ? ", deleted data" : "", results[2] & 0x20 ? ", data CRC error" : "", results[2] & 0x10 ? ", wrong cylinder" : "", results[2] & 0x02 ? ", bad cylinder" : "", results[2] & 0x01 ? ", missing DAM" : ""); oz_knl_printk ("oz_dev_floppy: - track/drive %x, cylinder %x, track %x, sector %x..%x\n", command[1], command[2], command[3], command[4], command[6]); } /************************************************************************/ /* */ /* Set up a DMA operation */ /* */ /* Input: */ /* */ /* dmadir = 8 : memory to device (floppy write) */ /* 4 : device to memory (floppy read) */ /* transferlen = transfer length */ /* iopex -> phypages = NULL : force temp buffer */ /* else : points to physical page number array */ /* byteoffs = 0 : must be when using phypages = NULL */ /* else : offset in first physical page to start */ /* */ /* Output: */ /* */ /* dmapage_setup = 0 : transfer set up using dmapage_vadr buffer */ /* else : direct access to iopex buffer, this many bytes /* */ /************************************************************************/ static uLong dmapage_setup (int dmadir, uLong transferlen, Devex *devex, uLong byteoffs, OZ_Mempage *phypages, OZ_Dbn slbn) { OZ_Mempage npage; OZ_Phyaddr phyaddr; uByte secpertrk, starttrack, stoptrack; uLong direct; if (transferlen > dmapage_size) oz_crash ("oz_dev_floppy_486 dmapage_setup: transferlen %u, dmapage_size %u\n", transferlen, dmapage_size); /* Make sure byteoffs fits within a physical page */ phypages += byteoffs >> OZ_HW_L2PAGESIZE; byteoffs &= (1 << OZ_HW_L2PAGESIZE) - 1; /* See if we can directly access the caller's buffer */ /* - we can do as much as is physically contiguous */ /* - if we can't do all that was requested, we just do to end of starting track */ /* (so we don't waste a rev of the disk after we got a couple blocks from the */ /* second side, we would have to wait for a rev to get the rest) */ /* - if we can't even do to end of track, use temp buffer */ /* In any case, we can't DMA to phys address > 16M, and we can't cross 64K boundary */ direct = 0; // assume we're beyond 16M limit if (phypages != NULL) { // make sure caller isn't forcing temp buffer phyaddr = (phypages[0] << OZ_HW_L2PAGESIZE) + byteoffs; // get starting physical address if (phyaddr < 0x1000000) { // make sure we at least start within 16M limit direct = (1 << OZ_HW_L2PAGESIZE) - byteoffs; // see how much is left of start page while (direct < transferlen) { // repeat as long as there is more to do npage = (direct + byteoffs) >> OZ_HW_L2PAGESIZE; // - calculate new page index if (phypages[npage] != phypages[0] + npage) break; // - stop if it is not contiguous direct += (1 << OZ_HW_L2PAGESIZE); // - ok, we can do one more page of bytes } if (direct > transferlen) direct = transferlen; // don't do more than caller asked for if (direct + phyaddr > 0x1000000) direct = 0x1000000 - phyaddr; // stop at 16M limit if (((direct + phyaddr - 1) ^ phyaddr) & -65536) direct = 65536 - (phyaddr & 65535); // don't cross 64K boundary if (direct < transferlen) { // see if we're doing less than caller asked for secpertrk = sectorspertrack[devex->sizecode]; starttrack = slbn / secpertrk; // can't do it all, get track starting on stoptrack = (slbn + (direct / DISK_BLOCK_SIZE)) / secpertrk; // get track stopping on if (starttrack == stoptrack) direct = 0; // if can't do to end of track, use temp buffer else direct = (secpertrk - (slbn % secpertrk)) * DISK_BLOCK_SIZE; // can do to end of track, so just do that much } } } if (direct != 0) transferlen = direct; // direct, this is how much of it we're going to do else phyaddr = dmapage_padr; // not direct, do all requested bytes using temp buffer if (transferlen == 0) oz_crash ("oz_dev_floppy dmapage_setup: transferlen 0"); /* Set up controller to transfer 'transferlen' bytes starting at physical address 'phyaddr' */ transferlen --; // controller requires length - 1 oz_hw486_outb (0x04, DMA_MASK); // turn off channel 2 oz_hw486_outb (0, DMA_CBP); // make sure it is ready for low byte oz_hw486_outb (phyaddr, DMA_ADDR2); // write bits <00:07> of the physical address oz_hw486_outb (phyaddr >> 8, DMA_ADDR2); // write bits <08:15> of the physical address oz_hw486_outb (phyaddr >> 16, DMA_PAGE2); // write bits <16:23> of the physical address oz_hw486_outb (transferlen, DMA_COUNT2); // write bits <00:07> of the byte count - 1 oz_hw486_outb (transferlen >> 8, DMA_COUNT2); // write bits <08:15> of the byte count - 1 oz_hw486_outb (0, DMA_CTRL); // select defaults for everything in here oz_hw486_outb (dmadir + 2, DMA_MODE); // set to demand transfer mode, address increment, // no auto-init, direction, channel 2 oz_hw486_outb (0, DMA_ACT); // activate all four DMA channels return (direct); } /************************************************************************/ /* */ /* Interrupt routine */ /* */ /* If the thread is waiting for an interrupt and the controller is */ /* ready, wake the kernel thread */ /* */ /* This routines runs with the smplock_fd set */ /* */ /************************************************************************/ static int fd_interrupt (void *dummy, OZ_Mchargs *mchargs) { Controller *controller; for (controller = controllers; controller != NULL; controller = controller -> next) { if (controller -> waitingforint > 0) { // see if the thread is waiting for an interrupt controller -> waitingforint = 0; // if so, say it is no longer waiting oz_knl_event_set (controller -> event, 1); // ... then wake it up } } return (0); }