/*-
* Copyright (c) 1998 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Charles M. Hannum and Minoura Makoto.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*-
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Don Ahn.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)fd.c 7.4 (Berkeley) 5/25/91
*/
struct fd_softc *sc_fd[4]; /* pointers to children */
TAILQ_HEAD(drivehead, fd_softc) sc_drives;
enum fdc_state sc_state;
int sc_errors; /* number of retries so far */
uint8_t sc_status[7]; /* copy of registers */
};
static int fdcintr(void *);
static void fdcreset(struct fdc_softc *);
/*
* Floppies come in various flavors, e.g., 1.2MB vs 1.44MB; here is how
* we tell them apart.
*/
struct fd_type {
int sectrac; /* sectors per track */
int heads; /* number of heads */
int seccyl; /* sectors per cylinder */
int secsize; /* size code for sectors */
int datalen; /* data len when secsize = 0 */
int steprate; /* step rate and head unload time */
int gap1; /* gap len between sectors */
int gap2; /* formatting gap */
int cyls; /* total num of cylinders */
int size; /* size of disk in sectors */
int step; /* steps per cylinder */
int rate; /* transfer speed code */
uint8_t fillbyte; /* format fill byte */
uint8_t interleave; /* interleave factor (formatting) */
const char *name;
};
/* The order of entries in the following table is important -- BEWARE! */
static struct fd_type fd_types[] = {
{ 8,2,16,3,0xff,0xdf,0x35,0x74,77,1232,1,FDC_500KBPS, 0xf6, 1,
"1.2MB/[1024bytes/sector]" }, /* 1.2 MB japanese format */
{ 18,2,36,2,0xff,0xcf,0x1b,0x6c,80,2880,1,FDC_500KBPS, 0xf6, 1,
"1.44MB" }, /* 1.44MB diskette */
{ 15,2,30,2,0xff,0xdf,0x1b,0x54,80,2400,1,FDC_500KBPS, 0xf6, 1,
"1.2MB" }, /* 1.2 MB AT-diskettes */
{ 9,2,18,2,0xff,0xdf,0x23,0x50,40, 720,2,FDC_300KBPS, 0xf6, 1,
"360KB/AT" }, /* 360kB in 1.2MB drive */
{ 9,2,18,2,0xff,0xdf,0x2a,0x50,40, 720,1,FDC_250KBPS, 0xf6, 1,
"360KB/PC" }, /* 360kB PC diskettes */
{ 9,2,18,2,0xff,0xdf,0x2a,0x50,80,1440,1,FDC_250KBPS, 0xf6, 1,
"720KB" }, /* 3.5" 720kB diskette */
{ 9,2,18,2,0xff,0xdf,0x23,0x50,80,1440,1,FDC_300KBPS, 0xf6, 1,
"720KB/x" }, /* 720kB in 1.2MB drive */
{ 9,2,18,2,0xff,0xdf,0x2a,0x50,40, 720,2,FDC_250KBPS, 0xf6, 1,
"360KB/x" }, /* 360kB in 720kB drive */
};
/* software state, per disk (with up to 4 disks per ctlr) */
struct fd_softc {
device_t sc_dev;
struct disk sc_dk;
struct fd_type *sc_deftype; /* default type descriptor */
struct fd_type *sc_type; /* current type descriptor */
#if 0 /* see comments in fd_motor_on() */
struct callout sc_motoron_ch;
#endif
struct callout sc_motoroff_ch;
daddr_t sc_blkno; /* starting block number */
int sc_bcount; /* byte count left */
int sc_opts; /* user-set options */
int sc_skip; /* bytes already transferred */
int sc_nblks; /* number of blocks currently transferring */
int sc_nbytes; /* number of bytes currently transferring */
int sc_drive; /* physical unit number */
int sc_flags;
#define FD_BOPEN 0x01 /* it's open */
#define FD_COPEN 0x02 /* it's open */
#define FD_OPEN (FD_BOPEN|FD_COPEN) /* it's open */
#define FD_MOTOR 0x04 /* motor should be on */
#define FD_MOTOR_WAIT 0x08 /* motor coming up */
#define FD_ALIVE 0x10 /* alive */
int sc_cylin; /* where we think the head is */
TAILQ_ENTRY(fd_softc) sc_drivechain;
int sc_ops; /* I/O ops since last switch */
struct bufq_state *sc_q;/* pending I/O requests */
int sc_active; /* number of active I/O operations */
uint8_t *sc_copybuf; /* for secsize >=3 */
uint8_t sc_part; /* for secsize >=3 */
#define SEC_P10 0x02 /* first part */
#define SEC_P01 0x01 /* second part */
#define SEC_P11 0x03 /* both part */
/*
* Note 1:
* uPD72065 ignores A0 input (connected to x68k bus A1)
* during DMA xfer access, but it's better to explicitly
* specify FDC data register address for clarification.
* Note 2:
* FDC is connected to LSB 8 bits of X68000 16 bit bus
* (as BUS_SPACE_MAP_SHIFTED_ODD defined in bus.h)
* so each FDC register is mapped at sparse odd address.
*
* XXX: No proper API to get DMA address of FDC register for DMAC.
*/
fdc->sc_xfer = dmac_prepare_xfer(fdc->sc_dmachan, fdc->sc_dmat,
fdc->sc_dmamap,
read ? DMAC_OCR_DIR_DTM : DMAC_OCR_DIR_MTD,
DMAC_SCR_MAC_COUNT_UP | DMAC_SCR_DAC_NO_COUNT,
fdc->sc_addr + fddata * 2 + 1);
if (ia->ia_addr == INTIOCF_ADDR_DEFAULT)
ia->ia_addr = FDC_ADDR;
if (ia->ia_intr == INTIOCF_INTR_DEFAULT)
ia->ia_intr = FDC_INTR;
if (ia->ia_dma == INTIOCF_DMA_DEFAULT)
ia->ia_dma = FDC_DMA;
if (ia->ia_dmaintr == INTIOCF_DMAINTR_DEFAULT)
ia->ia_dmaintr = FDC_DMAINTR;
if ((ia->ia_intr & 0x03) != 0)
return 0;
ia->ia_size = FDC_MAPSIZE;
if (intio_map_allocate_region(parent, ia, INTIO_MAP_TESTONLY))
return 0;
/* builtin device; always there */
return 1;
}
/*
* Arguments passed between fdcattach and fdprobe.
*/
struct fdc_attach_args {
int fa_drive;
struct fd_type *fa_deftype;
};
/*
* Print the location of a disk drive (called just before attaching the
* the drive). If `fdc' is not NULL, the drive was found but was not
* in the system config file; print the drive name as well.
* Return QUIET (config_find ignores this if the device was configured) to
* avoid printing `fdN not configured' messages.
*/
static int
fdprint(void *aux, const char *fdc)
{
struct fdc_attach_args *fa = aux;
/*
* Initialize and attach the disk structure.
*/
disk_init(&fd->sc_dk, device_xname(fd->sc_dev), &fddkdriver);
disk_attach(&fd->sc_dk);
/*
* Establish a mountroot_hook anyway in case we booted
* with RB_ASKNAME and get selected as the boot device.
*/
mountroothook_establish(fd_mountroot_hook, fd->sc_dev);
/*
* Move this drive to the end of the queue to give others a `fair'
* chance. We only force a switch if N operations are completed while
* another drive is waiting to be serviced, since there is a long motor
* startup delay whenever we switch.
*/
(void)bufq_get(fd->sc_q);
if (TAILQ_NEXT(fd, sc_drivechain) && ++fd->sc_ops >= 8) {
fd->sc_ops = 0;
TAILQ_REMOVE(&fdc->sc_drives, fd, sc_drivechain);
if (bufq_peek(fd->sc_q) != NULL)
TAILQ_INSERT_TAIL(&fdc->sc_drives, fd, sc_drivechain);
else
fd->sc_active = 0;
}
bp->b_resid = fd->sc_bcount;
fd->sc_skip = 0;
rnd_add_uint32(&fd->rnd_source, bp->b_blkno);
biodone(bp);
/* turn off motor 5s from now */
callout_reset(&fd->sc_motoroff_ch, 5 * hz, fd_motor_off, fd);
fdc->sc_state = DEVIDLE;
}
static int
fdread(dev_t dev, struct uio *uio, int flags)
{
#if 0 /* on x68k motor on triggers interrupts by state change of ready line. */
static void
fd_motor_on(void *arg)
{
struct fd_softc *fd = arg;
struct fdc_softc *fdc = device_private(device_parent(fd->sc_dev));
int s;
DPRINTF(("fd_motor_on:\n"));
s = splbio();
fd->sc_flags &= ~FD_MOTOR_WAIT;
if ((TAILQ_FIRST(&fdc->sc_drives) == fd) &&
(fdc->sc_state == MOTORWAIT))
(void)fdcintr(fdc);
splx(s);
}
#endif
static int
fdcresult(struct fdc_softc *fdc)
{
bus_space_tag_t iot = fdc->sc_iot;
bus_space_handle_t ioh = fdc->sc_ioh;
uint8_t i;
int j, n;
n = 0;
for (j = 100000; j != 0; j--) {
i = bus_space_read_1(iot, ioh, fdsts) &
(NE7_DIO | NE7_RQM | NE7_CB);
if (i == NE7_RQM)
return n;
if (i == (NE7_DIO | NE7_RQM | NE7_CB)) {
if (n >= sizeof(fdc->sc_status)) {
log(LOG_ERR, "fdcresult: overrun\n");
return -1;
}
fdc->sc_status[n++] =
bus_space_read_1(iot, ioh, fddata);
}
delay(10);
}
log(LOG_ERR, "fdcresult: timeout\n");
return -1;
}
static int
out_fdc(bus_space_tag_t iot, bus_space_handle_t ioh, uint8_t x)
{
int i = 100000;
while ((bus_space_read_1(iot, ioh, fdsts) & NE7_DIO) && i-- > 0);
if (i <= 0)
return -1;
while ((bus_space_read_1(iot, ioh, fdsts) & NE7_RQM) == 0 && i-- > 0);
if (i <= 0)
return -1;
bus_space_write_1(iot, ioh, fddata, x);
return 0;
}
static int
fdopen(dev_t dev, int flags, int mode, struct lwp *l)
{
int unit;
struct fd_softc *fd;
struct fd_type *type;
struct fdc_softc *fdc;
unit = FDUNIT(dev);
fd = device_lookup_private(&fd_cd, unit);
if (fd == NULL)
return ENXIO;
type = fd_dev_to_type(fd, dev);
if (type == NULL)
return ENXIO;
#ifdef DIAGNOSTIC
/*
* only got here if controller's drive queue was inactive; should
* be in idle state
*/
if (fdc->sc_state != DEVIDLE) {
printf("fdcstart: not idle\n");
return;
}
#endif
(void)fdcintr(fdc);
}
static void
fdcpstatus(int n, struct fdc_softc *fdc)
{
char bits[64];
loop:
fd = TAILQ_FIRST(&fdc->sc_drives);
if (fd == NULL) {
DPRINTF(("fdcintr: set DEVIDLE\n"));
if (fdc->sc_state == DEVIDLE) {
if ((intio_get_sicilian_intr() & SICILIAN_STAT_FDC)
!= 0) {
out_fdc(iot, ioh, NE7CMD_SENSEI);
if ((tmp = fdcresult(fdc)) != 2 ||
(st0 & 0xf8) != 0x20) {
goto loop;
}
}
}
/* no drives waiting; end */
fdc->sc_state = DEVIDLE;
return 1;
}
/* Is there a transfer to this drive? If not, deactivate drive. */
bp = bufq_peek(fd->sc_q);
if (bp == NULL) {
fd->sc_ops = 0;
TAILQ_REMOVE(&fdc->sc_drives, fd, sc_drivechain);
fd->sc_active = 0;
goto loop;
}
if (bp->b_flags & B_FORMAT)
finfo = (struct ne7_fd_formb *)bp->b_data;
switch (fdc->sc_state) {
case DEVIDLE:
DPRINTF(("fdcintr: in DEVIDLE\n"));
fdc->sc_errors = 0;
fd->sc_skip = 0;
fd->sc_bcount = bp->b_bcount;
fd->sc_blkno = bp->b_blkno / (FDC_BSIZE / DEV_BSIZE);
callout_stop(&fd->sc_motoroff_ch);
if ((fd->sc_flags & FD_MOTOR_WAIT) != 0) {
fdc->sc_state = MOTORWAIT;
return 1;
}
if ((fd->sc_flags & FD_MOTOR) == 0) {
/* Turn on the motor */
/* being careful about other drives. */
for (i = 0; i < 4; i++) {
struct fd_softc *ofd = fdc->sc_fd[i];
if (ofd != NULL &&
(ofd->sc_flags & FD_MOTOR) != 0) {
callout_stop(&ofd->sc_motoroff_ch);
ofd->sc_flags &=
~(FD_MOTOR | FD_MOTOR_WAIT);
break;
}
}
fd->sc_flags |= FD_MOTOR | FD_MOTOR_WAIT;
fd_set_motor(fdc, 0);
fdc->sc_state = MOTORWAIT;
#if 0 /* no need to callout on x68k; motor on will trigger interrupts */
/* allow .5s for motor to stabilize */
callout_reset(&fd->sc_motoron_ch, hz / 2,
fd_motor_on, fd);
#endif
return 1;
}
/* Make sure the right drive is selected. */
fd_set_motor(fdc, 0);
/* fall through */
case DOSEEK:
doseek:
DPRINTF(("fdcintr: in DOSEEK\n"));
if (fd->sc_cylin == bp->b_cylinder)
goto doio;
case DORESET:
DPRINTF(("fdcintr: in DORESET\n"));
/* try a reset, keep motor on */
fd_set_motor(fdc, 1);
DELAY(100);
fd_set_motor(fdc, 0);
fdc->sc_state = RESETCOMPLETE;
callout_reset(&fdc->sc_timo_ch, hz / 2, fdctimeout, fdc);
return 1; /* will return later */
case RESETCOMPLETE:
DPRINTF(("fdcintr: in RESETCOMPLETE\n"));
callout_stop(&fdc->sc_timo_ch);
/* clear the controller output buffer */
for (i = 0; i < 4; i++) {
out_fdc(iot, ioh, NE7CMD_SENSEI);
(void)fdcresult(fdc);
}
/* fall through */
case DORECAL:
DPRINTF(("fdcintr: in DORECAL\n"));
out_fdc(iot, ioh, NE7CMD_RECAL); /* recalibrate function */
out_fdc(iot, ioh, fd->sc_drive);
fdc->sc_state = RECALWAIT;
callout_reset(&fdc->sc_timo_ch, 5 * hz, fdctimeout, fdc);
return 1; /* will return later */
case RECALWAIT:
DPRINTF(("fdcintr: in RECALWAIT\n"));
callout_stop(&fdc->sc_timo_ch);
fdc->sc_state = RECALCOMPLETE;
/* allow 1/30 second for heads to settle */
#if 0
callout_reset(&fdc->sc_intr_ch, hz / 30, fdcpseudointr, fdc);
#endif
return 1; /* will return later */
case FDIOCSETFORMAT:
DPRINTF(("FDIOCSETFORMAT\n"));
if((flag & FWRITE) == 0)
return EBADF; /* must be opened for writing */
form_parms = (struct fdformat_parms *)addr;
if (form_parms->fdformat_version != FDFORMAT_VERSION)
return EINVAL; /* wrong version of formatting prog */
scratch = form_parms->nbps >> 7;
if ((form_parms->nbps & 0x7f) || ffs(scratch) == 0 ||
scratch & ~(1 << (ffs(scratch) - 1)))
/* not a power-of-two multiple of 128 */
return EINVAL;
case FDIOCFORMAT_TRACK:
DPRINTF(("FDIOCFORMAT_TRACK\n"));
if ((flag & FWRITE) == 0)
return EBADF; /* must be opened for writing */
form_cmd = (struct fdformat_cmd *)addr;
if (form_cmd->formatcmd_version != FDFORMAT_VERSION)
return EINVAL; /* wrong version of formatting prog */
case FDIOCGETOPTS: /* get drive options */
DPRINTF(("FDIOCGETOPTS\n"));
*(int *)addr = fd->sc_opts;
return 0;
case FDIOCSETOPTS: /* set drive options */
DPRINTF(("FDIOCSETOPTS\n"));
fd->sc_opts = *(int *)addr;
return 0;
case DIOCLOCK:
/*
* Nothing to do here, really.
*/
return 0; /* XXX */
case DIOCEJECT:
DPRINTF(("DIOCEJECT\n"));
if (*(int *)addr == 0) {
/*
* Don't force eject: check that we are the only
* partition open. If so, unlock it.
*/
if ((fd->sc_dk.dk_openmask & ~(1 << part)) != 0 ||
fd->sc_dk.dk_bopenmask + fd->sc_dk.dk_copenmask !=
fd->sc_dk.dk_openmask) {
return EBUSY;
}
}
/* FALLTHROUGH */
case ODIOCEJECT:
DPRINTF(("ODIOCEJECT\n"));
fd_do_eject(fdc, FDUNIT(dev));
return 0;
/*
* Build disk label. For now we only create a label from what we know
* from 'sc'.
*/
static int
fdgetdisklabel(struct fd_softc *sc, dev_t dev)
{
struct disklabel *lp;
int part;
DPRINTF(("fdgetdisklabel()\n"));
part = DISKPART(dev);
lp = sc->sc_dk.dk_label;
memset(lp, 0, sizeof(struct disklabel));
/*
* Mountroot hook: prompt the user to enter the root file system
* floppy.
*/
static void
fd_mountroot_hook(device_t dev)
{
struct fd_softc *fd = device_private(dev);
struct fdc_softc *fdc = device_private(device_parent(fd->sc_dev));
int c;
/* XXX device_unit() abuse */
fd_do_eject(fdc, device_unit(dev));
printf("Insert filesystem floppy and press return.");
for (;;) {
c = cngetc();
if ((c == '\r') || (c == '\n')) {
printf("\n");
break;
}
}
}